Dotnet Maui Net Maui 7.0
Dotnet Maui Net Maui 7.0
Dotnet Maui Net Maui 7.0
Overview
e OVERVIEW
Supported platforms
Troubleshooting
h WHAT'S NEW
Get started
c HOW-TO GUIDE
Installation
b GET STARTED
b GET STARTED
Learning resources
p CONCEPT
Custom renderers
Effects
XAML
p CONCEPT
Overview
Fundamentals
Compilation
Field modifiers
Generics
Markup extensions
Namespaces
Pass arguments
Fundamentals
p
p CONCEPT
Accessibility
App lifecycle
Behaviors
Data binding
Resource dictionaries
Shell
Single project
Triggers
User interface
p CONCEPT
Animation
Brushes
Controls
Graphics
Handlers
Layouts
Pages
Styles
Theming
Platform integration
p CONCEPT
App information
Clipboard
Device information
Device sensors
File picker
Network connectivity
Permissions
Photo picker
Preferences
Web authenticator
p CONCEPT
Local databases
Deployment
p CONCEPT
Hot restart
Project configuration
Using .NET MAUI, you can develop apps that can run on Android, iOS, macOS, and
Windows from a single shared code-base.
.NET MAUI is open-source and is the evolution of Xamarin.Forms, extended from mobile
to desktop scenarios, with UI controls rebuilt from the ground up for performance and
extensibility. If you've previously used Xamarin.Forms to build cross-platform user
interfaces, you'll notice many similarities with .NET MAUI. However, there are also some
differences. Using .NET MAUI, you can create multi-platform apps using a single project,
but you can add platform-specific source code and resources if necessary. One of the
key aims of .NET MAUI is to enable you to implement as much of your app logic and UI
layout as possible in a single code-base.
Write cross-platform apps in XAML and C#, from a single shared code-base in
Visual Studio.
Share UI layout and design across platforms.
Share code, tests, and business logic across platforms.
While the BCL enables apps running on different platforms to share common business
logic, the various platforms have different ways of defining the user interface for an app,
and they provide varying models for specifying how the elements of a user interface
communicate and interoperate. You can craft the UI for each platform separately using
the appropriate platform-specific framework (.NET for Android, .NET for iOS, .NET for
macOS, or WinUI 3), but this approach then requires you to maintain a code-base for
each individual family of devices.
.NET MAUI provides a single framework for building the UIs for mobile and desktop
apps. The following diagram shows a high-level view of the architecture of a .NET MAUI
app:
In a .NET MAUI app, you write code that primarily interacts with the .NET MAUI API (1).
.NET MAUI then directly consumes the native platform APIs (3). In addition, app code
may directly exercise platform APIs (2), if required.
.NET MAUI apps can be written on PC or Mac, and compile into native app packages:
Android apps built using .NET MAUI compile from C# into intermediate language
(IL) which is then just-in-time (JIT) compiled to a native assembly when the app
launches.
iOS apps built using .NET MAUI are fully ahead-of-time (AOT) compiled from C#
into native ARM assembly code.
macOS apps built using .NET MAUI use Mac Catalyst, a solution from Apple that
brings your iOS app built with UIKit to the desktop, and augments it with
additional AppKit and platform APIs as required.
Windows apps built using .NET MAUI use Windows UI 3 (WinUI 3) library to create
native apps that target the Windows desktop. For more information about WinUI
3, see Windows UI Library.
7 Note
Single project
.NET MAUI single project takes the platform-specific development experiences you
typically encounter while developing apps and abstracts them into a single shared
project that can target Android, iOS, macOS, and Windows.
A single shared project that can target Android, iOS, macOS, and Windows.
A simplified debug target selection for running your .NET MAUI apps.
Shared resource files within the single project.
A single app manifest that specifies the app title, id, and version.
Access to platform-specific APIs and tools when required.
A single cross-platform app entry point.
.NET MAUI single project is enabled using multi-targeting and the use of SDK-style
projects. For more information about .NET MAUI single project, see .NET MAUI single
project.
Hot reload
.NET MAUI includes support for .NET hot reload, which enables you to modify your
managed source code while the app is running, without the need to manually pause or
hit a breakpoint. Then, your code edits can be applied to your running app without
recompilation.
.NET MAUI also includes support for XAML hot reload, which enables you to save your
XAML files and see the changes reflected in your running app without recompilation. In
addition, your navigation state and data will be maintained, enabling you to quickly
iterate on your UI without losing your place in the app.
Supported platforms for .NET MAUI
apps
Article • 10/24/2022 • 2 minutes to read
.NET Multi-platform App UI (.NET MAUI) apps can be written for the following platforms:
.NET MAUI Blazor apps have the following additional platform requirements:
.NET MAUI Blazor apps also require an updated platform specific WebView control. For
more information, see Blazor supported platforms.
.NET MAUI apps for Android, iOS, and Windows can be built in Visual Studio. However, a
networked Mac is required for iOS development.
Visual Studio
Prerequisites
Visual Studio 2022 17.3 or greater. For information about supported operating
systems, hardware, supported languages, and additional requirements and
guidance, see Visual Studio 2022 System Requirements.
To build, sign, and deploy .NET MAUI apps for iOS, you'll also need:
A Mac that is compatible with the latest version of Xcode. For more
information, see Apple's minimum requirements documentation
The latest version of Xcode .
An Apple ID and paid Apple Developer Program enrollment. An Apple ID
is required to deploy apps to devices, and to submit apps to the Apple Store.
Alternatively, to deploy debug builds of your app directly from Windows to your
iOS device with hot restart, you'll need:
Installation
1. To create .NET MAUI apps, you'll need the latest version of Visual Studio 2022:
Next steps
To learn how to create and run your first .NET MAUI app in Visual Studio 2022 on
Windows, or Visual Studio 2022 for Mac, click the button below.
In this tutorial, you'll learn how to create and run your first .NET Multi-platform App UI
(.NET MAUI) app in Visual Studio 2022 on Windows or Visual Studio 2022 for Mac. This
will help to ensure that your development environment is correctly set up.
Visual Studio
Prerequisites
Visual Studio 2022 17.3 or greater, with the .NET Multi-platform App UI
workload installed. For more information, see Installation.
Create an app
In this tutorial, you'll create your first .NET MAUI app in Visual Studio 2022 and run
it on Windows:
1. Launch Visual Studio 2022. In the start window, click Create a new project to
create a new project:
2. In the Create a new project window, select MAUI in the All project types
drop-down, select the .NET MAUI App template, and click the Next button:
3. In the Configure your new project window, name your project, choose a
suitable location for it, and click the Next button:
4. In the Additional information window, choose the version of .NET that you'd
like to target and click the Create button:
5. Wait for the project to be created and its dependencies to be restored:
6. In the Visual Studio toolbar, use the Debug Target drop-down to select
Framework and then the net6.0-windows entry:
7. In the Visual Studio toolbar, press the Windows Machine button to build and
run the app:
If you've not enabled Developer Mode, Visual Studio will prompt you to
enable it. In the Enable Developer Mode for Windows dialog, click settings
for developers to open the Settings app:
In the Settings app, turn on Developer Mode and accept the disclaimer:
Close the Settings app and then close the Enable Developer Mode for
Windows dialog.
8. In the running app, press the Click me button several times and observe that
the count of the number of button clicks is incremented:
Troubleshooting
If your app fails to compile, review Troubleshooting known issues, which may have a
solution to your problem.
Next steps
In this tutorial, you've learned how to create and run your first .NET Multi-platform App
UI (.NET MAUI) app.
To learn the fundamentals of building an app with .NET MAUI, see Create a .NET MAUI
app. Alternatively, for a full .NET MAUI training course, see Build mobile and desktop
apps with .NET MAUI.
Resources for learning .NET MAUI
Article • 09/21/2022 • 2 minutes to read
There are many different resources available for you to use to learn .NET Multi-platform
App UI (.NET MAUI). There are Microsoft Learn modules, workshops, videos, and
podcasts. Each varies in its depth and the topics it covers.
Learn how to use .NET MAUI to build apps that run on mobile devices and the
desktop using C# and Visual Studio. You'll learn the fundamentals of building an
app with .NET MAUI and more advanced topics such as local data storage and
invoking REST-based web services.
Follow a short video series that teaches you how to get started with .NET MAUI
and Visual Studio, to build your very first cross-platform desktop and mobile app.
Learn how to build a .NET MAUI app that displays a list of monkeys from around
the world. You'll start by building the business logic backend that retrieves JSON-
encoded data from a REST-based endpoint. You then use .NET MAUI to find the
closest monkey to you, and show the monkey on a map. You'll also examine how
to display data using different approaches, and then finally fully theme the app.
This book provides real world solutions for addressing challenges faced when
building an enterprise app using .NET MAUI. The book covers topics such as:
Model-View-ViewModel (MVVM) pattern
Dependency injection
Navigation
Configuration
Loose-coupling of components
Additional enterprise concerns
The content of this book is helpful for anyone looking to build a new app or
looking to solve the problems of apps that evolve over time.
Download and explore the code of different example .NET MAUI apps.
.NET MAUI podcast
Keep up with the latest news in the world of mobile and desktop development
with the official .NET MAUI podcast.
How to enable hardware acceleration
with Android emulators (Hyper-V &
HAXM)
Article • 06/24/2022 • 4 minutes to read
This article explains how to use your computer's hardware acceleration features to
maximize Android emulator performance.
With Visual Studio, you can easily test and debug your .NET MAUI app for Android in
situations where an Android device isn't available. However, if hardware acceleration
isn't available or enabled, the emulator will run too slow. You can drastically improve the
performance of the emulator by enabling hardware acceleration and using x86-64 or
x86 virtual device images.
For the best experience on Windows, it's recommended you use WHPX to accelerate the
Android emulator. If WHPX isn't available on your computer, then HAXM can be used.
The Android emulator automatically uses hardware acceleration if the following criteria
are met:
The emulator is running a system image created for an x86-64 or x86-based virtual
device.
) Important
You can't run a VM-accelerated emulator inside another VM, such as a VM hosted
by VirtualBox, VMware, or Docker (unless using WSL2). You must run the Android
emulator directly on your system hardware .
For information about launching and debugging with the Android emulator, see
Debugging on the Android Emulator.
To verify that your computer hardware and software is compatible with Hyper-V, open a
command prompt and type the following command:
cmd
systeminfo
If all listed Hyper-V requirements have a value of Yes, then your computer can support
Hyper-V. For example:
If the Hyper-V result indicates that a hypervisor is currently running, Hyper-V is already
enabled.
1. Enter windows features in the Windows search box and select Turn Windows
features on or off in the search results. In the Windows Features dialog, enable
both Hyper-V and Windows Hypervisor Platform:
After making these changes, reboot your computer.
) Important
On Windows 10 October 2018 Update (RS5) and higher, you only need to enable
Hyper-V, as it will use Windows Hypervisor Platform (WHPX) automatically.
1. Make sure that the virtual device you created in the Android Device Manager is an
x86-64 or x86-based system image. If you use an Arm-based system image, the
virtual device won't be accelerated and will run slowly.
After Hyper-V is enabled, you'll be able to run your accelerated Android emulator.
) Important
If your computer doesn't support Hyper-V, you may use HAXM to accelerate the
Android emulator. To use HAXM, disable Device Guard.
Verifying HAXM support
To determine if your hardware supports HAXM, follow the steps in Does My Processor
Support Intel Virtualization Technology? . If your hardware supports HAXM, you can
check to see if HAXM is already installed by using the following steps:
cmd
sc query intelhaxm
2. Examine the output to see if the HAXM process is running. If it is, you should see
output listing the intelhaxm state as RUNNING . For example:
If your computer can support HAXM but HAXM isn't installed, use the steps in the next
section to install HAXM.
Installing HAXM
HAXM install packages for Windows are available from the Intel Hardware Accelerated
Execution Manager GitHub releases page. Use the following steps to download and
install HAXM:
1. From the Intel website, download the latest HAXM virtualization engine installer
for Windows. The advantage of downloading the HAXM installer directly from the
Intel website is that you can be assured of using the latest version.
2. Run intelhaxm-android.exe to start the HAXM installer. Accept the default values
in the installer dialogs.
When you create a virtual device, be sure to select an x86_64 or x86-based system
image. If you use an Arm-based system image, the virtual device will not be accelerated
and will run slowly.
Troubleshooting
For help with troubleshooting hardware acceleration issues, see the Android emulator
Troubleshooting guide.
Related Links
Run Apps on the Android Emulator
Managing virtual devices with the
Android Device Manager
Article • 12/12/2022 • 6 minutes to read
This article explains how to use the Android Device Manager to create and configure
Android Virtual Devices (AVDs) that emulate physical Android devices. You can use these
virtual devices to run and test your app without having to rely on a physical device.
) Important
Enable hardware acceleration for the Android devices. For more information, see
Hardware Acceleration for Emulator Performance.
Requirements
To use the Android Device Manager, you'll need the following items:
The Android SDK API Level 30 or later. Be sure to install the Android SDK at its
default location if it isn't already installed: C:\Program Files (x86)\Android\android-
sdk.
These packages should be displayed with Installed status as seen in the following
screenshot:
When you install the .NET Multi-Platform App UI development workload in Visual
Studio, everything is installed for you. For more information on setting up .NET MAUI
with Visual Studio, see Build your first app.
When you select a device in the list, the Start button appears on the right. Press the
Start button to launch the emulator with this virtual device. If the emulator is running
with the selected virtual device, the Start button changes to a Stop button that you can
use to halt the emulator.
The New Device window is displayed. To configure the device, follow these steps:
1. Give the device a new name. In the following example, the new device is named
Pixel 3a - API 31.
2. Select a physical device to emulate by selecting a device in the Base Device box.
3. Select a processor type for this virtual device with the Processor box.
It's recommended that you choose x86_64 and enable hardware acceleration.
If you select an Android API level that has not yet been installed, the Device
Manager will display A new device will be downloaded message at the bottom of
the screen – it will download and install the necessary files as it creates the new
virtual device.
5. If you want to include Google Play Services APIs in your virtual device, select the
Google APIs option. To include the Google Play Store app on the virtual device,
select the Google Play Store option
7 Note
Google Play Store images are available only for some base device types such
as Pixel, Pixel 2, Pixel 3, and Nexus 5. This is indicated by the text (+ Store) in
the image name.
6. Use the property list to change some of the most commonly modified properties.
To make changes to properties, see Editing Android Virtual Device Properties.
7. Add any additional properties that you need to explicitly set with the Add Property
box at the bottom of the window:
You can also define a custom property by selecting Custom....
You might get a License Acceptance screen when you create the device. Select
Accept if you agree to the license terms.
9. The Android Device Manager adds the new device to the list of installed virtual
devices while displaying a Creating progress indicator during device creation:
10. When the creation process is complete, the new device is shown in the list of
installed virtual devices with a Start button, ready to launch
Edit device
To edit an existing virtual device, select the device and then press the Edit button:
Pressing Edit displays the Device Editor window for the selected virtual device.
The Device Editor window lists the properties of the virtual device under the Property
column, with the corresponding values of each property in the Value column. When you
select a property, a detailed description of that property is displayed on the right.
To change a property, edit its value in the Value column. For example, in the following
screenshot the hw.lcd.density property is being changed to 240:
After you've made the necessary configuration changes, press the Save button. For
more information about changing virtual device properties, see Editing Android Virtual
Device Properties.
Additional options
Additional options for working with devices are available from the Additional Options
(…) pull-down menu:
Duplicate and Edit – Duplicates the currently selected device and opens it in the
New Device screen with a new name that's similar to the existing device. For
example, selecting Pixel 3a - API 31 and pressing Duplicate and Edit appends a
counter to the name: Pixel 3a - API 31 (1).
Start with Factory Defaults – Starts the device with a cold boot.
Start with Kernel Logs – Starts the emulator and opens up kernel logs directory.
Download System Image – Downloads the Android OS system image for the
device, if it's not already downloaded.
Reveal in Explorer – Opens Windows Explorer and navigates to the folder that
holds the files for the virtual device.
Factory Reset – Resets the selected device to its default settings, erasing any user
changes made to the internal state of the device while it was running. This action
also erases the current Fast Boot snapshot if it exists. This change doesn't alter
modifications that you make to the virtual device during creation and editing. A
dialog box will appear with the reminder that this reset cannot be undone – press
Factory Reset to confirm the reset.
Delete – Permanently deletes the selected virtual device. A dialog box will appear
with the reminder that deleting a device cannot be undone. Press Delete if you are
certain that you want to delete the device.
Troubleshooting
The following sections explain how to diagnose and work around problems that may
occur when using the Android Device Manager to configure virtual devices.
If you see that error dialog, press Open SDK Manager to open the Android SDK
Manager. In the Android SDK Manager, go to the Tools tab and install the following
packages:
After these changes are made, the AVD will restart in a state that allows Wi-Fi to work
again.
Editing Android virtual device
properties
Article • 08/09/2022 • 14 minutes to read
This article explains how to use the Android Device Manager (AVD) to edit the profile
properties of an Android virtual device.
When you select a property, a detailed description of that property is displayed on the
right. You can modify hardware profile properties and AVD properties. Hardware profile
properties (such as hw.ramSize and hw.accelerometer ) describe the physical
characteristics of the emulated device. These characteristics include screen size, the
amount of available RAM, whether or not an accelerometer is present. AVD properties
specify the operation of the AVD when it runs. For example, AVD properties can be
configured to specify how the AVD uses your development computer's graphics card for
rendering.
You can change properties by using the following guidelines:
To change a boolean property, click the check mark to the right of the boolean
property:
The following table provides a detailed explanation of the properties listed in the New
Device and Device Editor screens:
abi.type ABI type – Specifies the ABI (application binary x86, x86_64,
interface) type of the emulated device. The x86 armeabi-
option is for the instruction set commonly v7a, arm64-
referred to as "x86" or "IA-32." The x86_64 v8a
option is for the 64-bit x86 instruction set. The
armeabi-v7a option is for the ARM instruction
set with v7-a ARM extensions. The arm64-v8a
option is for the ARM instruction set that
supports AArch64.
Property Description Options
hw.lcd.density LCD density – The density of the emulated LCD 120, 160,
display, measured in density-independent pixels, 240, 213,
or dp (dp is a virtual pixel unit). When the setting 320
is 160 dp, each dp corresponds to one physical
pixel. At runtime, Android uses this value to
select and scale the appropriate resources/assets
for correct display rendering.
For more information about these properties, see Hardware Profile Properties .
Debug on the Android Emulator
Article • 08/18/2022 • 2 minutes to read
After you've chosen a virtual device from the Debug Target device drop-down menu,
select either Debug or Release mode, then select the Play button to run the application:
After the emulator starts, Visual Studio deploys the app to the virtual device. An
example screenshot of the Android Emulator is displayed below. In this example, the
emulator is running the .NET MAUI template app.
When you're finished debugging and running your app, you can leave the emulator
running. The first time a .NET MAUI app is run in the emulator, the .NET MAUI shared
runtime for the targeted API level is installed, followed by the app. The runtime
installation may take a few moments to install. If you leave the emulator running, later
debugging sessions start faster as the runtime is already present on the device. If the
device is restarted, the runtime will be redeployed to the device.
Fast boot
The Android Emulator includes a feature named Fast Boot which is enabled by default.
This feature is configured by each device's emulator settings. With this feature enabled,
a snapshot of the virtual device is saved when the emulator is closed. The snapshot is
quickly restored the next time the device is started.
The first time a virtual device is started, a cold boot of the virtual device takes place
without a speed improvement because a snapshot hasn't yet been created:
When you exit out of the emulator, Fast Boot saves the state of the emulator in a
snapshot:
The next time the virtual device starts, it loads much faster because the emulator simply
restores the state at which you closed the emulator.
Troubleshooting
For tips and workarounds for common emulator problems, see Android Emulator
Troubleshooting.
For more information about using the Android Emulator, see the following Android
Developer articles:
This article describes the most common warning messages and issues that occur while
configuring and running the Android Emulator. Also, it describes solutions for resolving
these errors and various troubleshooting tips to help you diagnose emulator problems.
Deployment errors
If you see an error about a failure to install the APK on the emulator or a failure to run
the Android Debug Bridge (adb), verify that the Android SDK can connect to your
emulator. To verify emulator connectivity, use the following steps:
1. Launch the emulator from the Android Device Manager (select your virtual device
and select Start).
2. Open a command prompt and go to the folder where adb is installed. If the
Android SDK is installed at its default location, adb is located at C:\Program Files
(x86)\Android\android-sdk\platform-tools\adb.exe; if not, modify this path for the
location of the Android SDK on your computer.
shell
adb devices
4. If the emulator is accessible from the Android SDK, the emulator should appear in
the list of attached devices. For example:
shell
emulator-5554 device
5. If the emulator doesn't appear in this list, start the Android SDK Manager, apply all
updates, then try launching the emulator again.
For example, this virtual device will include Google Play Services and Google Play Store:
7 Note
Google Play Store images are available only for some base device types such as
Pixel, Pixel 2, Nexus 5, and Nexus 5X.
Performance issues
Performance issues are typically caused by one of the following problems:
To fix this error, follow the troubleshooting steps in the Hardware acceleration issues
section.
cmd
This command assumes that the Android SDK is installed at the default location of
C:\Program Files (x86)\Android\android-sdk. If the Android SDK is installed elsewhere,
modify the preceding command to the correct location.
Tip
Make sure the Android Emulator is up to date. From Visual Studio, press Tools >
Android > Android SDK Manager. Select the Tools tab and see if the Android
Emulator entry has an update available.
cmd
cmd
If hardware acceleration isn't available, a message like the following example will be
displayed (the emulator looks for HAXM if it's unable to find Hyper-V):
cmd
If hardware acceleration isn't available, see Enabling Hyper-V acceleration to learn how
to enable hardware acceleration on your computer.
To correct this problem, reboot into your computer's BIOS and enable the following
options:
If problems still occur because of issues related to Hyper-V and HAXM, see the following
section.
Hyper-V issues
In some cases, enabling both Hyper-V and Windows Hypervisor Platform in the Turn
Windows features on or off dialog may not properly enable Hyper-V. To verify that
Hyper-V is enabled, use the following steps:
PowerShell
cmd
FeatureName : Microsoft-Hyper-V-All
DisplayName : Hyper-V
RestartRequired : Possible
State : Disabled
CustomProperties :
If the Hypervisor isn't enabled, a message similar to the following example will be
displayed to indicate that the state of HypervisorPlatform is Disabled:
cmd
FeatureName : HypervisorPlatform
RestartRequired : Possible
State : Disabled
CustomProperties :
PowerShell
For more information about enabling Hyper-V (including techniques for enabling
Hyper-V using the Deployment Image Servicing and Management tool), see Install
Hyper-V.
HAXM issues
HAXM issues are often the result of conflicts with other virtualization technologies,
incorrect settings, or an out-of-date HAXM driver.
cmd
sc query intelhaxm
If the HAXM process is running, you should see output similar to the following result:
cmd
SERVICE_NAME: intelhaxm
TYPE : 1 KERNEL_DRIVER
STATE : 4 RUNNING
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
For information about troubleshooting HAXM, see Troubleshooting in the HAXM wiki.
HAXM can conflict with other technologies that use virtualization, such as Hyper-V,
Windows Device Guard, and some antivirus software:
Hyper-V—If you're using a version of Windows before the Windows 10 April 2018
update (build 1803) and Hyper-V is enabled, follow the steps in Disabling Hyper-V
so that HAXM can be enabled.
Device Guard—Device Guard and Credential Guard can prevent Hyper-V from
being disabled on Windows machines. To disable Device Guard and Credential
Guard, see Disabling Device Guard.
On Windows, HAXM won't work unless virtualization technology (Intel VT-x) is enabled
in the BIOS. If VT-x is disabled, you'll get an error similar to the following when you
attempt to start the Android Emulator:
This computer meets the requirements for HAXM, but Intel Virtualization
Technology (VT-x) isn't turned on.
To correct this error, boot the computer into the BIOS, enable both VT-x and SLAT
(Second-Level Address Translation) and restart the computer.
Disabling Hyper-V
If you're using a version of Windows before the Windows 10 April 2018 Update (build
1803) and Hyper-V is enabled, you must disable Hyper-V and reboot your computer to
install and use HAXM. If you're using Windows 10 April 2018 Update (build 1803) or
later, Android Emulator version 27.2.7 or later can use Hyper-V (instead of HAXM) for
hardware acceleration, so it isn't necessary to disable Hyper-V.
You can disable Hyper-V from the Control Panel by following these steps:
1. Enter windows features in the Windows search box and select Turn Windows
features on or off in the search results.
2. Uncheck Hyper-V:
Alternately, you can use the following PowerShell command to disable the Hyper-V
Hypervisor:
PowerShell
Intel HAXM and Microsoft Hyper-V can't both be active at the same time. Unfortunately,
there's no way to switch between Hyper-V and HAXM without restarting your computer.
It's possible that the preceding steps won't succeed in disabling Hyper-V if Device Guard
and Credential Guard are enabled. If you're unable to disable Hyper-V, or it seems to be
disabled but HAXM installation still fails, use the steps in the next section to disable
Device Guard and Credential Guard.
Device Guard and Credential Guard can prevent Hyper-V from being disabled on
Windows machines. This situation is often a problem for domain-joined machines that
are configured and controlled by an owning organization. On Windows 10, use the
following steps to see if Device Guard is running:
1. Enter System info in the Windows search box and select System Information in
the search results.
2. In the System Summary, look to see if Device Guard Virtualization based security
is present and is in the Running state:
2. In the Windows Search Box, enter gpedit.msc and select the Edit group policy
search result. These steps launch the Local Group Policy Editor.
4. Change Turn On Virtualization Based Security to Disabled (as shown above) and
exit the Local Group Policy Editor.
5. In the Windows Search Box, enter cmd. When Command Prompt appears in the
search results, right-click Command Prompt and select Run as Administrator.
6. Copy and paste the following commands into the command prompt window (if
drive Z: is in use, pick an unused drive letter to use instead):
cmd
mountvol Z: /s
copy %WINDIR%\System32\SecConfig.efi
Z:\EFI\Microsoft\Boot\SecConfig.efi /Y
mountvol Z: /d
7. Restart your computer. On the boot screen, you should see a prompt similar to the
following message:
If Hyper-V is still not disabled, the policies of your domain-joined computer may prevent
you from disabling Device Guard or Credential Guard. In this case, you can request an
exemption from your domain administrator to allow you to opt out of Credential Guard.
Alternately, you can use a computer that isn't domain-joined if you must use HAXM.
You can launch the emulator with an AVD image from this location by passing in the
folder name of the AVD. For example, this command launches an AVD named
Pixel_API_27:
cmd
This command assumes that the Android SDK is installed at the default location of
C:\Program Files (x86)\Android\android-sdk. If the Android SDK is installed elsewhere,
modify the preceding command to the correct location.
When you run this command, it produces many lines of output while the emulator starts
up. Specifically, lines such as the following example are printed if hardware acceleration
is enabled and working properly. In this example, HAXM is used for hardware
acceleration:
cmd
emulator: CPU Acceleration status: HAXM version 6.2.1 (4) is installed and
usable.
You can view each DeviceManager.log file by using a text editor such as Notepad. The
following example log entry indicates that HAXM wasn't found on the computer:
cmd
While the Android emulator is a great way to rapidly develop and test your app, you'll
want to test your apps on a real Android device. To run on a device, you'll need to
enable developer mode on the device and connect it to your computer.
) Important
The steps in this article are written generically, to work on as many devices as
possible. If you can't find these settings on your device, consult your device
manufacturer's documentation.
Depending on the UI your device is running, the About phone option may be in a
different location. Consult your device documentation if you can't find About phone.
Depending on the UI your device is running, the USB debugging option may be in a
different location. Consult your device documentation if you can't find USB debugging.
You'll receive a prompt to trust the computer on your device if you haven't used it for
debugging before. You can also check Always allow from this computer to prevent
requiring this prompt each time you connect the device.
If your computer isn't recognizing the device when it's plugged in, try installing a driver
for the device. Consult your device manufacturer's support documentation. You can also
try installing the Google USB Driver through the Android SDK Manager:
Enable WiFi debugging
It's possible to debug an android device over WiFi, without keeping the device physically
connected to the computer. This technique requires more effort, but could be useful
when the device is too far from the computer to remain constantly plugged-in via a
cable.
1. Follow the steps in the Enable developer mode on the device section.
2. Follow the steps in the Enable USB debugging section.
3. Go to the Settings screen.
4. Select Developer options.
5. Turn on the Wireless debugging option.
Depending on the UI your device is running, the Wireless debugging option may be in
a different location. Consult your device documentation if you can't find Wireless
debugging.
Next, use adb to connect to your device, first through a USB connection:
1. Determine the IP address of your Android device. One way to find out the IP
address is to look under Settings > Network & internet > Wi-Fi, then tap on the
WiFi network that the device is connected to, and then tap on Advanced. This will
open a drop-down showing information about the network connection, similar to
what is seen in the screenshot below:
On some versions of Android the IP address won't be listed there but can be found
instead under Settings > About phone > Status.
2. In Visual Studio, open the adb command prompt by selecting the menu option:
Tools > Android > Android Adb Command Prompt....
3. In the command prompt, use the adb tcpip command to tell the device to listen
to TCP/IP connections on port 5555.
command
command
When this command finishes, the Android device is connected to the computer via
WiFi.
When you're finished debugging via WiFi, you can reset ADB back to USB mode
with the following command:
command
adb usb
To see the devices connected to the computer, use the adb devices command:
command
adb devices
In this tutorial, you'll learn how to create and run a .NET Multi-platform App UI (.NET
MAUI) app on iOS using .NET Command Line Interface (CLI) on macOS:
1. To create .NET MAUI apps, you'll need to download and run the installer for the
latest .NET runtime. You'll also need to download and install the latest version of
Xcode, which is also available from the App Store app on your Mac.
2. On your Mac, open Terminal and check that you have the latest .NET runtime
installed:
zsh
dotnet --version
zsh
This command will install the latest released version of .NET MAUI, including the
required platform SDKs.
zsh
5. In Terminal, change directory to MyMauiApp, and build and run the app:
zsh
cd MyMauiApp
The dotnet build command will restore the project the dependencies, build the
app, and launch it in the default simulator.
6. In the default simulator, press the Click me button several times and observe that
the count of the number of button clicks is incremented.
2. Right-click on your chosen simulator, and select Copy Identifier to copy the UDID
to the clipboard.
Alternatively, you can retrieve a list of UDID values by executing the simctl list
command:
zsh
/Applications/Xcode.app/Contents/Developer/usr/bin/simctl list
3. In Terminal, build the app and run it on your chosen simulator by specifying the
_DeviceName MSBuild property using the -p MSBuild option:
zsh
dotnet build -t:Run -f net7.0-ios -
p:_DeviceName=:v2:udid=insert_UDID_here
For example, use the following command to build the app and run it on the iPhone
13 Pro simulator:
zsh
4. In your chosen simulator, press the Click me button several times and observe that
the count of the number of button clicks is incremented.
Launch the app on a device
A device must be provisioned before you can deploy an iOS app to it. For more
information, see Device provisioning for iOS. Once a device has been provisioned, a
.NET MAUI iOS app can be launched on the device from a Mac by providing its unique
device id (UDID):
Alternatively, right-click on your device and select Copy Identifier to copy the
UDID to the clipboard.
5. In Terminal, build the app and run it on your chosen simulator by specifying the
_DeviceName MSBuild property using the -p MSBuild option:
zsh
Building native iOS applications using .NET Multi-platform App UI (.NET MAUI) requires
access to Apple's build tools, which only run on a Mac. Because of this, Visual Studio
2022 must connect to a network-accessible Mac to build .NET MAUI iOS apps.
Visual Studio 2022's Pair to Mac feature discovers, connects to, authenticates with, and
remembers Mac build hosts so that you can work productively on Windows.
You can write .NET MAUI iOS code in Visual Studio 2022.
Visual Studio 2022 opens a network connection to a Mac build host and uses the
build tools on that machine to compile and sign the iOS app.
There's no need to run a separate application on the Mac – Visual Studio 2022
invokes Mac builds securely over SSH.
Visual Studio 2022 is notified of changes as soon as they happen. For example,
when an iOS device is plugged into the Mac or becomes available on the network,
the iOS Toolbar updates instantly.
Multiple instances of Visual Studio 2022 can connect to the Mac simultaneously.
It's possible to use the Windows command-line to build iOS apps.
7 Note
Before following the instructions in this article, on a Mac, install Xcode . Then
manually open Xcode, after installation, so that it can add additional components.
In addition, you should also install either the latest Visual Studio 2022 for Mac or
Mono . In addition, if you have a Mac computer with Apple silicon please ensure
that Rosetta is installed.
If you would prefer not to install Visual Studio 2022 for Mac, Visual Studio 2022 can
automatically configure the Mac build host. However, you must still install and run
Xcode, and install Mono.
Make sure that it's configured to allow access for All users, or that your Mac
username or group is included in the list of allowed users.
3. If prompted, configure the macOS firewall. If you have set the macOS firewall to
block incoming connections, you may need to allow mono-sgen to receive
incoming connections. An alert appears to prompt you if so.
4. If it's on the same network as the Windows machine, the Mac should now be
discoverable by Visual Studio 2022. If the Mac is still not discoverable, try manually
adding a Mac.
1. In Visual Studio 2022, open an existing .NET MAUI project or create a new one.
2. Open the Pair to Mac dialog with the Pair to Mac button iOS toolbar:
Alternatively, select Tools > iOS > Pair to Mac.
The Pair to Mac dialog displays a list of all previously connected and currently
available Mac build hosts:
4. Enter your username and password. The first time you connect to any particular
Mac, you're prompted to enter your username and password for that machine:
Tip
Pair to Mac uses these credentials to create a new SSH connection to the Mac. If it
succeeds, a key is added to the authorized_keys file on the Mac. Subsequent
connections to the same Mac will log in automatically.
5. Pair to Mac automatically configures the Mac. Visual Studio 2022 installs or
updates pre-requisites on a connected Mac build host as needed. However, Xcode
must still be installed manually.
6. Examine the connection status icon. When Visual Studio 2022 is connected to a
Mac, that Mac's item in the Pair to Mac dialog displays an icon indicating that it's
currently connected:
Tip
Right-clicking any Mac in the Pair to Mac list brings up a context menu that
allows you to Connect..., Forget this Mac, or Disconnect:
If you choose Forget this Mac, your credentials for the selected Mac will be
forgotten. To reconnect to that Mac, you will need to re-enter your username
and password.
If you've successfully paired to a Mac build host, you're ready to build .NET MAUI iOS
apps in Visual Studio 2022. For more information, see Build your first app.
If you haven't been able to pair a Mac, try manually adding a Mac.
1. Open System Preferences > Sharing > Remote Login on your Mac to locate your
Mac’s IP address:
Alternatively, use the command line. In Terminal, issue the following command:
zsh
Depending on your network configuration, you may need to use an interface name
other than en0 , for example, en1 or en2 .
Tip
5. Select Login to connect Visual Studio 2022 to the Mac over SSH and add it to the
list of known machines.
) Important
Pair to Mac can't install Xcode. You must manually install it on the Mac build
host. It's required for .NET MAUI iOS development.
Automatic Mac provisioning requires that remote login is enabled on the Mac,
and the Mac must be network-accessible to the Windows machine.
Automatic Mac provisioning requires sufficient free space on the Mac to
install .NET.
In addition, Pair to Mac performs required software installations and updates to the
Mac, when Visual Studio 2022 connects to it.
In addition, Pair to Mac will install or update various packages distributed with Xcode.
The installation of these packages happens quickly and without a prompt.
dotnet
ServerUser – the username to use when logging in to the Mac build host. Use your
system username rather than your full name.
ServerPassword – the password to use when logging in to the Mac build host.
_DotNetRootRemoteDirectory - the folder on the Mac build host that contains the
.NET SDK.
The first time Pair to Mac logs in to a Mac build host from either Visual Studio 2022 or
the command-line, it sets up SSH keys. With these keys, future logins won't require a
username or password. Newly created keys are stored in
%LOCALAPPDATA%\Xamarin\MonoTouch.
If the ServerPassword parameter is omitted from a command-line build invocation, Pair
to Mac attempts to log in to the Mac build host using the saved SSH keys.
Remote iOS Simulator for Windows
Article • 12/02/2022 • 2 minutes to read
The remote iOS Simulator for Windows allows you to test your apps on an iOS simulator
displayed in Windows alongside Visual Studio 2022.
Get started
The remote iOS Simulator for Windows is installed automatically as part of the .NET
Multi-platform App UI development workload in Visual Studio 2022. To use it, follow
these steps:
1. Launch Visual Studio 2022 and create or load a .NET MAUI app project.
2. In Visual Studio 2022, pair the IDE to a Mac Build host if you haven't previously. For
more information, see Pair to Mac for iOS development.
3. In the Visual Studio toolbar, use the Debug Target drop-down to select iOS
Simulators and then a specific iOS simulator:
4. In the Visual Studio toolbar, press the green Start button for your chosen iOS
simulator:
Visual Studio will build the app, start the remote iOS simulator for Windows, and
deploy the app to the simulator:
Enable the remote iOS simulator for Windows
The remote iOS simulator for Windows is enabled by default. However, if it's been
previously disabled it can be enabled in Visual Studio by navigating to Tools > Options
> Xamarin > iOS Settings and ensuring that Remote Simulator to Windows is checked:
7 Note
When the remote simulator is disabled in Visual Studio, debugging a .NET MAUI
iOS app will open the iOS Simulator on the connected Mac build host.
Clicking the toolbar's Settings button (the gear icon) opens the Settings window:
These settings allow you to enable the hardware keyboard and reset the content and
settings for the simulator.
Clicking the toolbar's Other options button (the ellipsis icon) reveals additional buttons
such as rotation, shake gestures, and rebooting:
7 Note
Right-clicking anywhere in the remote iOS simulator window will display all the
toolbar buttons as a context menu.
Touchscreen support
Many Windows computers have touch screens. Since the remote iOS Simulator for
Windows supports touch interactions, you can test your app with the same pinch, swipe,
and multi-finger touch gestures that you use with physical iOS devices.
Similarly, the remote iOS Simulator for Windows treats Windows Stylus input as Apple
Pencil input.
Sound handling
Sounds played by the simulator will come from the host Mac's speakers. iOS sounds are
not heard on Windows.
Troubleshooting
In some circumstances, an Xcode configuration problem can result in the remote iOS
Simulator for Windows getting stuck in a Connecting to Mac...Checking
Server...Connected... loop. When this occurs, you need to remove and reset the
Simulators on your Mac build host:
Ensure that Xamarin Mac Agent (XMA) and Xcode aren't running.
Delete your ~/Library/Developer/CoreSimulator/Devices folder.
Run killall -9 com.apple.CoreSimulator.CoreSimulatorService .
Run xcrun simctl list devices .
Logs
If you experience issues with the remote iOS Simulator, you can view the logs in the
following locations:
Mac – ~/Library/Logs/Xamarin/Simulator.Server
Windows – %LOCALAPPDATA%\Xamarin\Logs\Xamarin.Simulator
Build a Mac Catalyst app with .NET CLI
Article • 03/23/2023 • 2 minutes to read
In this tutorial, you'll learn how to create and run a .NET Multi-platform App UI (.NET
MAUI) app on Mac Catalyst using .NET Command Line Interface (CLI) on macOS:
1. To create .NET MAUI apps, you'll need to download and run the installer for the
latest .NET runtime. You'll also need to download and install the latest version of
Xcode, which is also available from the App Store app on your Mac.
2. On your Mac, open Terminal and check that you have the latest .NET runtime
installed:
zsh
dotnet --version
zsh
This command will install the latest released version of .NET MAUI, including the
required platform SDKs.
zsh
5. In Terminal, change directory to MyMauiApp, and build and run the app:
zsh
cd MyMauiApp
The dotnet build command will restore the project dependencies, build the app,
and launch it.
If you see a build error and a warning that the Xcode app bundle could not be
found, you may need to run the following command:
zsh
xcode-select --reset
6. In the running app, press the Click me button several times and observe that the
count of the number of button clicks is incremented.
Deploy and debug your .NET MAUI app
on Windows
Article • 11/08/2022 • 2 minutes to read
You can use your local Windows development computer to deploy and debug a .NET
Multi-platform App UI (.NET MAUI) app. This article describes how to configure
Windows to debug a .NET MAUI app.
Configure Windows
You must enable Developer Mode in Windows. Both Windows 10 and Windows 11 are
supported.
Windows 11
Developer Mode is enabled in Settings app, under Privacy & security > For developers.
To enable Developer Mode in Windows 11:
Windows 10
Developer Mode is enabled in Settings app, under Update & Security > For developers.
To enable Developer Mode in Windows 10:
Xamarin projects can run on .NET, starting with .NET 6, after completing an upgrade
process. This series of articles describe the process for migrating your Xamarin projects
to .NET.
) Important
To upgrade your Xamarin native projects to .NET, you'll first have to update the projects
to be SDK-style projects and then update your dependencies to .NET 6+. For more
information, see Upgrade Xamarin.Android, Xamarin.iOS, and Xamarin.Mac apps to .NET.
The .NET Upgrade Assistant is a command-line tool that can help you upgrade
Xamarin.Forms projects to .NET Multi-platform App UI (.NET MAUI). After running the
tool, in most cases the app will require additional effort to complete the migration. For
more information, see Upgrade a Xamarin.Forms app to .NET MAUI with the .NET
Upgrade Assistant.
Alternatively, you can manually upgrade a Xamarin.Forms project to .NET MAUI with a
two-step process:
1. Upgrade your Xamarin native projects, in your Xamarin.Forms solution, to .NET. For
more information, see Upgrade Xamarin.Android, Xamarin.iOS, and Xamarin.Mac
apps to .NET.
2. Upgrade your Xamarin.Forms library project to .NET Multi-platform App UI (.NET
MAUI). For more information, see Manually upgrade a Xamarin.Forms app to .NET
MAUI.
Upgrade Xamarin.Android, Xamarin.iOS,
and Xamarin.Mac projects to .NET
Article • 03/10/2023 • 3 minutes to read
For most apps, you won't need to change namespaces or undertake other rewrites.
To simplify the upgrade process, we recommend creating a new .NET project of the
same type and name as your Xamarin native project, and then copying in your code.
This is the approach outlined below.
XML
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationId>com.companyname.AndroidApp2</ApplicationId>
<ApplicationVersion>1</ApplicationVersion>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
</PropertyGroup>
</Project>
) Important
The target framework moniker (TFM) is what denotes the project as using .NET, in
this case .NET 6. Valid TFMs for equivalent Xamarin native projects are net6.0-
android, net6.0-ios, net6.0-macos, net6.0-tvos, and their .NET 7 equivalents.
Launch the app to confirm that your development environment can build the app.
Merge files
Copy your code and resource files from the folders of your Xamarin native app to
identical folders within your new app. You should overwrite any files of the same name.
If you have other library projects, you should add them to your new solution and add
project references to them from your new .NET project.
You'll also need to copy some project properties from your Xamarin native project to
your new .NET project, for settings like conditional compilation arguments and code
signing. Opening the projects side-by-side in separate Visual Studio instances will
enable you to compare the project properties. Alternatively, you can migrate the
settings by editing the new project file directly. For more information, see
Xamarin.Android project migration and Xamarin Apple project migration.
Update dependencies
Xamarin native NuGet packages are not compatible with .NET 6+ unless they have been
recompiled using .NET TFMs. You can confirm a package is .NET 6+ compatible by
looking at the Frameworks tab on NuGet for the package you're using, and checking
that it lists one of the compatible frameworks shown in the following table:
xamarinwatchos
7 Note
If you can't find a .NET 6+ compatible version of a NuGet package you should:
Recompile the package with .NET TFMs, if you own the code.
Look for a preview release of a .NET 6+ version of the package.
Replace the dependency with a .NET 6+ compatible alternative.
For information about migrating Xamarin.Essentials code in a .NET for Android or .NET
for iOS app, see Migrate Xamarin.Essentials code in .NET for Android and .NET for iOS
apps.
Delete all bin and obj folders from all projects before opening and building
projects in Visual Studio, particularly when changing .NET versions.
Delete the Resource.designer.cs generated file from the Android project.
Xamarin.Android project migration
Article • 02/15/2023 • 5 minutes to read
A .NET 7 project for a .NET for Android app is similar to the following example:
XML
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-android</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
</Project>
For a library project, omit the $(OutputType) property completely or specify Library as
the property value.
XML
<PropertyGroup>
<AndroidSupportedAbis>armeabi-v7a;arm64-
v8a;x86;x86_64</AndroidSupportedAbis>
</PropertyGroup>
XML
<PropertyGroup>
<RuntimeIdentifiers>android-arm;android-arm64;android-x86;android-
x64</RuntimeIdentifiers>
</PropertyGroup>
For more information about runtime identifiers, see .NET RID Catalog.
The following table shows other MSBuild properties that have changed in .NET for
Android:
Property Comments
7 Note
Runtime behavior
There are behavioral changes to the String.IndexOf() method in .NET 5+ on different
platforms. For more information, see .NET globalization and ICU.
Linker
.NET 5+ has new settings for the linker:
<PublishTrimmed>true</PublishTrimmed>
In .NET for Android projects by default, Debug builds will not use the linker and Release
builds will set PublishTrimmed=true and TrimMode=link . TrimMode=copyused is the default
for the .NET SDK but isn't appropriate for mobile apps. However, you can still opt into
TrimMode=copyused if required.
If the legacy AndroidLinkMode setting is used, both SdkOnly and Full will default to
equivalent linker settings:
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
With AndroidLinkMode=SdkOnly only BCL and SDK assemblies marked with %(Trimmable)
will be linked at the member level. AndroidLinkMode=Full will set %(TrimMode)=link on
all .NET assemblies.
Tip
You should migrate to the new linker settings, because the AndroidLinkMode setting
will eventually be deprecated.
Ahead-of-Time compilation
$(RunAOTCompilation) is the new MSBuild property for enabling Ahead-of-Time (AoT)
compilation. This is the same property used for Blazor WASM. The $(AotAssemblies)
property also enables AOT, in order to help with migration from Xamarin.Android
projects to .NET for Android projects.
Tip
XML
<RunAOTCompilation>true</RunAOTCompilation>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
XML
<RunAOTCompilation>false</RunAOTCompilation>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
</PropertyGroup>
Supported encodings
If your Xamarin.Android app uses certain international codesets, they have to be
specified explicitly in your project file using the Mandroidl18n MSBuild property, so that
the linker can include supporting resources. For more information about this build
property, see MAndroidl18n.
However, the Mandroidl18n MSBuild property isn't supported in .NET for Android apps.
Instead, support is provided by the System.TextEncoding.CodePages NuGet package.
For more information, see CodePagesEncodingProvider.
.NET CLI
.NET for Android supports using .NET command-line interface (.NET CLI) to create, build,
publish, and run Android apps.
dotnet new
dotnet new can be used to create new .NET for Android projects and items using project
templates and item templates that are named following the patterns and naming of
existing .NET templates:
The following examples show using dotnet new to create different types of .NET for
Android projects:
.NET CLI
Once .NET for Android projects have been created, item templates can be used to add
items to the projects:
.NET CLI
SDK so that they run during the build. Therefore, .NET for Android does the following
during a build:
Run aapt to generate Resource.designer.cs and potentially emit build errors for
issues in @(AndroidResource) files.
Compile C# code.
Run the ILLink MSBuild target for linking.
Generate java stubs, and AndroidManifest.xml .
Compile java code via javac .
Convert java code to .dex via d8/r8.
Create an .apk or .aab and sign it.
dotnet publish is reserved for publishing an app for Google Play and other distribution
mechanisms such as ad-hoc. It also signs the .apk or .aab with different keys.
7 Note
Behavior inside IDEs will differ. The Build target will not produce an .apk file if
$(BuildingInsideVisualStudio) is true . IDEs will call the Install target for
deployment, which will produce the .apk file. This behavior matches
Xamarin.Android.
dotnet run
dotnet run can be used to launch apps on a device or emulator via the --project
argument:
.NET CLI
.NET CLI
See also
Binding projects
Xamarin Apple project migration
Article • 02/15/2023 • 2 minutes to read
A .NET 7 project for a .NET for iOS app is similar to the following example:
XML
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-ios</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
</PropertyGroup>
</Project>
For a library project, omit the $(OutputType) property completely or specify Library as
the property value.
MtouchEnableSGenConc Rename to
EnableSGenConc .
EnableSGenConc Copy
Convert to RuntimeIdentifier
The following table shows how to convert the MtouchArch and XamMacArch properties to
the RuntimeIdentifier property when migrating a Xamarin.iOS project to .NET for iOS:
ARMv7 ios-arm
ARMv7s ios-arm
ARMv7+ARMv7s ios-arm
ARM64 ios-arm64
ARMv7+ARM64 ios-arm,ios-arm64
ARMv7+ARMv7s+ARM64 ios-arm,ios-arm64
x86_64 iossimulator-x64
i386 iossimulator-x86
x86_64+i386 iossimulator-x86,iossimulator-x64
The following table shows how to convert the MtouchArch and XamMacArch properties to
the RuntimeIdentifier property when migrating a Xamarin.Mac project to .NET for
macOS+:
Property RuntimeIdentifier
x86_64 osx-x64
The following table shows how to convert the MtouchArch and XamMacArch properties to
the RuntimeIdentifier property when migrating a Xamarin.tvOS project to .NET for
tvOS:
Property RuntimeIdentifier
ARM64 tvos-arm64
x86_64 tvossimulator-x64
For more information about the RuntimeIdentifier property, see RuntimeIdentifier. For
more information about runtime identifiers, see .NET RID Catalog.
Convert to UseNativeHttpHandler
The following table shows how to convert the HttpClientHandler and
MtouchHttpClientHandler properties to the UseNativeHttpHandler property when
migrating a Xamarin Apple project to .NET 6+:
Value UseNativeHttpHandler
HttpClientHandler false
Changes to Info.plist
Some values have moved from Info.plist to the project file.
See also
Project file properties
Migrate Xamarin.Essentials code in .NET
for Android and .NET for iOS apps
Article • 03/10/2023 • 4 minutes to read
Xamarin.Essentials is a fundamental library for nearly every Xamarin app, and its
functionality is now part of .NET Multi-platform App UI (.NET MAUI).
The process to use .NET MAUIs native device functionality, that was formerly known as
Xamarin.Essentials, in a .NET for Android or .NET for iOS app, is:
1. Remove the Xamarin.Essentials NuGet package from your .NET for Android or .NET
for iOS app.
2. Set the $(UseMauiEssentials) build property to true in your project file. For more
information, see Modify your project file.
3. Initialize the "essentials" functionality by calling the Platform.Init method. For
more information, see Initialize the platform.
4. Perform additional setup, if required. For more information, see Perform additional
setup.
5. Add using directives for the required functionality. For more information, see Add
using directives.
Android
XML
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-android</TargetFramework>
...
<UseMauiEssentials>true</UseMauiEssentials>
</PropertyGroup>
</Project>
In any Activity that's launched you must call the Platform.Init method, which is in
the Microsoft.Maui.ApplicationModel namespace, from the OnCreate method:
C#
using Android.Content.PM;
using Android.Runtime;
using Microsoft.Maui.ApplicationModel;
namespace MyAndroidApp;
base.OnCreate(savedInstanceState);
Platform.Init(this, savedInstanceState);
// ...
Android
Member Purpose
AppContext A property that gets the Context object that represents the
current app context.
C#
If there's a situation where the Activity is needed, but the app hasn't fully started,
call the WaitForActivityAsync method:
C#
C#
Platform.OnRequestPermissionsResult(requestCode, permissions,
grantResults);
base.OnRequestPermissionsResult(requestCode, permissions,
grantResults);
In addition to getting the current Activity, you can also register for lifecycle events:
C#
protected override void OnCreate(Bundle bundle)
base.OnCreate(bundle);
Platform.Init(this, bundle);
Platform.ActivityStateChanged += Platform_ActivityStateChanged;
base.OnDestroy();
Platform.ActivityStateChanged -= Platform_ActivityStateChanged;
Created
Resumed
Paused
Destroyed
SaveInstanceState
Started
Stopped
Namespace Purpose
For more information about the functionality in each namespace, see Platform
integration.
Upgrade a Xamarin.Forms app to .NET
MAUI with the .NET Upgrade Assistant
Article • 04/12/2023
The .NET Upgrade Assistant is a command-line tool that will help you upgrade
Xamarin.Forms projects to .NET Multi-platform App UI (.NET MAUI) by converting the
solution's project file and by performing common code updates. Specifically, the tool
will:
After running the tool, additional effort will be required to complete the migration.
7 Note
The .NET Upgrade Assistant for .NET MAUI doesn't support upgrading UWP
projects, iOS extension projects, or binding projects.
For more information about .NET Upgrade Assistant, including the other app types it
can convert, see Overview of the .NET Upgrade Assistant.
Get started
.NET Upgrade Assistant is currently only available for Windows, and only works with
Xamarin.Forms projects. To use it, your Xamarin.Forms project must use Xamarin.Forms
4.8 or higher. However, for best success we recommend that your Xamarin.Forms
project uses Xamarin.Forms 5.0, and .NET Standard 2.0 or higher.
) Important
The .NET Upgrade Assistant for .NET MAUI is still under development. Please file
feedback so we can continue to improve this tool.
.NET Upgrade Assistant will make a backup of your solution, but we recommend using
source control. When using source control you may add the --skip-backup parameter
to bypass the backup and speed up the upgrade process.
Installation
Install the .NET Upgrade Assistant globally with the following command:
.NET CLI
Similarly, because the .NET Upgrade Assistant is installed as a .NET tool, it can be easily
updated by running:
.NET CLI
) Important
Installing this tool may fail if you've configued additional NuGet feed source. Use
the --ignore-failed-sources parameter to treat those failures as warnings instead
of errors:
.NET CLI
Run upgrade-assistant
Open a terminal and navigate to the folder where the target project or solution is
located. Run the upgrade-assistant upgrade command, passing in the name of the
project or solution you're upgrading:
.NET CLI
This command runs the tool in non-interactive mode. It will update all eligible projects
in the solution and dependent projects.
Next steps
Manual migration
Manually upgrade a Xamarin.Forms app to
.NET MAUI
Article • 04/03/2023
Upgrading a Xamarin.Forms app to a .NET Multi-platform App UI (.NET MAUI) app follows the same
steps as a Xamarin.Android and Xamarin.iOS project, with additional steps to take advantage of
changes in .NET MAUI.
This article describes how to manually migrate a Xamarin.Forms library project to a .NET MAUI library
project. Before you do this, you must update your Xamarin.Forms platform projects to be SDK-style
projects. SDK-style projects are the same project format used by all .NET workloads, and compared to
many Xamarin projects are much less verbose. For information about updating your app projects, see
Upgrade Xamarin.Android, Xamarin.iOS, and Xamarin.Mac apps to .NET, Xamarin.Android project
migration and Xamarin Apple project migration.
To migrate a Xamarin.Forms library project to a .NET MAUI library project, you must:
To simplify the upgrade process, we recommend creating a new .NET MAUI library project of the same
name as your Xamarin.Forms library project, and then copying in your code. This is the approach
outlined below.
XML
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net7.0-android;net7.0-ios;net7.0-
maccatalyst</TargetFrameworks>
<TargetFrameworks
Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-
windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by
following this: https://github.com/Samsung/Tizen.NET -->
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<SupportedOSPlatformVersion
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) ==
'ios'">11.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) ==
'maccatalyst'">13.1</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) ==
'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) ==
'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) ==
'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) ==
'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>
</Project>
In your platform projects, add a reference to this new library project. Then copy your Xamarin.Forms
library files into the .NET MAUI library project.
Namespace changes
Namespaces have changed in the move from Xamarin.Forms to .NET MAUI, and Xamarin.Essentials
features are now part of .NET MAUI. To make namespace updates, perform a find and replace for the
following namespaces:
Xamarin.Forms.DualScreen Microsoft.Maui.Controls.Foldable
Xamarin.Forms.PlatformConfiguration Microsoft.Maui.Controls.PlatformConfiguration
Xamarin.Forms.PlatformConfiguration.AndroidSpeci Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpe
fic cific
Xamarin.Forms.PlatformConfiguration.AndroidSpeci Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpe
fic.AppCompat cific.AppCompat
Xamarin.Forms.PlatformConfiguration.TizenSpecific Microsoft.Maui.Controls.PlatformConfiguration.TizenSpecifi
c
Xamarin.Forms.PlatformConfiguration.WindowsSpec Microsoft.Maui.Controls.PlatformConfiguration.WindowsSp
ific ecific
Xamarin.Forms.PlatformConfiguration.iOSSpecific Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific
Xamarin.Forms.Shapes Microsoft.Maui.Controls.Shapes
Xamarin.Forms namespace .NET MAUI namespace(s)
Xamarin.Forms.StyleSheets Microsoft.Maui.Controls.StyleSheets
Xamarin.Forms.Xaml Microsoft.Maui.Controls.Xaml
.NET MAUI projects make use of implicit global using directives. This enables you to remove using
directives for the Xamarin.Essentials namespace, without having to replace them with the equivalent
.NET MAUI namespaces.
API changes
Some APIs have changed in the move from Xamarin.Forms to .NET MAUI. This is multiple reasons
including removing duplicate functionality caused by Xamarin.Essentials becoming part of .NET MAUI,
and ensuring that APIs follow .NET naming guidelines. The following sections discuss these changes.
Color changes
In Xamarin.Forms, the Xamarin.Forms.Color struct lets you construct Color objects using double values,
and provides named colors, such as Xamarin.Forms.Color.AliceBlue. In .NET MAUI, this functionality has
been separated into the Microsoft.Maui.Graphics.Color class, and the Microsoft.Maui.Graphics.Colors
class.
The following table shows the API changes between the Xamarin.Forms.Color struct and the
Microsoft.Maui.Graphics.Color class:
Xamarin.Forms.Color.R Microsoft.Maui.Graphics.Color.Red
Xamarin.Forms.Color.G Microsoft.Maui.Graphics.Color.Green
Xamarin.Forms.Color.B Microsoft.Maui.Graphics.Color.Blue
Xamarin.Forms.Color.A Microsoft.Maui.Graphics.Color.Alpha
In addition, all of the numeric values in a Microsoft.Maui.Graphics.Color are float , rather than double
as used in Xamarin.Forms.Color.
7 Note
Layout changes
The following table lists the layout APIs that have been removed in the move from Xamarin.Forms to
.NET MAUI:
Xamarin.Forms.Abs The Add overload that accepts 3 arguments isn't present in .NET MAU
oluteLayout.IAbsol I.
uteList<T>.Add
Xamarin.Forms.Gri The Add overload that accepts 5 arguments isn't present in .NET MAU
d.IGridList<T>.Add I.
C#
In .NET MAUI, the Children collection is for internal use by .NET MAUI and shouldn't be manipulated
directly. Therefore, in code children should be added directly to the layout:
C#
) Important
Any Add layout extension methods, such as GridExtensions.Add, are invoked on the layout rather
than the layouts Children collection.
You may notice when running your upgraded .NET MAUI app that layout behavior is different. For
more information, see Layout behavior changes from Xamarin.Forms.
The process for creating a custom layout in .NET MAUI involves creating an ILayoutManager
implementation, and overriding the Measure and ArrangeChildren methods:
The Measure override should call Measure on each IView in the layout, and should return the
total size of the layout given the constraints.
The ArrangeChildren override should determine where each IView should be placed within the
given bounds, and should call Arrange on each IView with its appropriate bounds. The return
value should be the actual size of the layout.
Device changes
Xamarin.Forms has a Xamarin.Forms.Device class that helps you to interact with the device and
platform the app is running on. The equivalent class in .NET MAUI, Microsoft.Maui.Controls.Device, is
deprecated and its functionality is replaced by multiple types.
The following table shows the .NET MAUI replacements for the functionality in the
Xamarin.Forms.Device class:
Xamarin.Forms.Device.An Microsoft.Maui.Devices.DevicePlatform.Androi
droid d
Xamarin.Forms.Device.iOS Microsoft.Maui.Devices.DevicePlatform.iOS
Xamarin.Forms.Device.Tiz Microsoft.Maui.Devices.DevicePlatform.Tizen
en
Xamarin.Forms.Device.U Microsoft.Maui.Devices.DevicePlatform.WinUI
WP
Xamarin.Forms.Device.Flo Microsoft.Maui.ApplicationModel.AppInfo.Req
wDirection uestedLayoutDirection
Xamarin.Forms.Device.Idi Microsoft.Maui.Devices.DeviceInfo.Idiom
om
Xamarin.Forms.Device.IsIn Microsoft.Maui.Dispatching.Dispatcher.IsDispat
vokeRequired chRequired
Xamarin.Forms.Device.OS Microsoft.Maui.Devices.DeviceInfo.Platform
Xamarin.Forms.Device.Ru Microsoft.Maui.Devices.DeviceInfo.Platform
ntimePlatform
Xamarin.Forms.Device.Be Microsoft.Maui.ApplicationModel.MainThread.
ginInvokeOnMainThread BeginInvokeOnMainThread
Xamarin.Forms.Device.Get Microsoft.Maui.ApplicationModel.MainThread.
MainThreadSynchronizati GetMainThreadSynchronizationContextAsync
onContextAsync
Xamarin.Forms.Device.Inv Microsoft.Maui.Controls.VisualElement.Invalida
alidate teMeasure
Xamarin.Forms API .NET MAUI API Comments
Xamarin.Forms.Device.Inv Microsoft.Maui.ApplicationModel.MainThread.I
okeOnMainThreadAsync nvokeOnMainThreadAsync
Xamarin.Forms.Device.On Microsoft.Maui.Devices.DeviceInfo.Platform
Platform
Xamarin.Forms.Device.Op Microsoft.Maui.ApplicationModel.Launcher.Op
enUri enAsync
Xamarin.Forms.Device.Set Microsoft.Maui.Controls.Window.FlowDirection
FlowDirection
Xamarin.Forms.Device.Sta Microsoft.Maui.Dispatching.DispatcherExtensio
rtTimer ns.StartTimer or Microsoft.Maui.Dispatching.Di
spatcher.DispatchDelayed
Map changes
In Xamarin.Forms, the Map control and associated types are in the Xamarin.Forms.Maps namespace. In
.NET MAUI, this functionality has moved to the Microsoft.Maui.Controls.Maps and
Microsoft.Maui.Maps namespaces. Some properties have been renamed and some types have been
replaced with equivalent types from Xamarin.Essentials.
The following table shows the .NET MAUI replacements for the functionality in the
Xamarin.Forms.Maps namespace:
Xamarin.Forms.Maps.Map. Microsoft.Maui.Controls.M
HasScrollEnabled aps.Map.IsScrollEnabled
Xamarin.Forms.Maps.Map. Microsoft.Maui.Controls.M
HasZoomEnabled aps.Map.IsZoomEnabled
Xamarin.Forms.Maps.Map.T Microsoft.Maui.Controls.M
rafficEnabled aps.Map.IsTrafficEnabled
Xamarin.Forms.Maps.Pin.Id Microsoft.Maui.Controls.M
aps.Pin.MarkerId
Xamarin.Forms.Maps.Pin.Po Microsoft.Maui.Controls.M
sition aps.Pin.Location
Xamarin.Forms.Maps.MapC Microsoft.Maui.Controls.M
lickedEventArgs.Position aps.MapClickedEventArgs.
Location
Xamarin.Forms API .NET MAUI API Comment
In XAML, an xmlns namespace definition should be added for the Map control. While this isn't
required, it prevents a collision between the Polygon and Polyline types, which exist in both the
Microsoft.Maui.Controls.Maps and Microsoft.Maui.Controls.Shapes namespaces. For more information,
see Display a map.
Other changes
A small number of other APIs have been consolidated in the move from Xamarin.Forms to .NET MAUI.
The following table shows these changes:
Xamarin.Forms.A Microsoft.Maui.Stora
pplication.Proper ge.Preferences
ties
Xamarin.Forms.B Microsoft.Maui.Cont
utton.Image rols.Button.ImageSo
urce
Xamarin.Forms.Fr Microsoft.Maui.Cont
ame.OutlineColor rols.Frame.BorderCol
or
Xamarin.Forms.O Microsoft.Maui.Appli
SAppTheme cationModel.AppThe
me
Xamarin.Forms.S Microsoft.Maui.Cont
pan.ForegroundC rols.Span.TextColor
olor
C#
using System;
using Android.App;
using Android.Runtime;
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
namespace YOUR_NAMESPACE_HERE.Droid
[Application]
using System;
using Microsoft.Maui;
using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.OS;
namespace YOUR_NAMESPACE_HERE.Droid
base.OnCreate(savedInstanceState);
Then, update your manifest file to specify that the minSdKVersion is 21, which is the minimum Android
SDK version required by .NET MAUI. This can be achieved by modifying the <uses-sdk /> node, which
is a child of the <manifest> node:
XML
C#
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Maui;
using Foundation;
using UIKit;
namespace YOUR_NAMESPACE_HERE.iOS
[Register("AppDelegate")]
Then, update Info.plist so that MinimumOSVersion is 11.0, which is the minimum iOS version required by
.NET MAUI.
Therefore, add a new class named MauiProgram that contains the following code:
C#
namespace YOUR_NAMESPACE_HERE;
builder
.UseMauiApp<App>();
return builder.Build();
AssemblyInfo changes
Properties that are typically set in an AssemblyInfo.cs file are now available in your SDK-style project.
We recommend migrating them from AssemblyInfo.cs to your project file in every project, and
removing the AssemblyInfo.cs file.
Optionally, you can keep the AssemblyInfo.cs file and set the GenerateAssemblyInfo property in your
project file to false :
XML
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
net6.0-maccatalyst, net7.0-maccatalyst
7 Note
.NET Standard libraries that have no dependencies on the incompatible frameworks listed below
are still compatible with .NET 6+.
If a package on NuGet indicates compatibility with any of the net6 or newer frameworks above,
regardless of also including incompatible frameworks, then the package is compatible. Compatible
NuGet packages can be added to your .NET MAUI library project using the NuGet package manager in
Visual Studio.
If you can't find a .NET 6+ compatible version of a NuGet package you should:
Recompile the package with .NET TFMs, if you own the code.
Look for a preview release of a .NET 6+ version of the package.
Replace the dependency with a .NET 6+ compatible alternative.
Tip
Delete all bin and obj folders from all projects before opening and building projects in Visual
Studio, particularly when changing .NET versions.
Delete the Resource.designer.cs generated file from the Android project.
The following table provides guidance for overcoming common build or runtime issues:
Issue Tip
Xamarin.* Update the namespace to its .NET MAUI equivalent. For more information, see Namespace
namespace changes.
doesn't exist.
API doesn't Update the API usage to its .NET MAUI equivalent. For more information, see API changes.
exist.
Issue Tip
App won't Ensure that the required platform project is set to deploy in Visual Studio's Configuration
deploy. Manager.
App won't Update each platform project's entry point class, and the app entry point. For more information,
launch. see Boostrap your migrated app.
CollectionView Check the container layout and the measured size of the CollectionView. By default the control
doesn't scroll. will take up as much space as the container allows. A Grid will constrain children at its own size.
However a StackLayout will enable children to take up space beyond its bounds.
BoxView not The default size of a BoxView in Xamarin.Forms is 40x40. The default size of a BoxView in .NET
appearing. MAUI is 0x0. Set WidthRequest and HeightRequest to 40.
Layout is Add default values to your project based on the .NET MAUI style resource. For more information,
missing see Default value changes from Xamarin.Forms.
padding,
margin, or
spacing.
Custom layout Custom layout code needs updating to work in .NET MAUI. For more information, see Custom
doesn't work. layout changes.
Custom Renderer code needs updating to work in .NET MAUI. For more information, see Use custom
renderer renderers in .NET MAUI.
doesn't work.
Effect doesn't Effect code needs updating to work in .NET MAUI. For more information, see Use effects in .NET
work. MAUI.
See also
Porting from .NET Framework to .NET
.NET Upgrade Assistant
Layout behavior changes from
Xamarin.Forms
Article • 02/17/2023 • 5 minutes to read
You may notice when running your upgraded .NET Multi-platform App UI (.NET MAUI)
app that layout behavior is different. Some of this is the result of changes to layout
spacing values. For more information, see Default value changes from Xamarin.Forms.
StackLayout Children can fill Children are stacked If you need child views to
space in the and will go beyond fill space, change to a Grid.
stacking direction. available space.
.NET MAUI controls generally honour explicit size requests. If you ask a control to be
200 device-independent units wide, then .NET MAUI will make that control 200 units
wide, even if the control's container is only 100 units wide.
To preserve the Xamarin.Forms default values in projects that don't set explicit values,
add implicit styles to your project. For more information about implicit styles, see
Implicit styles.
7 Note
The .NET MAUI project template includes resource dictionaries that provide default
styles for most controls. It's recommended you take a similar approach in your
apps, by modifying or inheriting from these resource dictionaries .
The following table lists the layout property values that have changed between
Xamarin.Forms and .NET MAUI:
Grid.ColumnSpacing 6 0
Grid.RowSpacing 6 0
StackLayout.Spacing 6 0
Grid
The biggest change in Grid behavior between Xamarin.Forms and .NET MAUI is that
grids don't automatically add missing rows and columns for you. For example, in
Xamarin.Forms you could add controls to a Grid without specifying their row behavior:
XML
<Grid>
<Label Text="Hello"/>
</Grid>
In Xamarin.Forms, despite not declaring that the Grid contains two rows, a second row
would be automatically added for you. .NET MAUI doesn't do this. Instead, you have to
explicitly specify how many rows are in the Grid with the RowDefinitions property.
) Important
By default, .NET MAUI creates a Grid with one column and one row. Therefore, it's
not necessary to set the ColumnDefinitions and RowDefinitions properties if this is
your intention.
StackLayout
There are several differences between the stack layouts in .NET MAUI (StackLayout,
VerticalStackLayout, and HorizontalStackLayout) and the StackLayout in Xamarin.Forms.
The main difference is that .NET MAUI stack layouts are very simple. They stack their
child views in a single direction until all of them have been stacked. They will keep going
until the last child has been stacked, even if that takes them beyond the available space
in the stacking direction. Therefore, .NET MAUI stack layouts arrange controls in a
particular direction. They do not subdivide a space. This is completely different to the
Xamarin.Forms StackLayout, which changes its layout behavior based on circumstances
and the presence of any *AndExpand layout options, such as FillAndExpand or
CenterAndExpand . The Xamarin.Forms StackLayout sometimes subdivides the space,
expanding to or stopping at the edge of its container. In other cases, it expands beyond
its container.
the .NET MAUI StackLayout does honor the *AndExpand layout options, although they've
been marked as obsolete. To avoid warnings about using obsolete members, you should
convert your layouts that use *AndExpand layout options to the appropriate layout type.
The can be achieved as follows:
1. If your layout is anything other than a StackLayout, remove all uses of AndExpand .
Just as in Xamarin.Forms, in .NET MAUI the AndExpand layout options have no
effect on any layout other than StackLayout.
2. Remove any AndExpand properties which are orthogonal to the stacking direction.
For example, if you have a StackLayout with an Orientation of Vertical , and it has
a child with a HorizontalAligment="CenterAndExpand" - that layout options has no
effect and can be removed.
XAML
<StackLayout>
</StackLayout>
XAML
</Grid>
When performing this conversion, anything that was marked AndExpand in the
StackLayout should go in its own row or column with a size of * in the Grid.
RelativeLayout
Use of RelativeLayout is not recommended in .NET MAUI. Instead, use a Grid wherever
possible.
XML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:compat="clr-
namespace:Microsoft.Maui.Controls.Compatibility;assembly=Microsoft.Maui.Cont
rols"
x:Class="MyMauiApp.MyPage"
Title="MyPage">
<compat:RelativeLayout>
</compat:RelativeLayout>
</ContentPage>
ScrollView
While ScrollView often isn't considered to be a layout, it can be thought of as a layout as
it's used to scroll its child content. In Xamarin.Forms, ScrollView doesn't behave
consistently when stacking. It has some arbitrary limits on minimum size that depend
partially on its content, and it will sometimes compress to enable other items to fit on
the page inside a StackLayout in ways that are inconsistent and sometimes surprising.
In .NET MAUI, the ScrollView expands to whatever size it wants to be unless otherwise
constrained. This means that inside of a VerticalStackLayout, which can expand infinitely,
a ScrollView will expand to its full content height and doesn't scroll. This behavior can
be confusing if you're a Xamarin.Forms user.
) Important
A StackLayout continues in its stacking direction until it runs out of content. It does
not subdivide its container along that axis. If you want to limit your content to a
constrained space in a direction, you should use another layout such as a Grid.
Use custom renderers in .NET MAUI
Article • 02/15/2023 • 2 minutes to read
While there are many benefits to using .NET Multi-platform App UI (.NET MAUI)
handlers to customize and create controls, it's still possible to use Xamarin.Forms
custom renderers in .NET MAUI apps. For more information about custom renderers, see
Xamarin.Forms custom renderers.
The process for migrating a Xamarin.Forms custom renderer to .NET MAUI is to:
1. Add the custom renderer code into the appropriate location in your .NET MAUI
project(s). For more information, see Add the code.
2. Modify the using directives and remove ExportRenderer attributes. For more
information, see Modify using directives and other code.
3. Register the renderers. For more information, see Register renderers.
4. Consume the renderers. For more information, see Consume the custom renderers.
You should also remove any ExportRenderer attributes as they won't be needed in .NET
MAUI. For example, the following should be removed:
C#
[assembly: ExportRenderer(typeof(PressableView),
typeof(PressableViewRenderer))]
Register renderers
In your .NET MAUI app project, open MauiProgram.cs and add a using statement for the
Microsoft.Maui.Controls.Compatibility.Hosting namespace. Then, call
UseMauiCompatibility on the MauiAppBuilder object in the CreateMauiApp method, and
configure each renderer using conditional compilation per platform:
C#
using Microsoft.Maui.Controls.Compatibility.Hosting;
builder
.UseMauiApp<App>()
.UseMauiCompatibility()
.ConfigureFonts(fonts =>
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureMauiHandlers((handlers) =>
#if ANDROID
handlers.AddHandler(typeof(PressableView),
typeof(XamarinCustomRenderer.Droid.Renderers.PressableViewRenderer));
#elif IOS
handlers.AddHandler(typeof(PressableView),
typeof(XamarinCustomRenderer.iOS.Renderers.PressableViewRenderer));
#endif
});
return builder.Build();
XML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:XamarinCustomRenderer.Controls"
x:Class="MauiCustomRenderer.MainPage">
<Grid BackgroundColor="#f1f1f1">
<controls:PressableView Pressed="Handle_Pressed"
Released="Handle_Released"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid BackgroundColor="#202020"
HorizontalOptions="Center"
VerticalOptions="Center">
FontSize="16"
TextColor="White"
Margin="24,20"
HorizontalTextAlignment="Center" />
</Grid>
</controls:PressableView>
</Grid>
</ContentPage>
The process for migrating a Xamarin.Forms custom control that's backed by custom
renderers on each platform to a .NET MAUI custom control that's backed by a handler
on each platform is as follows:
1. Create a class for the cross-platform control, which provides the control's public
API. For more information, see Create the cross-platform control.
2. Create a partial handler class. For more information, see Create the handler.
3. In the handler class, create a PropertyMapper dictionary, which defines the Actions
to take when cross-platform property changes occur. For more information, see
Create the property mapper.
4. Create partial handler classes for each platform that create the native views that
implement the cross-platform control. For more information, see Create the
platform controls.
5. Register the handler using the ConfigureMauiHandlers and AddHandler methods in
your app's MauiProgram class. For more information, see Register the handler.
Then, the cross-platform control can be consumed. For more information, see Consume
the cross-platform control.
C#
namespace MyMauiControl.Controls
BindableProperty.Create(nameof(Text), typeof(string),
typeof(CustomEntry), null);
BindableProperty.Create(nameof(TextColor), typeof(Color),
typeof(CustomEntry), null);
The control should provide a public API that will be accessed by its handler, and control
consumers. Cross-platform controls should derive from View, which represents a visual
element that's used to place layouts and views on the screen.
C#
#elif ANDROID
#elif WINDOWS
#endif
using MyMauiControl.Controls;
using Microsoft.Maui.Handlers;
namespace MyMauiControl.Handlers
The handler class is a partial class whose implementation will be completed on each
platform with an additional partial class.
The conditional using statements define the PlatformView type on each platform. The
final conditional using statement defines PlatformView to be equal to System.Object .
This is necessary so that the PlatformView type can be used within the handler for usage
across all platforms. The alternative would be to have to define the PlatformView
property once per platform, using conditional compilation.
PropertyMapper is defined in .NET MAUI's generic ViewHandler class, and requires two
generic arguments to be supplied:
The class for the cross-platform control, which derives from View.
The class for the handler.
The following code example shows the CustomEntryHandler class extended with the
PropertyMapper definition:
C#
[nameof(CustomEntry.Text)] = MapText,
[nameof(CustomEntry.TextColor)] = MapTextColor
};
The PropertyMapper is a Dictionary whose key is a string and whose value is a generic
Action . The string represents the cross-platform control's property name, and the
Action represents a static method that requires the handler and cross-platform
control as arguments. For example, the signature of the MapText method is public
static void MapText(CustomEntryHandler handler, CustomEntry view) .
Each platform handler must provide implementations of the Actions, which manipulate
the native view APIs. This ensures that when a property is set on a cross-platform
control, the underlying native view will be updated as required. The advantage of this
approach is that it allows for easy cross-platform control customization, because the
property mapper can be modified by cross-platform control consumers without
subclassing. For more information, see Customize controls with handlers.
Create the platform controls
After creating the mappers for your handler, you must provide handler implementations
on all platforms. This can be accomplished by adding partial class handler
implementations in the child folders of the Platforms folder. Alternatively you could
configure your project to support filename-based multi-targeting, or folder-based
multi-targeting, or both.
XML
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-android')) !=
true">
<None Include="**\**\*.Android.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\**\*.MaciOS.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\*.Windows.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
Each platform handler class should be a partial class and derive from the generic
ViewHandler class, which requires two type arguments:
The class for the cross-platform control, which derives from View.
The type of the native view that implements the cross-platform control on the
platform. This should be identical to the type of the PlatformView property in the
handler.
) Important
The ViewHandler class provides VirtualView and PlatformView properties. The
VirtualView property is used to access the cross-platform control from its handler.
The PlatformView property, is used to access the native view on each platform that
implements the cross-platform control.
Each of the platform handler implementations should override the following methods:
CreatePlatformView , which should create and return the native view that
unsubscribing from events and disposing objects. This method is intentionally not
invoked by .NET MAUI. Instead, you must invoke it yourself from a suitable location
in your app's lifecycle. For more information, see Native view cleanup.
7 Note
Each platform handler should also implement the Actions that are defined in the
mapper dictionaries. In addition, each platform handler should also provide code, as
required, to implement the functionality of the cross-platform control on the platform.
Alternatively, for more complex controls this can be provided by an additional type.
C#
#nullable enable
using AndroidX.AppCompat.Widget;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using MyMauiControl.Controls;
namespace MyMauiControl.Handlers
base.ConnectHandler(platformView);
platformView.Dispose();
base.DisconnectHandler(platformView);
handler.PlatformView.Text = view.Text;
handler.PlatformView?.SetSelection(handler.PlatformView?.Text?.Length ?? 0);
handler.PlatformView?.SetTextColor(view.TextColor.ToPlatform());
CustomEntryHandler derives from the ViewHandler class, with the generic CustomEntry
argument specifying the cross-platform control type, and the AppCompatEditText
argument specifying the type of native control.
The handler also implements the Actions defined in the property mapper dictionary.
Each Action is executed in response to a property changing on the cross-platform
control, and is a static method that requires handler and cross-platform control
instances as arguments. In each case, the Action calls methods defined on the native
control.
Register the handler
A custom control and its handler must be registered with an app, before it can be
consumed. This should occur in the CreateMauiApp method in the MauiProgram class in
your app project, which is the cross-platform entry point for the app:
C#
using Microsoft.Extensions.Logging;
using MyMauiControl.Controls;
using MyMauiControl.Handlers;
namespace MyMauiControl;
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureMauiHandlers(handlers =>
handlers.AddHandler(typeof(CustomEntry),
typeof(CustomEntryHandler));
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
The handler is registered with the ConfigureMauiHandlers and AddHandler method. The
first argument to the AddHandler method is the cross-platform control type, with the
second argument being its handler type.
7 Note
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:MyMauiControl.Controls"
x:Class="MyMauiControl.MainPage">
<Grid>
TextColor="Blue" />
</Grid>
</ContentPage>
An event handler for the page's Unloaded event can be registered in XAML:
XAML
<ContentPage ...
xmlns:controls="clr-namespace:MyMauiControl.Controls"
Unloaded="ContentPage_Unloaded">
<Grid>
<controls:CustomEntry x:Name="customEntry"
... />
</Grid>
</ContentPage>
The event handler for the Unloaded event can then invoke the DisconnectHandler
method on its Handler instance:
C#
customEntry.Handler?.DisconnectHandler();
See also
Create a custom control using handler
Use effects in .NET MAUI
Article • 02/15/2023 • 2 minutes to read
While there are many benefits to using .NET Multi-platform App UI (.NET MAUI)
handlers to customize controls, it's still possible to use Xamarin.Forms effects in .NET
MAUI apps. For more information about effects, see Xamarin.Forms effects.
1. Remove the effect attributes from your effect classes. For more information, see
Remove effect attributes.
2. Remove the effect using directives. For more information, see Remove using
directives.
3. Add the effect code into the appropriate location in your .NET MAUI app project.
For more information, see Add the effect code.
4. Register the effect. For more information, see Register the effect.
5. Consume your .NET MAUI effect. For more information, see Consume the effect.
The following code example shows a FocusRoutingEffect class and its platform
implementations combined into a single file:
C#
using Microsoft.Maui.Controls.Platform;
namespace MyMauiApp.Effects;
#if ANDROID
#elif IOS
#elif WINDOWS
#endif
C#
builder
.UseMauiApp<App>()
.ConfigureEffects(effects =>
effects.Add<FocusRoutingEffect, FocusPlatformEffect>();
});
return builder.Build();
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyMauiApp.Effects"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<Entry.Effects>
<local:FocusRoutingEffect />
</Entry.Effects>
</Entry>
</VerticalStackLayout>
</ContentPage>
XAML
Article • 06/24/2022 • 2 minutes to read
XAML allows developers to define user interfaces in .NET Multi-platform App UI (.NET
MAUI) apps using markup rather than code. XAML is not required in a .NET MAUI app,
but it is the recommended approach to developing your UI because it's often more
succinct, more visually coherent, and has tooling support. XAML is also well suited for
use with the Model-View-ViewModel (MVVM) pattern, where XAML defines the view
that is linked to viewmodel code through XAML-based data bindings.
Within a XAML file, you can define user interfaces using all the .NET MAUI views, layouts,
and pages, as well as custom classes. The XAML file can be either compiled or
embedded in the app package. Either way, the XAML is parsed at build time to locate
named objects, and at runtime the objects represented by the XAML are instantiated
and initialized.
There are also disadvantages, mostly related to limitations that are intrinsic to markup
languages:
XAML cannot contain code. All event handlers must be defined in a code file.
XAML cannot contain loops for repetitive processing.
XAML cannot contain conditional processing. However, a data-binding can
reference a code-based binding converter that effectively allows some conditional
processing.
XAML generally cannot instantiate classes that do not define a parameterless
constructor, although this restriction can sometimes be overcome.
XAML generally cannot call methods, although this restriction can sometimes be
overcome.
There is no visual designer for producing XAML in .NET MAUI apps. All XAML must be
hand-written, but you can use XAML hot reload to view your UI as you edit it.
XAML is basically XML, but XAML has some unique syntax features. The most important
are:
Property elements
Attached properties
Markup extensions
These features are not XML extensions. XAML is entirely legal XML. But these XAML
syntax features use XML in unique ways.
Get started with XAML
Article • 03/03/2023 • 8 minutes to read
In a .NET Multi-platform App UI (.NET MAUI) app, XAML is mostly used to define the
visual contents of a page and works together with a C# code-behind file. The code-
behind file provides code support for the markup. Together, these two files contribute to
a new class definition that includes child views and property initialization. Within the
XAML file, classes and properties are referenced with XML elements and attributes, and
links between the markup and code are established.
The first file pairing is App.xaml, a XAML file, and App.xaml.cs, a C# code-behind file
associated with the XAML file. Both App.xaml and App.xaml.cs contribute to a class
named App that derives from Application . The second file pairing is AppShell.xaml and
AppShell.xaml.cs, which contribute to a class named AppShell that derives from Shell.
Most other classes with XAML files contribute to a class that derives from ContentPage,
and define the UI of a page. This is true of the MainPage.xaml and MainPage.xaml.cs
files.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
...
</ContentPage>
The two XML namespace ( xmlns ) declarations refer to URIs on microsoft.com. However,
there's no content at these URIs, and they basically function as version identifiers.
The first XML namespace declaration means that tags defined within the XAML file with
no prefix refer to classes in .NET MAUI, for example ContentPage. The second
namespace declaration defines a prefix of x . This is used for several elements and
attributes that are intrinsic to XAML itself and which are supported by other
implementations of XAML. However, these elements and attributes are slightly different
depending on the year embedded in the URI. .NET MAUI supports the 2009 XAML
specification.
At the end of the first tag, the x prefix is used for an attribute named Class . Because
the use of this x prefix is virtually universal for the XAML namespace, XAML attributes
such as Class are almost always referred to as x:Class . The x:Class attribute specifies
a fully qualified .NET class name: the MainPage class in the MyMauiApp namespace. This
means that this XAML file defines a new class named MainPage in the MyMauiApp
namespace that derives from ContentPage (the tag in which the x:Class attribute
appears).
The x:Class attribute can only appear in the root element of a XAML file to define a
derived C# class. This is the only new class defined in the XAML file. Everything else that
appears in a XAML file is instead simply instantiated from existing classes and initialized.
C#
namespace MyMauiApp;
public MainPage()
InitializeComponent();
The MainPage class derives from ContentPage, and is a partial class definition.
When Visual Studio builds the project, a source generator generates new C# source that
contains the definition of the InitializeComponent method that's called from the
MainPage constructor and adds it to the compilation object.
At runtime, code in the MauiProgram class bootstraps the app and executes the App class
constructor, which instantiates AppShell . The AppShell class instantiates the first page
of the app to be displayed, which is MainPage . The MainPage constructor calls
InitializeComponent , which initializes all the objects defined in the XAML file, connects
them all together in parent-child relationships, attaches event handlers defined in code
to events set in the XAML file, and sets the resultant tree of objects as the content of the
page.
7 Note
The AppShell class uses .NET MAUI Shell to set the first page of the app to be
displayed. However, Shell is beyond the scope of this introduction to XAML. For
more information, see .NET MAUI Shell.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.HelloXamlPage"
VerticalOptions="Center"
HorizontalTextAlignment="Center"
Rotation="-15"
FontSize="18"
FontAttributes="Bold"
TextColor="Blue" />
</ContentPage>
From the example above the relationship between classes, properties, and XML should
be evident. A .NET MAUI class (such as ContentPage or Label) appears in the XAML file
as an XML element. Properties of that class—including Title on ContentPage and seven
properties of Label usually appear as XML attributes.
Many shortcuts exist to set the values of these properties. Some properties are basic
data types. For example, the Title and Text properties are of type string , and
Rotation is of type double . The HorizontalTextAlignment property is of type
TextAlignment , which is an enumeration. For a property of any enumeration type, all you
For properties of more complex types, however, converters are used for parsing the
XAML. These are classes in .NET MAUI that derive from TypeConverter . For the example
above, several .NET MAUI converters are automatically applied to convert string values
to their correct type:
of public static fields of the Colors class or hexadecimal RGB values, with or
without an alpha channel.
Page navigation
When you run a .NET MAUI app, the MainPage is typically displayed. To see a different
page you can either set that as the new startup page in the AppShell.xaml file, or
navigate to the new page from MainPage .
C#
public MainPage()
InitializeComponent();
Text = "Navigate!",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
};
Content = button;
When you compile and deploy the new version of this app, a button appears on the
screen. Pressing it navigates to HelloXamlPage :
You can navigate back to MainPage using the navigation bar that appears on each
platform.
7 Note
An alternative to this navigation model is to use .NET MAUI Shell. For more
information, see .NET MAUI Shell overview.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.XamlPlusCodePage"
<StackLayout>
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
This XAML file is syntactically complete, and produces the following UI:
However, while you can interact with the Slider and Button, the UI isn't updated. The
Slider should cause the Label to display the current value, and the Button should do
something.
Displaying a Slider value using a Label can be achieved entirely in XAML with a data
binding. However, it's useful to see the code solution first. Even so, handling the Button
click definitely requires code. This means that the code-behind file for XamlPlusCodePage
must contain handlers for the ValueChanged event of the Slider and the Clicked event of
the Button:
C#
namespace XamlSamples
public XamlPlusCodePage()
InitializeComponent();
valueLabel.Text = args.NewValue.ToString("F3");
Back in the XAML file, the Slider and Button tags need to include attributes for the
ValueChanged and Clicked events that reference these handlers:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.XamlPlusCodePage"
<StackLayout>
<Slider VerticalOptions="Center"
ValueChanged="OnSliderValueChanged" />
<Label x:Name="valueLabel"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
HorizontalOptions="Center"
VerticalOptions="Center"
Clicked="OnButtonClicked" />
</StackLayout>
</ContentPage>
Notice that assigning a handler to an event has the same syntax as assigning a value to
a property. In addition, for the ValueChanged event handler of the Slider to use the Label
to display the current value, the handler needs to reference that object from code.
Therefore, the Label needs a name, which is specified with the x:Name attribute. The x
prefix of the x:Name attribute indicates that this attribute is intrinsic to XAML. The name
you assign to the x:Name attribute has the same rules as C# variable names. For
example, it must begin with a letter or underscore and contain no embedded spaces.
The ValueChanged event handler can now set the Label to display the new Slider value,
which is available from the event arguments:
C#
valueLabel.Text = args.NewValue.ToString("F3");
Alternatively, the handler could obtain the Slider object that is generating this event
from the sender argument and obtain the Value property from that:
C#
valueLabel.Text = ((Slider)sender).Value.ToString("F3");
The result is that any manipulation of the Slider causes its value to be displayed in the
Label:
In the example above the Button simulates a response to a Clicked event by displaying
an alert with the Text of the button. Therefore, the event handler can cast the sender
argument to a Button and then access its properties:
C#
Next steps
XAML is mostly designed for instantiating and initializing objects. But often, properties
must be set to complex objects that cannot easily be represented as XML strings, and
sometimes properties defined by one class must be set on a child class. These two
needs require the essential XAML syntax features of property elements and attached
properties.
XAML is mostly designed for instantiating and initializing objects. But often, properties
must be set to complex objects that cannot easily be represented as XML strings, and
sometimes properties defined by one class must be set on a child class. These two
needs require the essential XAML syntax features of property elements and attached
properties.
Property elements
In .NET Multi-platform App UI (.NET MAUI) XAML, properties of classes are normally set
as XML attributes:
XAML
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="18"
TextColor="Aqua" />
XAML
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="18">
<Label.TextColor>
Aqua
</Label.TextColor>
</Label>
These two examples that specify the TextColor property are functionally equivalent, and
enable the introduction of some basic terminology:
7 Note
In a property element, the value of the property is always defined as the content
between the property-element start and end tags.
Property-element syntax can also be used on more than one property of an object:
XAML
VerticalOptions="Center">
<Label.FontAttributes>
Bold
</Label.FontAttributes>
<Label.FontSize>
Large
</Label.FontSize>
<Label.TextColor>
Aqua
</Label.TextColor>
</Label>
While property-element syntax might seem unnecessary, it's essential when the value of
a property is too complex to be expressed as a simple string. Within the property-
element tags you can instantiate another object and set its properties. For example, the
Grid layout has properties named RowDefinitions and ColumnDefinitions , which are of
type RowDefinitionCollection and ColumnDefinitionCollection respectively. These
types are collections of RowDefinition and ColumnDefinition objects, and you typically
use property element syntax to set them:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.GridDemoPage"
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
...
</Grid>
</ContentPage>
Attached properties
In the previous example you saw that the Grid requires property elements for the
RowDefinitions and ColumnDefinitions collections to define the rows and columns. This
suggests that there must be a technique for indicating the row and column where each
child of the Grid resides.
Within the tag for each child of the Grid you specify the row and column of that child
using the Grid.Row and Grid.Column attributes, which have default values of 0. You can
also indicate if a child spans more than one row or column with the Grid.RowSpan and
Grid.ColumnSpan attributes, which have default values of 1.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.GridDemoPage"
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
TextColor="White"
BackgroundColor="Blue" />
<BoxView Color="Silver"
Grid.Column="1" />
<BoxView Color="Teal"
Grid.Row="1" />
Grid.Row="1" Grid.Column="1"
TextColor="Purple"
BackgroundColor="Aqua"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
Grid.Column="2" Grid.RowSpan="2"
TextColor="Yellow"
BackgroundColor="Blue"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
Grid.Row="2" Grid.ColumnSpan="2"
TextColor="Blue"
BackgroundColor="Yellow"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
Grid.Row="2" Grid.Column="2"
TextColor="Aqua"
BackgroundColor="Red"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>
special types of bindable properties known as attached properties. They are defined by
the Grid class but set on children of the Grid.
7 Note
When you wish to use these attached properties in code, the Grid class provides
static methods named GetRow , SetRow , GetColumn , SetColumn , GetRowSpan ,
SetRowSpan , GetColumnSpan , and SetColumnSpan .
Attached properties are recognizable in XAML as attributes containing both a class and
a property name separated by a period. They are called attached properties because they
are defined by one class (in this case, Grid) but attached to other objects (in this case,
children of the Grid). During layout, the Grid can interrogate the values of these
attached properties to know where to place each child.
Content properties
In the previous example, the Grid object was set to the Content property of the
ContentPage. However, the Content property wasn't referenced in the XAML but can be:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.XamlPlusCodePage"
<ContentPage.Content>
<Grid>
...
</Grid>
</ContentPage.Content>
</ContentPage>
The Content property isn't required in XAML because elements defined for use in .NET
MAUI XAML are allowed to have one property specified as the ContentProperty
attribute on the class:
C#
[ContentProperty("Content")]
...
Any property specified as the ContentProperty of a class means that the property-
element tags for the property are not required. Therefore, the example above specifies
that any XAML content that appears between the start and end ContentPage tags is
assigned to the Content property.
Many classes also have ContentProperty attribute definitions. For example, the content
property of Label is Text .
Platform differences
.NET MAUI apps can customize UI appearance on a per-platform basis. This can be
achieved in XAML using the OnPlatform and On classes:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
OnPlatform is a generic class and so you need to specify the generic type argument, in
this case, Thickness , which is the type of Padding property. This is achieved with the
x:TypeArguments XAML attribute. The OnPlatform class has a property named
Platforms , that is an IList of On objects. Each On object can set the Platform and
Value property to define the Thickness value for a specific platform.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
7 Note
Next steps
.NET MAUI XAML markup extensions enable properties to be set to objects or values
that are referenced indirectly from other sources. XAML markup extensions are
particularly important for sharing objects, and referencing constants used throughout an
app.
.NET Multi-platform App UI (.NET MAUI) XAML markup extensions enable properties to
be set to objects or values that are referenced indirectly from other sources. XAML
markup extensions are particularly important for sharing objects, and referencing
constants used throughout an app, but they find their greatest utility in data bindings.
Typically, you use XAML to set properties of an object to explicit values, such as a string,
a number, an enumeration member, or a string that is converted to a value behind the
scenes. Sometimes, however, properties must instead reference values defined
somewhere else, or which might require a little processing by code at runtime. For these
purposes, XAML markup extensions are available.
XAML markup extensions are so named because they are backed by code in classes that
implement IMarkupExtension . It's also possible to write your own custom markup
extensions.
In many cases, XAML markup extensions are instantly recognizable in XAML files
because they appear as attribute values delimited by curly braces, { and }, but
sometimes markup extensions also appear in markup as conventional elements.
) Important
Markup extensions can have properties, but they are not set like XML attributes. In
a markup extension, property settings are separated by commas, and no quotation
marks appear within the curly braces.
Shared resources
Some XAML pages contain several views with properties set to the same values. For
example, many of the property settings for these Button objects are the same:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
<StackLayout>
HorizontalOptions="Center"
VerticalOptions="Center"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
HorizontalOptions="Center"
VerticalOptions="Center"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
HorizontalOptions="Center"
VerticalOptions="Center"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
</StackLayout>
</ContentPage>
If one of these properties needs to be changed, you might prefer to make the change
just once rather than three times. If this were code, you’d likely be using constants and
static read-only objects to help keep such values consistent and easy to modify.
In XAML, one popular solution is to store such values or objects in a resource dictionary.
The VisualElement class defines a property named Resources of type
ResourceDictionary, which is a dictionary with keys of type string and values of type
object . You can put objects into this dictionary and then reference them from markup,
all in XAML.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
<ContentPage.Resources>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center" />
</ContentPage.Resources>
...
</ContentPage>
In this example, the two resources are values of the structure type LayoutOptions , and
each has a unique key and one or two properties set. In code and markup, it’s much
more common to use the static fields of LayoutOptions , but here it’s more convenient to
set the properties.
7 Note
The resources can then be consumed by the Button objects, by using the StaticResource
XAML markup extension to set their HorizontalOptions and VerticalOptions properties:
XAML
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
The StaticResource markup extension is always delimited with curly braces, and includes
the dictionary key. The name StaticResource distinguishes it from DynamicResource,
which .NET MAUI also supports. DynamicResource is for dictionary keys associated with
values that might change at runtime, while StaticResource accesses elements from the
dictionary just once when the elements on the page are constructed. Whenever the
XAML parser encounters a StaticResource markup extension, it searches up the visual
tree and uses the first ResourceDictionary it encounters containing that key.
It’s necessary to store doubles in the dictionary for the BorderWidth , Rotation , and
FontSize properties. XAML conveniently defines tags for common data types like
x:Double and x:Int32 :
XAML
<ContentPage.Resources>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center" />
<x:Double x:Key="borderWidth">3</x:Double>
<x:Double x:Key="rotationAngle">-15</x:Double>
<x:Double x:Key="fontSize">24</x:Double>
</ContentPage.Resources>
These additional three resources can be referenced in the same way as the
LayoutOptions values:
XAML
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="Red"
For resources of type Color, you can use the same string representations that you use
when directly assigning attributes of these types. Type converters included in .NET MAUI
are invoked when the resource is created. It's also possible to use the OnPlatform class
within the resource dictionary to define different values for the platforms. The following
example uses this class for setting different text colors:
XAML
<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
</OnPlatform>
The OnPlatform resource gets an x:Key attribute because it’s an object in the dictionary,
and an x:TypeArguments attribute because it’s a generic class. The iOS , and Android
attributes are converted to Color values when the object is initialized.
The following example shows the three buttons accessing six shared values:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
<ContentPage.Resources>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center" />
<x:Double x:Key="borderWidth">3</x:Double>
<x:Double x:Key="rotationAngle">-15</x:Double>
<x:Double x:Key="fontSize">24</x:Double>
<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
</OnPlatform>
</ContentPage.Resources>
<StackLayout>
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
</StackLayout>
</ContentPage>
XAML
<StackLayout>
<StackLayout.Resources>
<Color x:Key="textColor">Blue</Color>
</StackLayout.Resources>
...
</StackLayout>
One of the most common types of objects stored in resource dictionaries is the .NET
MAUI Style, which defines a collection of property settings. For more information about
styles, see Style apps using XAML.
7 Note
7 Note
The following example demonstrates how x:Static can explicitly reference static fields
and enumeration members:
XAML
VerticalOptions="{x:Static LayoutOptions.Start}"
HorizontalTextAlignment="{x:Static TextAlignment.Center}"
The main use of the x:Static markup extension is in referencing static fields or
properties from your own code. For example, here’s an AppConstants class that contains
some static fields that you might want to use on multiple pages throughout an app:
C#
namespace XamlSamples
To reference the static fields of this class in a XAML file, you need to use an XML
namespace declaration to indicate where this file is located. Each additional XML
namespace declaration defines a new prefix. To access classes local to the root app
namespace, such as AppConstants , you could use the prefix local . The namespace
declaration must indicate the CLR (Common Language Runtime) namespace name, also
known as the .NET namespace name, which is the name that appears in a C# namespace
definition or in a using directive:
C#
xmlns:local="clr-namespace:XamlSamples"
You can also define XML namespace declarations for .NET namespaces. For example,
here’s a sys prefix for the standard .NET System namespace, which is in the netstandard
assembly. Because this is another assembly, you must also specify the assembly name, in
this case netstandard :
C#
xmlns:sys="clr-namespace:System;assembly=netstandard"
7 Note
The keyword clr-namespace is followed by a colon and then the .NET namespace
name, followed by a semicolon, the keyword assembly , an equal sign, and the
assembly name.
The static fields can then be consumed after declaring the XML namespace:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="XamlSamples.StaticConstantsPage"
Padding="5,25,5,0">
<StackLayout>
TextColor="{x:Static local:AppConstants.BackgroundColor}"
BackgroundColor="{x:Static
local:AppConstants.ForegroundColor}"
FontAttributes="Bold"
FontSize="30"
HorizontalOptions="Center" />
HeightRequest="{x:Static sys:Math.E}"
Color="{x:Static local:AppConstants.ForegroundColor}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="100" />
</StackLayout>
</ContentPage>
In this example, the BoxView dimensions are set to Math.PI and Math.E , but scaled by a
factor of 100:
If a property has a non- null value by default but you want to set it to null , set it
to the {x:Null} markup extension.
If a property is of type Type , you can assign it to a Type object using the markup
extension {x:Type someClass} .
You can define arrays in XAML using the x:Array markup extension. This markup
extension has a required attribute named Type that indicates the type of the
elements in the array.
For more information about XAML markup extensions, see Consume XAML markup
extensions.
Next steps
.NET MAUI data bindings allow properties of two objects to be linked so that a change
in one causes a change in the other.
.NET Multi-platform App UI (.NET MAUI) data bindings allow properties of two objects
to be linked so that a change in one causes a change in the other. This is a very valuable
tool, and while data bindings can be defined entirely in code, XAML provides shortcuts
and convenience.
Data bindings
Data bindings connect properties of two objects, called the source and the target. In
code, two steps are required:
1. The BindingContext property of the target object must be set to the source object,
2. The SetBinding method (often used in conjunction with the Binding class) must
be called on the target object to bind a property of that object to a property of the
source object.
The target property must be a bindable property, which means that the target object
must derive from BindableObject. A property of Label, such as Text , is associated with
the bindable property TextProperty .
In XAML, you must also perform the same two steps that are required in code, except
that the Binding markup extension takes the place of the SetBinding call and the
Binding class. However, when you define data bindings in XAML, there are multiple
ways to set the BindingContext of the target object. Sometimes it’s set from the code-
behind file, sometimes using a StaticResource or x:Static markup extension, and
sometimes as the content of BindingContext property-element tags.
View-to-view bindings
You can define data bindings to link properties of two views on the same page. In this
case, you set the BindingContext of the target object using the x:Reference markup
extension.
The following example contains a Slider and two Label views, one of which is rotated by
the Slider value and another which displays the Slider value:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderBindingsPage"
<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="Center" />
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
The Slider contains an x:Name attribute that is referenced by the two Label views using
the x:Reference markup extension. The x:Reference binding extension defines a
property named Name to set to the name of the referenced element, in this case slider .
However, the ReferenceExtension class that defines the x:Reference markup extension
also defines a ContentProperty attribute for Name , which means that it isn’t explicitly
required.
The Binding markup extension itself can have several properties, just like the
BindingBase and Binding class. The ContentProperty for Binding is Path , but the
“Path=” part of the markup extension can be omitted if the path is the first item in the
Binding markup extension.
The second Binding markup extension sets the StringFormat property. In .NET MAUI,
bindings do not perform any implicit type conversions, and if you need to display a non-
string object as a string you must provide a type converter or use StringFormat .
) Important
The solution to this and other problems involves the Mode property, which is set to a
member of the BindingMode enumeration:
Default
OneTime — data goes from source to target, but only when the BindingContext
changes
The following example demonstrates one common use of the OneWayToSource and
TwoWay binding modes:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderTransformsPage"
Padding="5"
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Label x:Name="label"
Text="TEXT"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="scaleSlider"
BindingContext="{x:Reference label}"
Grid.Row="1" Grid.Column="0"
Maximum="10"
Grid.Row="1" Grid.Column="1"
VerticalTextAlignment="Center" />
<Slider x:Name="rotationSlider"
BindingContext="{x:Reference label}"
Grid.Row="2" Grid.Column="0"
Maximum="360"
Grid.Row="2" Grid.Column="1"
VerticalTextAlignment="Center" />
<Slider x:Name="rotationXSlider"
BindingContext="{x:Reference label}"
Grid.Row="3" Grid.Column="0"
Maximum="360"
Grid.Row="3" Grid.Column="1"
VerticalTextAlignment="Center" />
<Slider x:Name="rotationYSlider"
BindingContext="{x:Reference label}"
Grid.Row="4" Grid.Column="0"
Maximum="360"
Grid.Row="4" Grid.Column="1"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>
In this example, four Slider views are intended to control the Scale , Rotate , RotateX ,
and RotateY properties of a Label. At first, it seems as if these four properties of the
Label should be data-binding targets because each is being set by a Slider. However, the
BindingContext of Label can be only one object, and there are four different sliders. For
that reason, the BindingContext of each of the four sliders is set to the Label, and the
bindings are set on the Value properties of the sliders. By using the OneWayToSource and
TwoWay modes, these Value properties can set the source properties, which are the
The bindings on three of the Slider views are OneWayToSource , meaning that the Slider
value causes a change in the property of its BindingContext , which is the Label named
label . These three Slider views cause changes to the Rotate , RotateX , and RotateY
However, the binding for the Scale property is TwoWay . This is because the Scale
property has a default value of 1, and using a TwoWay binding causes the Slider initial
value to be set at 1 rather than 0. If that binding were OneWayToSource , the Scale
property would initially be set to 0 from the Slider default value. The Label would not be
visible
7 Note
The VisualElement class also has ScaleX and ScaleY properties, which scale the
VisualElement on the x-axis and y-axis respectively.
want, but in many cases, ToString returns only the fully-qualified class name of the
object.
However, the items in the ListView collection can be displayed any way you want
through the use of a template, which involves a class that derives from Cell. The
template is cloned for every item in the ListView, and data bindings that have been set
on the template are transferred to the individual clones. Custom cells can be created for
items using the ViewCell class.
ListView can display a list of every named color that's available in .NET MAUI, with the
help of the NamedColor class:
C#
using System.Reflection;
using System.Text;
namespace XamlSamples
static NamedColor()
if (fieldInfo.IsPublic &&
fieldInfo.IsStatic &&
fieldInfo.FieldType == typeof(Color))
stringBuilder.Clear();
int index = 0;
stringBuilder.Append(' ');
stringBuilder.Append(ch);
index++;
Name = name,
FriendlyName = stringBuilder.ToString(),
Color = (Color)fieldInfo.GetValue(null)
};
all.Add(namedColor);
all.TrimExcess();
All = all;
Each NamedColor object has Name and FriendlyName properties of type string , a Color
property of type Color, and Red , Green , and Blue properties. In addition, the
NamedColor static constructor creates an IEnumerable<NamedColor> collection that
contains NamedColor objects corresponding to the fields of type Color in the Colors
class, and assigns it to its public static All property.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
</ContentPage>
To define a template for the items, the ItemTemplate should be set to a DataTemplate
that references a ViewCell. The ViewCell should define a layout of one or more views to
display each item:
XAML
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
7 Note
The binding source for cells, and children of cells, is the ListView.ItemsSource
collection.
In this example, the Label element is set to the View property of the ViewCell. The
ViewCell.View tags are not needed because the View property is the content property
of ViewCell. This XAML displays the FriendlyName property of each NamedColor object:
The item template can be expanded to display more information and the actual color:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
<ContentPage.Resources>
<x:Double x:Key="boxSize">50</x:Double>
<x:Int32 x:Key="rowHeight">60</x:Int32>
</ContentPage.Resources>
RowHeight="{StaticResource rowHeight}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
Orientation="Horizontal"
Spacing="15">
HeightRequest="{StaticResource boxSize}"
VerticalOptions="Center">
FontAttributes="Bold"
FontSize="14" />
<StackLayout Orientation="Horizontal"
Spacing="0">
Converter=
{StaticResource intConverter},
ConverterParameter=255,
StringFormat='R=
{0:X2}'}" />
Converter=
{StaticResource intConverter},
ConverterParameter=255,
StringFormat=', G=
{0:X2}'}" />
Converter=
{StaticResource intConverter},
ConverterParameter=255,
StringFormat=', B=
{0:X2}'}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
This issue can be solved with a value converter, also called a binding converter. This is a
class that implements the IValueConverter interface, which means it has two methods
named Convert and ConvertBack . The Convert method is called when a value is
transferred from source to target. The ConvertBack method is called for transfers from
target to source in OneWayToSource or TwoWay bindings:
C#
using System.Globalization;
namespace XamlSamples
float multiplier;
multiplier = 1;
float divider;
divider = 1;
7 Note
The ConvertBack method does not play a role in this example because the bindings
are only one way from source to target.
A binding references a binding converter with the Converter property. A binding
converter can also accept a parameter specified with the ConverterParameter property.
For some versatility, this is how the multiplier is specified. The binding converter checks
the converter parameter for a valid float value.
XAML
XAML
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
The item template dsplays the color, its friendly name, and its RGB values:
The ListView can handle changes that dynamically occur in the underlying data, but only
if you take certain steps. If the collection of items assigned to the ItemsSource property
of the ListView changes during runtime, use an ObservableCollection class for these
items. ObservableCollection implements the INotifyCollectionChanged interface, and
ListView will install a handler for the CollectionChanged event.
If properties of the items themselves change during runtime, then the items in the
collection should implement the INotifyPropertyChanged interface and signal changes
to property values using the PropertyChanged event.
Next steps
Data bindings provide a powerful mechanism for linking properties between two objects
within a page, or between visual objects and underlying data. But when the application
begins working with data sources, a popular app architectural pattern begins to emerge
as a useful paradigm.
Data binding and MVVM
Data binding and MVVM
Article • 04/03/2023 • 10 minutes to read
) Important
.NET Multi-platform App UI (.NET MAUI) marshals binding updates to the UI thread.
When using MVVM this enables you to update data-bound viewmodel properties
from any thread, with .NET MAUI's binding engine bringing the updates to the UI
thread.
Simple MVVM
In XAML markup extensions you saw how to define a new XML namespace declaration
to allow a XAML file to reference classes in other assemblies. The following example
uses the x:Static markup extension to obtain the current date and time from the static
DateTime.Now property in the System namespace:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="XamlSamples.OneShotDateTimePage"
Spacing="25" Padding="30,0"
VerticalOptions="Center"
HorizontalOptions="Center">
</VerticalStackLayout>
</ContentPage>
However, the problem is that the date and time are set once when the page is
constructed and initialized, and never change.
A XAML page can display a clock that always shows the current time, but it requires
additional code. The MVVM pattern is a natural choice for .NET MAUI apps when data
binding from properties between visual objects and the underlying data. When thinking
in terms of MVVM, the model and viewmodel are classes written entirely in code. The
view is often a XAML file that references properties defined in the viewmodel through
data bindings. In MVVM, a model is ignorant of the viewmodel, and a viewmodel is
ignorant of the view. However, often you tailor the types exposed by the viewmodel to
the types associated with the UI.
7 Note
In simple examples of MVVM, such as those shown here, often there is no model at
all, and the pattern involves just a view and viewmodel linked with data bindings.
The following example shows a viewmodel for a clock, with a single property named
DateTime that's updated every second:
C#
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace XamlSamples;
set
if (_dateTime != value)
_dateTime = value;
public ClockViewModel()
this.DateTime = DateTime.Now;
~ClockViewModel() =>
_timer.Dispose();
target updated with the new value. In the previous code example, the
OnPropertyChanged method handles raising the event while automatically determining
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.ClockPage"
Title="Clock Page">
<ContentPage.BindingContext>
<local:ClockViewModel />
</ContentPage.BindingContext>
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
</ContentPage>
The Binding markup extension on the Text property of the Label formats the DateTime
property. The following screenshot shows the result:
In addition, it’s possible to access individual properties of the DateTime property of the
viewmodel by separating the properties with periods:
XAML
Interactive MVVM
MVVM is often used with two-way data bindings for an interactive view based on an
underlying data model.
The following example shows the HslViewModel that converts a Color value into Hue ,
Saturation , and Luminosity values, and back again:
C#
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace XamlSamples;
set
if (_hue != value)
set
if (_saturation != value)
set
if (_luminosity != value)
set
if (_color != value)
_color = value;
_hue = _color.GetHue();
_saturation = _color.GetSaturation();
_luminosity = _color.GetLuminosity();
OnPropertyChanged("Hue");
OnPropertyChanged("Saturation");
OnPropertyChanged("Luminosity");
In this example, changes to the Hue , Saturation , and Luminosity properties cause the
Color property to change, and changes to the Color property causes the other three
properties to change. This might seem like an infinite loop, except that the viewmodel
doesn't invoke the PropertyChanged event unless the property has changed.
The following XAML example contains a BoxView whose Color property is bound to the
Color property of the viewmodel, and three Slider and three Label views bound to the
Hue , Saturation , and Luminosity properties:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.HslColorScrollPage"
<ContentPage.BindingContext>
</ContentPage.BindingContext>
HeightRequest="100"
WidthRequest="100"
HorizontalOptions="Center" />
HorizontalOptions="Center" />
Margin="20,0,20,0" />
HorizontalOptions="Center" />
Margin="20,0,20,0" />
HorizontalOptions="Center" />
Margin="20,0,20,0" />
</VerticalStackLayout>
</ContentPage>
The binding on each Label is the default OneWay . It only needs to display the value.
However, the default binding on each Slider is TwoWay . This allows the Slider to be
initialized from the viewmodel. When the viewmodel is instantiated it's Color property
is set to Aqua . A change in a Slider sets a new value for the property in the viewmodel,
which then calculates a new color:
Commanding
Sometimes an app has needs that go beyond property bindings by requiring the user to
initiate commands that affect something in the viewmodel. These commands are
generally signaled by button clicks or finger taps, and traditionally they are processed in
the code-behind file in a handler for the Clicked event of the Button or the Tapped
event of a TapGestureRecognizer.
To allow a data binding between a Button and a viewmodel, the Button defines two
properties:
7 Note
The viewmodel can define properties of type ICommand . You can then bind these
properties to the Command property of each Button or other element, or perhaps a
custom view that implements this interface. You can optionally set the CommandParameter
property to identify individual Button objects (or other elements) that are bound to this
viewmodel property. Internally, the Button calls the Execute method whenever the user
taps the Button, passing to the Execute method its CommandParameter .
The CanExecute method and CanExecuteChanged event are used for cases where a Button
tap might be currently invalid, in which case the Button should disable itself. The Button
calls CanExecute when the Command property is first set and whenever the
CanExecuteChanged event is raised. If CanExecute returns false , the Button disables itself
You can use the Command or Command<T> class included in .NET MAUI to implement the
ICommand interface. These two classes define several constructors plus a
ChangeCanExecute method that the viewmodel can call to force the Command object to
The following example shows a viewmodel for a simple keypad that is intended for
entering telephone numbers:
C#
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace XamlSamples;
private set
if (_inputString != value)
_inputString = value;
OnPropertyChanged();
DisplayText = FormatText(_inputString);
((Command)DeleteCharCommand).ChangeCanExecute();
private set
if (_displayText != value)
_displayText = value;
OnPropertyChanged();
public KeypadViewModel()
DeleteCharCommand =
new Command(
);
// Format the string based on the type of data and the length
// Do nothing
else
return formatted;
In this example, the Execute and CanExecute methods for the commands are defined as
lambda functions in the constructor. The viewmodel assumes that the AddCharCommand
property is bound to the Command property of several buttons (or anything other
controls that have a command interface), each of which is identified by the
CommandParameter . These buttons add characters to an InputString property, which is
then formatted as a phone number for the DisplayText property. There's also a second
property of type ICommand named DeleteCharCommand . This is bound to a back-spacing
button, but the button should be disabled if there are no characters to delete.
The following example shows the XAML that consumes the KeypadViewModel :
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.KeypadPage"
Title="Keypad Page">
<ContentPage.BindingContext>
<local:KeypadViewModel />
</ContentPage.BindingContext>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
Margin="0,0,10,0" FontSize="20"
LineBreakMode="HeadTruncation"
VerticalTextAlignment="Center" HorizontalTextAlignment="End"
Grid.ColumnSpan="2" />
</Grid>
</ContentPage>
In this example, the Command property of the first Button that is bound to the
DeleteCharCommand . The other buttons are bound to the AddCharCommand with a
CommandParameter that's the same as the character that appears on the Button:
XAML compilation
Article • 09/06/2022 • 2 minutes to read
.NET Multi-platform App UI (.NET MAUI) XAML is compiled directly into intermediate
language (IL) with the XAML compiler (XAMLC). XAML compilation offers a number of
benefits:
XAML compilation is enabled by default in .NET MAUI apps. For apps built using debug
configuration, XAML compilation provides compile-time validation of XAML, but does
not convert the XAML to IL in the assembly. Instead, XAML files are included as
embedded resources in the app package, and evaluated at runtime. For apps built using
release configuration, XAML compilation provides compile-time validation of XAML, and
converts the XAML to IL that's written to the assembly. However, XAML compilation
behavior can be overridden in both configurations with the XamlCompilationAttribute
class.
Enable compilation
XAML compilation can be enabled by passing XamlCompilationOptions.Compile to the
XamlCompilationAttribute :
C#
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
In this example, XAML compilation is enabled for all of the XAML contained within the
assembly, with XAML errors being reported at compile-time rather than runtime.
Tip
[XamlCompilation (XamlCompilationOptions.Compile)]
...
In this example, XAML compilation is enabled only for the MyPage class.
7 Note
Disable compilation
XAML compilation can be disabled by passing XamlCompilationOptions.Skip to the
XamlCompilationAttribute :
C#
[assembly: XamlCompilation(XamlCompilationOptions.Skip)]
In this example, XAML compilation is disabled within the assembly, with XAML errors
being reported at runtime rather than compile-time.
C#
[XamlCompilation (XamlCompilationOptions.Skip)]
...
In this example, XAML compilation is disabled only for the MyPage class.
2 Warning
The .NET Multi-platform App UI (.NET MAUI) x:FieldModifier attribute specifies the
access level for generated fields for named XAML elements.
Private – specifies that the generated field for the XAML element is accessible
By default, if the value of the attribute isn't set, the generated field for the element will
be private .
7 Note
The value of the attribute can use any casing, as it will be converted to lowercase by
.NET MAUI.
XAML
) Important
The x:FieldModifier attribute cannot be used to specify the access level of a .NET
MAUI XAML class.
Generics
Article • 04/03/2023 • 3 minutes to read
.NET Multi-platform App UI (.NET MAUI) XAML provides support for consuming generic
CLR types by specifying the generic constraints as type arguments. This support is
provided by the x:TypeArguments directive, which passes the constraining type
arguments of a generic to the constructor of the generic type.
Type arguments are specified as a string, and are typically prefixed, such as sys:String
and sys:Int32 . Prefixing is required because the typical types of CLR generic constraints
come from libraries that are not mapped to the default .NET MAUI namespaces.
However, the XAML 2009 built-in types such as x:String and x:Int32 , can also be
specified as type arguments, where x is the XAML language namespace for XAML 2009.
For more information about the XAML 2009 built-in types, see XAML 2009 Language
Primitives.
) Important
Defining generic classes in .NET MAUI XAML, with the x:TypeArguments directive, is
unsupported.
7 Note
The x:Type markup extension supplies a Common Language Runtime (CLR) type
reference for a generic type, and has a similar function to the typeof operator in
C#. For more information, see x:Type markup extension.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:scg="clr-
namespace:System.Collections.Generic;assembly=netstandard"
...>
<CollectionView>
<CollectionView.ItemsSource>
<scg:List x:TypeArguments="x:String">
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</scg:List>
</CollectionView.ItemsSource>
</CollectionView>
</ContentPage>
Alternatively, but equivalently, the List<T> collection can be instantiated with the CLR
String type:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:scg="clr-
namespace:System.Collections.Generic;assembly=netstandard"
xmlns:sys="clr-namespace:System;assembly=netstandard"
...>
<CollectionView>
<CollectionView.ItemsSource>
<scg:List x:TypeArguments="sys:String">
<sys:String>Baboon</sys:String>
<sys:String>Capuchin Monkey</sys:String>
<sys:String>Blue Monkey</sys:String>
<sys:String>Squirrel Monkey</sys:String>
<sys:String>Golden Lion Tamarin</sys:String>
<sys:String>Howler Monkey</sys:String>
<sys:String>Japanese Macaque</sys:String>
</scg:List>
</CollectionView.ItemsSource>
</CollectionView>
</ContentPage>
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:GenericsDemo.Models"
xmlns:scg="clr-
namespace:System.Collections.Generic;assembly=netstandard"
...>
<CollectionView>
<CollectionView.ItemsSource>
<scg:List x:TypeArguments="models:Monkey">
<models:Monkey Name="Baboon"
ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_an
ubis_%28Serengeti%2C_2009%29.jpg/200px-
Papio_anubis_%28Serengeti%2C_2009%29.jpg" />
ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin
_Costa_Rica.jpg/200px-Capuchin_Costa_Rica.jpg" />
ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonk
ey.jpg/220px-BlueMonkey.jpg" />
</scg:List>
</CollectionView.ItemsSource>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
Monkey type argument. The List<Monkey> collection is initialized with multiple Monkey
items, and a DataTemplate that defines the appearance of each Monkey object is set as
the ItemTemplate of the CollectionView.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:GenericsDemo.Models"
xmlns:scg="clr-
namespace:System.Collections.Generic;assembly=netstandard"
...>
<CollectionView>
<CollectionView.ItemsSource>
<scg:List
x:TypeArguments="scg:KeyValuePair(x:String,models:Monkey)">
<scg:KeyValuePair x:TypeArguments="x:String,models:Monkey">
<x:Arguments>
<x:String>Baboon</x:String>
ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_an
ubis_%28Serengeti%2C_2009%29.jpg/200px-
Papio_anubis_%28Serengeti%2C_2009%29.jpg" />
</x:Arguments>
</scg:KeyValuePair>
<scg:KeyValuePair x:TypeArguments="x:String,models:Monkey">
<x:Arguments>
<x:String>Capuchin Monkey</x:String>
ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin
_Costa_Rica.jpg/200px-Capuchin_Costa_Rica.jpg" />
</x:Arguments>
</scg:KeyValuePair>
<scg:KeyValuePair x:TypeArguments="x:String,models:Monkey">
<x:Arguments>
<x:String>Blue Monkey</x:String>
<models:Monkey Location="Central and East Africa"
ImageUrl="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonk
ey.jpg/220px-BlueMonkey.jpg" />
</x:Arguments>
</scg:KeyValuePair>
</scg:List>
</CollectionView.ItemsSource>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding Value.ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Key}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Value.Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage
.NET Multi-platform App UI (.NET MAUI) XAML markup extensions help enhance the
power and flexibility of XAML by allowing element attributes to be set from a variety of
sources.
For example, you typically set the Color property of BoxView like this:
XAML
However, you might prefer instead to set the Color attribute from a value stored in a
resource dictionary, or from the value of a static property of a class that you've created,
or from a property of type Color of another element on the page, or constructed from
separate hue, saturation, and luminosity values. All these options are possible using
XAML markup extensions.
XAML
Any attribute value in curly braces is always a XAML markup extension. However, XAML
markup extensions can also be referenced without the use of curly braces.
7 Note
Several XAML markup extensions are part of the XAML 2009 specification. These
appear in XAML files with the customary x namespace prefix, and are commonly
referred to with this prefix.
In addition to the markup extensions discussed in this article, the following markup
extensions are included in .NET MAUI and discussed in other articles:
StaticResource - reference objects from a resource dictionary. For more
information, see Resource dictionaries.
DynamicResource - respond to changes in objects in a resource dictionary. For
more information, see Dynamic styles.
Binding - establish a link between properties of two objects. For more information,
see Data binding.
TemplateBinding - performs data binding from a control template. For more
information, see Control templates.
RelativeSource - sets the binding source relative to the position of the binding
target. For more information, see Relative bindings.
One way to use x:Static is to first define a class with some constants or static variables,
such as this AppConstants class:
C#
The following XAML demonstrates the most verbose approach to instantiating the
StaticExtension class between Label.FontSize property-element tags:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
xmlns:local="clr-namespace:MarkupExtensions"
x:Class="MarkupExtensions.StaticDemoPage"
Title="x:Static Demo">
<Label.FontSize>
<x:StaticExtension
Member="local:AppConstants.NormalFontSize" />
</Label.FontSize>
</Label>
···
</StackLayout>
</ContentPage>
The XAML parser also allows the StaticExtension class to be abbreviated as x:Static :
XAML
<Label.FontSize>
</Label.FontSize>
</Label>
This syntax can be simplified even further by putting the StaticExtension class and the
member setting in curly braces. The resulting expression is set directly to the FontSize
attribute:
XAML
FontSize="{x:StaticExtension
Member=local:AppConstants.NormalFontSize}" />
In this example, there are no quotation marks within the curly braces. The Member
property of StaticExtension is no longer an XML attribute. It is instead part of the
expression for the markup extension.
Just as you can abbreviate x:StaticExtension to x:Static when you use it as an object
element, you can also abbreviate it in the expression within curly braces:
XAML
XAML
The root tag of the XAML example also contains an XML namespace declaration for the
.NET System namespace. This allows the Label font size to be set to the static field
Math.PI . That results in rather small text, so the Scale property is set to Math.E :
XAML
FontSize="{x:Static sys:Math.PI}"
Scale="{x:Static sys:Math.E}"
HorizontalOptions="Center" />
The following XAML example shows two uses of x:Reference with data bindings, the
first where it's used to set the Source property of the Binding object, and the second
where it's used to set the BindingContext property for two data bindings:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.ReferenceDemoPage"
x:Name="page"
Title="x:Reference Demo">
FontSize="18"
VerticalOptions="Center"
HorizontalTextAlignment="Center" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="Center" />
Rotation="{Binding Value}"
FontSize="24"
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
In this example, both x:Reference expressions use the abbreviated version of the
ReferenceExtension class name and eliminate the Name= part of the expression. In the
first example, the x:Reference markup extension is embedded in the Binding markup
extension and the Source and StringFormat properties are separated by commas.
returns the Type object of that class or structure. TypeName is the content property of
TypeExtension, so TypeName= is not required when x:Type appears with curly braces.
The x:Type markup extension is commonly used with the x:Array markup extension.
For more information, see x:Array markup extension.
The following XAML example demonstrates using the x:Type markup extension to
instantiate .NET MAUI objects and add them to a StackLayout. The XAML consists of
three Button elements with their Command properties set to a Binding and the
CommandParameter properties set to types of three .NET MAUI views:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.TypeDemoPage"
Title="x:Type Demo">
<StackLayout x:Name="stackLayout"
Padding="10, 0">
HorizontalOptions="Center"
VerticalOptions="Center"
Command="{Binding CreateCommand}"
HorizontalOptions="Center"
VerticalOptions="Center"
Command="{Binding CreateCommand}"
HorizontalOptions="Center"
VerticalOptions="Center"
Command="{Binding CreateCommand}"
</StackLayout>
</ContentPage>
C#
public TypeDemoPage()
InitializeComponent();
view.VerticalOptions = LayoutOptions.Center;
stackLayout.Add(view);
});
BindingContext = this;
Type of type Type , which indicates the type of the elements in the array. This
The x:Array markup extension itself never appears in curly braces. Instead, x:Array
start and end tags delimit the list of items.
The following XAML example shows how to use x:Array to add items to a ListView by
setting the ItemsSource property to an array:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.ArrayDemoPage"
<ListView Margin="10">
<ListView.ItemsSource>
<Color>Aqua</Color>
<Color>Black</Color>
<Color>Blue</Color>
<Color>Fuchsia</Color>
<Color>Gray</Color>
<Color>Green</Color>
<Color>Lime</Color>
<Color>Maroon</Color>
<Color>Navy</Color>
<Color>Olive</Color>
<Color>Pink</Color>
<Color>Purple</Color>
<Color>Red</Color>
<Color>Silver</Color>
<Color>Teal</Color>
<Color>White</Color>
<Color>Yellow</Color>
</x:Array>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<BoxView Color="{Binding}"
Margin="3" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
In this example, the ViewCell creates a simple BoxView for each color entry:
7 Note
When defining arrays of common types like strings or numbers, use the XAML
language primitives tags listed in Pass arguments.
The following XAML example shows how to use the x:Null markup extension:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.NullDemoPage"
Title="x:Null Demo">
<ContentPage.Resources>
<Style TargetType="Label">
</Style>
</ContentPage.Resources>
FontFamily="{x:Null}" />
</StackLayout>
</ContentPage>
In this example, an implicit Style is defined for Label that includes a Setter that sets the
FontFamily property to a specific font. However, the third Label avoids using the font
defined in the implicit style by setting its FontFamily to x:Null :
Default of type object , that you set to a default value to be applied to the
properties that represent platforms.
Android of type object , that you set to a value to be applied on Android.
Tizen of type object , that you set to a value to be applied on the Tizen platform.
WinUI of type object , that you set to a value to be applied on WinUI.
implementation.
ConverterParameter of type object , that can be set to a value to pass to the
IValueConverter implementation.
7 Note
) Important
The XAML parser expects that values of the correct type will be provided to
properties consuming the OnPlatform markup extension. If type conversion is
necessary, the OnPlatform markup extension will attempt to perform it using the
default converters provided by .NET MAUI. However, there are some type
conversions that can't be performed by the default converters and in these cases
the Converter property should be set to an IValueConverter implementation.
The OnPlatform Demo page shows how to use the OnPlatform markup extension:
XAML
HorizontalOptions="Center" />
In this example, all three OnPlatform expressions use the abbreviated version of the
OnPlatformExtension class name. The three OnPlatform markup extensions set the
xref:Microsoft.Maui.Graphics.Color , WidthRequest, and HeightRequest properties of
the BoxView to different values on iOS and Android. The markup extensions also
provide default values for these properties on the platforms that aren't specified, while
eliminating the Default= part of the expression.
Default of type object , that you set to a default value to be applied to the
properties that represent device idioms.
Phone of type object , that you set to a value to be applied on phones.
Tablet of type object , that you set to a value to be applied on tablets.
platforms.
TV of type object , that you set to a value to be applied on TV platforms.
Watch of type object , that you set to a value to be applied on Watch platforms.
Converter of type IValueConverter, that can be set to an IValueConverter
implementation.
ConverterParameter of type object , that can be set to a value to pass to the
IValueConverter implementation.
7 Note
The Default property is the content property of OnIdiomExtension. Therefore, for XAML
markup expressions expressed with curly braces, you can eliminate the Default= part of
the expression provided that it's the first argument.
) Important
The XAML parser expects that values of the correct type will be provided to
properties consuming the OnIdiom markup extension. If type conversion is
necessary, the OnIdiom markup extension will attempt to perform it using the
default converters provided by .NET MAUI. However, there are some type
conversions that can't be performed by the default converters and in these cases
the Converter property should be set to an IValueConverter implementation.
The following XAML example shows how to use the OnIdiom markup extension:
XAML
HorizontalOptions="Center" />
In this example, all three OnIdiom expressions use the abbreviated version of the
OnIdiomExtension class name. The three OnIdiom markup extensions set the Color ,
WidthRequest, and HeightRequest properties of the BoxView to different values on the
phone, tablet, and desktop idioms. The markup extensions also provide default values
for these properties on the idioms that aren't specified, while eliminating the Default=
part of the expression.
7 Note
XAML
<ShellContent Title="Monkeys"
Icon="monkey.png"
FontFamily of type string , the font family to which the font icon belongs.
Glyph of type string , the unicode character value of the font icon.
Color of type Color, the color to be used when displaying the font icon.
Size of type double , the size, in device-independent units, of the rendered font
icon. The default value is 30. In addition, this property can be set to a named font
size.
7 Note
The Glyph property is the content property of FontImageExtension. Therefore, for XAML
markup expressions expressed with curly braces, you can eliminate the Glyph= part of
the expression provided that it's the first argument.
The following XAML example shows how to use the FontImage markup extension:
XAML
<Image BackgroundColor="#D1D1D1"
In this example, the abbreviated version of the FontImageExtension class name is used
to display an XBox icon, from the Ionicons font family, in an Image:
While the unicode character for the icon is \uf30c , it has to be escaped in XAML and so
becomes  .
For information about displaying font icons by specifying the font icon data in a
FontImageSource object, see Display font icons.
Default , of type object , that you set to the resource to be used by default.
Light , of type object , that you set to the resource to be used when the device is
using its light theme.
Dark , of type object , that you set to the resource to be used when the device is
using its dark theme.
Value , of type object , that returns the resource that's currently being used by the
markup extension.
7 Note
The following XAML example shows how to use the AppThemeBinding markup extension:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MarkupExtensions.AppThemeBindingDemoPage"
Title="AppThemeBinding Demo">
<ContentPage.Resources>
<Style x:Key="labelStyle"
TargetType="Label">
<Setter Property="TextColor"
</Style>
</ContentPage.Resources>
<StackLayout Margin="20">
</StackLayout>
</ContentPage>
In this example, the text color of the first Label is set to green when the device is using
its light theme, and is set to red when the device is using its dark theme. The second
Label has its TextColor property set through a Style. This Style sets the text color of the
Label to black by default, to blue when the device is using its light theme, and to teal
when the device is using its dark theme:
Create XAML markup extensions
Article • 04/03/2023 • 3 minutes to read
At the developer level, a .NET Multi-platform App UI (.NET MAUI) XAML markup
extension is a class that implements the IMarkupExtension or IMarkupExtension<T>
interface. It's also possible to define your own custom XAML markup extensions by
deriving from IMarkupExtension or IMarkupExtension<T> . Use the generic form if the
markup extension obtains a value of a particular type. This is the case with several of the
.NET MAUI markup extensions:
The two IMarkupExtension interfaces define only one method each, named
ProvideValue :
C#
Often, XAML markup extensions define properties that contribute to the return value,
and the ProvideValue method has a single argument of type IServiceProvider . For
more information about service providers, see Service providers.
C#
return (this as
IMarkupExtension<Color>).ProvideValue(serviceProvider);
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MarkupExtensions"
x:Class="MarkupExtensions.HslColorDemoPage"
<ContentPage.Resources>
<Style TargetType="BoxView">
</Style>
</ContentPage.Resources>
<StackLayout>
<BoxView>
<BoxView.Color>
</BoxView.Color>
</BoxView>
<BoxView>
<BoxView.Color>
</BoxView.Color>
</BoxView>
</StackLayout>
</ContentPage>
In this example, when HslColorExtension is an XML tag the four properties are set as
attributes, but when it appears between curly braces, the four properties are separated
by commas without quotation marks. The default values for H , S , and L are 0, and the
default value of A is 1, so those properties can be omitted if you want them set to
default values. The last example shows an example where the luminosity is 0, which
normally results in black, but the alpha channel is 0.5, so it is half transparent and
appears gray against the white background of the page:
Service providers
By using the IServiceProvider argument to ProvideValue , XAML markup extensions can
get access to data about the XAML file in which they're being used. For example, the
IProvideValueTarget service enables you to retrieve data about the object the markup
C#
IProvideValueTarget provideValueTarget =
serviceProvider.GetService(typeof(IProvideValueTarget)) as
IProvideValueTarget;
TargetObject is the BoxView and TargetProperty is the Color property of BoxView. This
is the property on which the XAML markup extension has been set.
XAML namespaces
Article • 04/03/2023 • 3 minutes to read
XAML uses the xmlns XML attribute for namespace declarations. There are two XAML
namespace declarations that are always within the root element of a XAML file. The first
defines the default namespace:
XAML
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
The default namespace specifies that elements defined within the XAML file with no
prefix refer to .NET Multi-platform App UI (.NET MAUI) classes, such as ContentPage,
Label, and Button.
XAML
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
XAML uses prefixes to declare non-default namespaces, with the prefix being used when
referencing types within the namespace. The x namespace declaration specifies that
elements defined within XAML with a prefix of x are used for elements and attributes
that are intrinsic to XAML (specifically the 2009 XAML specification).
Construct Description
x:Class Specifies the namespace and class name for a class defined in XAML. The class
name must match the class name of the code-behind file. Note that this
construct can only appear in the root element of a XAML file.
x:DataType Specifies the type of the object that the XAML element, and it's children, will
bind to.
x:FieldModifier Specifies the access level for generated fields for named XAML elements.
Construct Description
x:Name Specifies a runtime object name for the XAML element. Setting x:Name is
similar to declaring a variable in code.
x:TypeArguments Specifies the generic type arguments to the constructor of a generic type.
For more information about the x:DataType attribute, see Compiled bindings. For more
information about the x:FieldModifier attribute, see Field modifiers. For more
information about the x:Arguments and x:FactoryMethod attributes, see Pass arguments.
For more information about the x:TypeArguments attribute, see Generics.
7 Note
In addition to the constructs listed above, .NET MAUI also includes markup
extensions that can be consumed through the x namespace prefix. For more
information, see Consume XAML Markup Extensions.
clr-namespace: or using: – the CLR namespace declared within the assembly that
is the name of the assembly, without the file extension. The path to the assembly
should be established as a reference in the project that contains the XAML file that
will reference the assembly. This keyword can be omitted if the clr-namespace
value is within the same assembly as the app code that's referencing the types.
7 Note
The character separating the clr-namespace or using token from its value is a
colon, whereas the character separating the assembly token from its value is an
equal sign. The character to use between the two tokens is a semicolon.
XAML
...
</ContentPage>
XAML
...
</ContentPage>
The local prefix is a convention used to indicate that the types within the namespace
are local to the app. Alternatively, if the types are in a different assembly, the assembly
name should also be defined in the namespace declaration:
XAML
...
</ContentPage>
The namespace prefix is then specified when declaring an instance of a type from an
imported namespace:
XAML
<controls:Expander IsExpanded="True">
...
</controls:Expander>
For information about defining a custom namespace schema, see Custom namespace
schemas.
Custom namespace schemas
Article • 02/15/2023 • 3 minutes to read
Types in a .NET Multi-platform App UI (.NET MAUI) library can be referenced in XAML by
declaring an XML namespace for the library, with the namespace declaration specifying
the Common Language Runtime (CLR) namespace name and an assembly name:
XAML
<ContentPage ...
xmlns:controls="clr-
namespace:MyCompany.Controls;assembly=MyCompany.Controls">
...
</ContentPage>
However, specifying a CLR namespace and assembly name in a xmlns definition can be
awkward and error prone. In addition, multiple XML namespace declarations may be
required if the library contains types in multiple namespaces.
C#
namespace MyCompany.Controls
...
All the controls in the library reside in the MyCompany.Controls namespace. These
controls can be exposed to a calling assembly through a custom namespace schema.
7 Note
C#
using MyCompany.Controls;
[assembly: Preserve]
[assembly: XmlnsDefinition("http://mycompany.com/schemas/controls",
"MyCompany.Controls")]
) Important
The Preserve attribute should be applied to classes in the assembly that are
mapped through the custom namespace schema, or applied to the entire assembly.
The custom namespace schema can then be used for type resolution in XAML files.
Consume a custom namespace schema
To consume types from the custom namespace schema, the XAML compiler requires
that there's a code reference from the assembly that consumes the types, to the
assembly that defines the types. This can be accomplished by adding a class containing
an Init method to the assembly that defines the types that will be consumed through
XAML:
C#
namespace MyCompany.Controls
The Init method can then be called from the assembly that consumes types from the
custom namespace schema:
C#
using MyCompany.Controls;
namespace CustomNamespaceSchemaDemo
public MainPage()
Controls.Init();
InitializeComponent();
2 Warning
Failure to include such a code reference will result in the XAML compiler being
unable to locate the assembly containing the custom namespace schema types.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="http://mycompany.com/schemas/controls"
x:Class="CustomNamespaceSchemaDemo.MainPage">
<StackLayout>
...
<controls:CircleButton Text="+"
BackgroundColor="Fuchsia"
BorderColor="Black"
CircleDiameter="100" />
<controls:CircleButton Text="-"
BackgroundColor="Teal"
BorderColor="Silver"
CircleDiameter="70" />
...
</StackLayout>
</ContentPage>
CircleButton instances can then be added to the ContentPage by declaring them with
the controls namespace prefix.
To find the custom namespace schema types, .NET MAUI will search referenced
assemblies for XmlnsDefinitionAttribute instances. If the xmlns attribute for an element
in a XAML file matches the XmlNamespace property value in a XmlnsDefinitionAttribute ,
.NET MAUI will attempt to use the XmlnsDefinitionAttribute.ClrNamespace property
value for resolution of the type. If type resolution fails, .NET MAUI will continue to
attempt type resolution based on any additional matching XmlnsDefinitionAttribute
instances.
The NET Multi-platform App UI (.NET MAUI) XmlnsPrefixAttribute class can be used by
control authors to specify a recommended prefix to associate with a XAML namespace,
for XAML usage. The prefix is useful when supporting object tree serialization to XAML,
or when interacting with a design environment that has XAML editing features. For
example:
XAML text editors could use the XmlnsPrefixAttribute as a hint for an initial XAML
namespace xmlns mapping.
XAML design environments could use the XmlnsPrefixAttribute to add mappings
to the XAML when dragging objects out of a toolbox and onto a visual design
surface.
Recommended namespace prefixes should be defined at the assembly level with the
XmlnsPrefixAttribute constructor, which takes two arguments: a string that specifies
the identifier of a XAML namespace, and a string that specifies a recommended prefix:
C#
[assembly: XmlnsPrefix("http://schemas.microsoft.com/dotnet/2021/maui",
"maui")]
Prefixes should use short strings, because the prefix is typically applied to all serialized
elements that come from the XAML namespace. Therefore, the prefix string length can
have a noticeable effect on the size of the serialized XAML output.
7 Note
It's often necessary to instantiate objects with constructors that require arguments, or by
calling a static creation method. This can be achieved in .NET Multi-platform App UI
(.NET MAUI) XAML by using the x:Arguments and x:FactoryMethod attributes:
In addition, the x:TypeArguments attribute can be used to specify the generic type
arguments to the constructor of a generic type. For more information, see Specify a
generic type argument.
Arguments can be passed to constructors and factory methods using the following .NET
MAUI XAML language primitives:
With the exception of x:DateTime , the other language primitives are in the XAML 2009
specification.
7 Note
The x:Single language primitive can be used to pass float arguments.
The following example demonstrates using the x:Arguments attribute with three
different Color constructors:
XAML
<BoxView HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center">
<BoxView.Color>
<Color>
<x:Arguments>
<x:Single>0.9</x:Single>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
<BoxView HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center">
<BoxView.Color>
<Color>
<x:Arguments>
<x:Single>0.25</x:Single>
<x:Single>0.5</x:Single>
<x:Single>0.75</x:Single>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
<BoxView HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center">
<BoxView.Color>
<Color>
<x:Arguments>
<x:Single>0.8</x:Single>
<x:Single>0.5</x:Single>
<x:Single>0.2</x:Single>
<x:Single>0.5</x:Single>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
The number of elements within the x:Arguments tag, and the types of these elements,
must match one of the Color constructors. The Color constructor with a single
parameter requires a grayscale float value from 0 (black) to 1 (white). The Color
constructor with three parameters requires float red, green, and blue values ranging
from 0 to 1. The Color constructor with four parameters adds a float alpha channel as
the fourth parameter.
The Color class defines a number of factory methods, and the following example
demonstrates calling three of them:
XAML
<BoxView HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center">
<BoxView.Color>
<Color x:FactoryMethod="FromRgba">
<x:Arguments>
<x:Byte>192</x:Byte>
<x:Byte>75</x:Byte>
<x:Byte>150</x:Byte>
<x:Byte>128</x:Byte>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
<BoxView HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center">
<BoxView.Color>
<Color x:FactoryMethod="FromHsla">
<x:Arguments>
<x:Double>0.23</x:Double>
<x:Double>0.42</x:Double>
<x:Double>0.69</x:Double>
<x:Double>0.7</x:Double>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
<BoxView HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center">
<BoxView.Color>
<Color x:FactoryMethod="FromHex">
<x:Arguments>
<x:String>#FF048B9A</x:String>
</x:Arguments>
</Color>
</BoxView.Color>
</BoxView>
The number of elements within the x:Arguments tag, and the types of these elements,
must match the arguments of the factory method being called. The FromRgba factory
method requires four byte arguments, which represent the red, green, blue, and alpha
values, ranging from 0 to 255 respectively. The FromHsla factory method requires four
float arguments, which represent the hue, saturation, luminosity, and alpha values,
XAML
<StackLayout>
<StackLayout.Margin>
<OnPlatform x:TypeArguments="Thickness">
</OnPlatform>
</StackLayout.Margin>
</StackLayout>
attribute can accept a single string value, or multiple comma-delimited string values.
In this example, the StackLayout.Margin property is set to a platform-specific Thickness .
For more information about generic type arguments, see Generics in XAML.
Load XAML at runtime
Article • 12/23/2022 • 2 minutes to read
C#
public MainPage()
InitializeComponent();
When Visual Studio builds a project containing a XAML file, a source generator
generates new C# source that contains the definition of the InitializeComponent
method and adds it to the compilation object. The following example shows the
generated InitializeComponent method for the MainPage class:
C#
global::Microsoft.Maui.Controls.Xaml.Extensions.LoadFromXaml(this,
typeof(MainPage));
...
The InitializeComponent method calls the LoadFromXaml method to extract the XAML
compiled binary (or its file) from the app package. After extraction, it initializes all of the
objects defined in the XAML, connects them all together in parent-child relationships,
attaches event handlers defined in code to events set in the XAML file, and sets the
resultant tree of objects as the content of the page.
2 Warning
Loading XAML at runtime has a significant performance cost, and generally should
be avoided.
C#
...
stackLayout.Add(navigationButton);
In this example, a Button instance is created, with its Text property value being set from
the XAML defined in the string . The Button is then added to a StackLayout that has
been defined in the XAML for the page.
7 Note
The LoadFromXaml method can be used to inflate any XAML, with the following example
inflating a ContentPage and then navigating to it:
C#
await Navigation.PushAsync(page);
Access elements
Loading XAML at runtime with the LoadFromXaml method does not allow strongly-typed
access to the XAML elements that have specified runtime object names (using x:Name ).
However, these XAML elements can be retrieved using the FindByName method, and
then accessed as required:
C#
In this example, the XAML for a ContentPage is inflated. This XAML includes a Label
named monkeyName , which is retrieved using the FindByName method, before its Text
property is set.
XAML Hot Reload for .NET MAUI
Article • 10/25/2022 • 4 minutes to read
.NET Multi-platform App UI (.NET MAUI) XAML Hot Reload is a Visual Studio feature that
enables you to view the result of XAML changes in your running app, without having to
rebuild your project. Without XAML Hot Reload, you have to build and deploy your app
every time you want to view the result of a XAML change.
When your .NET MAUI app is running in debug configuration, with the debugger
attached, XAML Hot Reload parses your XAML edits and sends those changes to the
running app. It preserves your UI state, since it doesn't recreate the UI for the full page,
and updates changed properties on controls affected by edits. In addition, your navigate
state and data will be maintained, enabling you to quickly iterate on your UI without
losing your location in the app. Therefore, you'll spend less time rebuilding and
deploying your apps to validate UI changes.
By default, you don't need to save your XAML file to see the results of your edits.
Instead, updates are applied immediately as you type. However, on Windows you can
change this behavior to update only on file save. This can be accomplished by checking
the Apply XAML Hot Reload on document save checkbox in the Hot Reload IDE
settings available by selecting Debug > Options> XAML Hot Reload from the Visual
Studio menu bar. Only updating on file save can sometimes be useful if you make
bigger XAML updates and don't wish them to be displayed until they are complete.
7 Note
If you're writing a native UWP or WPF app, without using .NET MAUI, see What is
XAML Hot Reload for WPF and UWP apps?.
XAML Hot Reload is available in both Visual Studio 2022 and Visual Studio 2022 for Mac.
On Windows, XAML Hot Reload is available on Android, iOS, and WinUI on emulators,
simulators, and physical devices. On Mac, XAML Hot Reload is available on Android, iOS,
and Mac Catalyst on emulators, simulators, and physical devices.
) Important
XAML Hot Reload is enabled by default in Visual Studio 2022. If it's been previously
disabled, it can be enabled by selecting Debug > Options > XAML Hot Reload
from the Visual Studio menu bar. Next, in the Options dialog box, ensure that the
Enable XAML Hot Reload, WinUI (including .NET MAUI), and Android and iOS
(.NET MAUI) options are checked:
Then, on iOS in your build settings, check that the Linker is set to "Don't Link".
Troubleshooting
The XAML Hot Reload output displays status messages that can help with
troubleshooting. In Visual Studio, these can be displayed by selecting View > Output
from the menu bar, and then selecting Xamarin Hot Reload in the Show output from:
drop-down. In Visual Studio for Mac, these can be displayed by hovering your mouse
cursor over XAML Hot Reload in the status bar.
If XAML Hot Reload fails to initialize you should ensure that you're using the latest
version of .NET MAUI, the latest version of the IDE, and that your iOS linker settings are
set to Don't Link in the project's build settings.
If nothing happens when saving your XAML file, ensure that XAML Hot Reload is
enabled in the IDE. For more information, see Enable XAML Hot Reload.
If you make a change that the XAML Hot Reload parser sees as invalid, it will show the
error underlined in the editor and include it in the Error List window. Hot Reload errors
have an error code starting with "XHR" (for XAML Hot Reload). If there are any such
errors on the page, XAML Hot Reload won't apply changes to your running app until the
errors have been fixed.
You can't add, remove, or rename files or NuGet packages during a XAML Hot Reload
session. If you add or remove a file or NuGet package, rebuild and redeploy your app to
continue using XAML Hot Reload.
Semantics for accessibility is concerned with building experiences that make your apps
inclusive for people who use technology in a wide range of environments and approach
your UI with a range of needs and experiences. In many situations, legal requirements
for accessibility may provide an impetus for developers to address accessibility issues.
Regardless, it's advisable to build inclusive and accessible apps so that your apps reach
the largest possible audience.
The Web Content Accessibility Guidelines (WCAG) are the global accessibility standard
and legal benchmark for web and mobile. These guidelines describe the various ways in
which apps can be made more perceivable, operable, understandable, and robust, for
all.
Many user accessibility needs are met by assistive technology products installed by the
user or by tools and settings provided by the operating system. This includes
functionality such as screen readers, screen magnification, and high-contrast settings.
Screen readers typically provide auditory descriptions of controls that are displayed on
the screen. These descriptions help users navigate through the app and provide
references to controls, such as images, that have no input or text. Screen readers are
often controlled through gestures on the touchscreen, trackpad, or keyboard. For
information about enabling screen readers, see Enable screen readers.
Operating systems have their own screen readers with their own unique behavior and
configuration. For example, most screen readers read the text associated with a control
when it receives focus, enabling users to orient themselves as they navigate through the
app. However, some screen readers can also read the entire app user interface when a
page appears, which enables the user to receive all of the page's available informational
content before attempting to navigate it.
Most screen readers will automatically read any text associated with a control that
receives accessibility focus. This means that controls, such as Label or Button, that have
a Text property set will be accessible for the user. However, Image, ImageButton,
ActivityIndicator, and others might not be in the accessibility tree because no text is
associated with them.
.NET Multi-platform App UI (.NET MAUI) supports two approaches to providing access
to the accessibility experience of the underlying platform. Semantic properties are the
.NET MAUI approach to providing accessibility values in apps, and are the
recommended approach. Automation properties are the Xamarin.Forms approach to
providing accessibility values in apps, and have been superseded by semantic
properties. In both cases, the default accessibility order of controls is the same order in
which they're listed in XAML or added to the layout. However, different layouts might
have additional factors that influence accessibility order. For example, the accessibility
order of StackLayout is also based on its orientation, and the accessibility order of Grid
is based on its row and column arrangement. For more information about content
ordering, see Meaningful Content Ordering on the Xamarin blog.
7 Note
Semantic properties
Semantic properties are used to define information about which controls should receive
accessibility focus and which text should be read aloud to the user. Semantic properties
are attached properties that can be added to any element to set the underlying platform
accessibility APIs.
) Important
Description, of type string , which represents a description that will be read aloud
by the screen reader. For more information, see Description.
Hint, of type string , which is similar to Description , but provides additional
context such as the purpose of a control. For more information, see Hint.
HeadingLevel, of type SemanticHeadingLevel, which enables an element to be
marked as a heading to organize the UI and make it easier to navigate. For more
information, see Heading levels.
These attached properties set platform accessibility values so that a screen reader can
speak about the element. For more information about attached properties, see Attached
properties.
Description
The Description attached property represents a short, descriptive string that a screen
reader uses to announce an element. This property should be set for elements that have
a meaning that's important for understanding the content or interacting with the user
interface. Setting this property can be accomplished in XAML:
XAML
<Image Source="dotnet_bot.png"
C#
In addition, the SetValue method can also be used to set the Description attached
property:
C#
2 Warning
Avoid setting the Description attached property on a Label. This will stop the Text
property being spoken by the screen reader. This is because the visual text should
ideally match the text read aloud by the screen reader.
The accessibility information for an element can also be defined on another element. For
example, a Label next to an Entry can be used to describe what the Entry represents.
This can be accomplished in XAML as follows:
XAML
<Label x:Name="label"
C#
};
SemanticProperties.SetDescription(entry, label.Text);
2 Warning
On iOS, if you set the Description property on any control that has children the
screen reader will be unable to reach the children. This is because iOS doesn't
provide accessibility features that allow the navigation from a parent element into a
child element.
Hint
The Hint attached property represents a string that provides additional context to the
Description attached property, such as the purpose of a control. Setting this property
can be accomplished in XAML:
XAML
<Image Source="like.png"
SemanticProperties.Description="Like"
C#
SemanticProperties.SetDescription(image, "Like");
In addition, the SetValue method can also be used to set the Hint attached property:
C#
On Android, this property behaves slightly differently depending on the control it's
attached to. For example, for controls without text values, such as Switch and CheckBox,
the controls will display the hint with the control. However, for controls with text values,
the hint is not displayed and is read after the text value.
2 Warning
The Hint property conflicts with the Entry.Placeholder property on Android, which
both map to the same platform property. Therefore, setting a different Hint value
to the Entry.Placeholder value isn't recommended.
Heading levels
The HeadingLevel attached property enables an element to be marked as a heading to
organize the UI and make it easier to navigate. Some screen readers enable users to
quickly jump between headings.
) Important
While Windows offers 9 levels of headings, Android and iOS only offer a single
heading. Therefore, when HeadingLevel is set on Windows it maps to the correct
heading level. However, when set on Android and iOS it maps to a single heading
level.
XAML
SemanticProperties.HeadingLevel="Level1" />
<Label Text="Installation"
SemanticProperties.HeadingLevel="Level2" />
SemanticProperties.HeadingLevel="Level3" />
SemanticProperties.HeadingLevel="Level4" />
C#
Label label1 = new Label { Text = "Get started with .NET MAUI" };
SemanticProperties.SetHeadingLevel(label1, SemanticHeadingLevel.Level1);
SemanticProperties.SetHeadingLevel(label3, SemanticHeadingLevel.Level1);
SemanticProperties.SetHeadingLevel(label5, SemanticHeadingLevel.Level1);
SemanticProperties.SetHeadingLevel(label7, SemanticHeadingLevel.Level1);
In addition, the SetValue method can also be used to set the HeadingLevel attached
property:
C#
label1.SetValue(SemanticProperties.HeadingLevelProperty,
SemanticHeadingLevel.Level1);
Semantic focus
Controls have a SetSemanticFocus extension method which forces screen reader focus
to a specified element. For example, given a Label named label , screen reader focus
can be forced to the element with the following code:
C#
label.SetSemanticFocus();
To instruct a screen reader to announce text, use the Announce method, passing a
string argument that represents the text. The following example demonstrates using
this method:
C#
Limitations
The default platform screen reader must be enabled for text to be read aloud.
Automation properties
Automation properties are attached properties that can be added to any element to
indicate how the element is reported to the underlying platform's accessibility
framework.
These attached properties set platform accessibility values so that a screen reader can
speak about the element. For more information about attached properties, see Attached
properties.
Different screen readers read different accessibility values. Therefore, when using
automation properties it's recommended that thorough accessibility testing is carried
out on each platform to ensure an optimal experience.
) Important
ExcludedWithChildren
The ExcludedWithChildren attached property, of type bool? , determines if an element
and its children should be excluded from the accessibility tree. This enables scenarios
such as displaying an AbsoluteLayout over another layout such as a StackLayout, with
the StackLayout being excluded from the accessibility tree when it's not visible. It can be
used from XAML as follows:
XAML
<StackLayout AutomationProperties.ExcludedWithChildren="true">
...
</StackLayout>
C#
...
AutomationProperties.SetExcludedWithChildren(stackLayout, true);
When this attached property is set, .NET MAUI sets the IsInAccessibleTree attached
property to false on the specified element and its children.
IsInAccessibleTree
2 Warning
This attached property should typically remain unset. The majority of controls
should be present in the accessibility tree, and the
AutomationProperties.ExcludedWithChildren attached property can be set in
scenarios where an element and its children need removing from the accessibility
tree.
XAML
C#
AutomationProperties.SetIsInAccessibleTree(entry, true);
2 Warning
On iOS, if the IsInAccessibleTree property is true on any control that has children
the screen reader will be unable to reach the children. This is because iOS doesn't
provide accessibility features that allow the navigation from a parent element into a
child element.
Name
) Important
The Name attached property has been superseded by the Description attached
property.
The Name attached property value should be a short, descriptive text string that a
screen reader uses to announce an element. This property should be set for elements
that have a meaning that is important for understanding the content or interacting with
the user interface. This can be accomplished in XAML as follows:
XAML
<ActivityIndicator AutomationProperties.IsInAccessibleTree="true"
C#
AutomationProperties.SetIsInAccessibleTree(activityIndicator, true);
HelpText
) Important
The HelpText attached property has been superseded by the Hint attached
property.
The HelpText attached property should be set to text that describes the user interface
element, and can be thought of as tooltip text associated with the element. This can be
accomplished in XAML as follows:
XAML
AutomationProperties.IsInAccessibleTree="true"
C#
AutomationProperties.SetIsInAccessibleTree(button, true);
On some platforms, for edit controls such as an Entry, the HelpText property can
sometimes be omitted and replaced with placeholder text. For example, "Enter your
name here" is a good candidate for the Entry.Placeholder property that places the text
in the control prior to the user's actual input.
LabeledBy
) Important
The LabeledBy attached property has been superseded by bindings. For more
information, see SemanticProperties: Description.
XAML
<Entry AutomationProperties.IsInAccessibleTree="true"
C#
AutomationProperties.SetIsInAccessibleTree(entry, true);
AutomationProperties.SetLabeledBy(entry, label);
) Important
Testing accessibility
.NET MAUI apps typically target multiple platforms, which means testing the accessibility
features according to the platform. Follow these links to learn how to test accessibility
on each platform:
However, none of these tools can perfectly emulate the screen reader user experience,
and the best way to test and troubleshoot your apps for accessibility will always be
manually on physical devices with screen readers.
Android has TalkBack. For information on enabling TalkBack, see Enable TalkBack.
iOS and macOS have VoiceOver. For information on enabling VoiceOver, see
Enable VoiceOver.
Windows has Narrator. For information on enabling Narrator, see Enable Narrator.
Enable TalkBack
TalkBack is the primary screen reader used on Android. How it's enabled depends on the
device manufacturer, Android version, and TalkBack version. However, TalkBack can
typically be enabled on your Android device via the device settings:
7 Note
While these steps apply to most devices, you might experience some differences.
A TalkBack tutorial opens automatically the first time you enable TalkBack.
Enable VoiceOver
VoiceOver is the primary screen reader used on iOS and macOS. On iOS, VoiceOver can
be enabled as follows:
For alternative methods of enabling VoiceOver, see Turn on and practice VoiceOver on
iPhone and Turn on and practice VoiceOver on iPad .
For alternative methods of enabling VoiceOver, see Turn VoiceOver on or off on Mac .
Enable Narrator
Narrator is the primary screen reader used on Windows. Narrator can be enabled by
pressing the Windows logo key + Ctrl + Enter together. These keys can be pressed
again to stop Narrator.
Accessibility checklist
Follow these tips to ensure that your .NET MAUI apps are accessible to the widest
audience possible:
" Ensure your app is perceivable, operable, understandable, and robust for all by
following the Web Content Accessibility Guidelines (WCAG). WCAG is the global
accessibility standard and legal benchmark for web and mobile. For more
information, see Web Content Accessibility Guidelines (WCAG) Overview .
" Make sure the user interface is self-describing. Test that all the elements of your
user interface are screen reader accessible. Add descriptive text and hints when
necessary.
" Ensure that images and icons have alternate text descriptions.
" Support large fonts and high contrast. Avoid hardcoding control dimensions, and
instead prefer layouts that resize to accommodate larger font sizes. Test color
schemes in high-contrast mode to ensure they are readable.
" Design the visual tree with navigation in mind. Use appropriate layout controls so
that navigating between controls using alternate input methods follows the same
logical flow as using touch. In addition, exclude unnecessary elements from screen
readers (for example, decorative images or labels for fields that are already
accessible).
" Don't rely on audio or color cues alone. Avoid situations where the sole indication
of progress, completion, or some other state is a sound or color change. Either
design the user interface to include clear visual cues, with sound and color for
reinforcement only, or add specific accessibility indicators. When choosing colors,
try to avoid a palette that is hard to distinguish for users with color blindness.
" Provide captions for video content and a readable script for audio content. It's also
helpful to provide controls that adjust the speed of audio or video content, and
ensure that volume and transport controls are easy to find and use.
" Localize your accessibility descriptions when the app supports multiple languages.
" Test the accessibility features of your app on each platform it targets. For more
information, see Testing accessibility.
App lifecycle
Article • 12/23/2022 • 16 minutes to read
.NET Multi-platform App UI (.NET MAUI) apps generally have four execution states: not running,
running, deactivated, and stopped. .NET MAUI raises cross-platform lifecycle events on the Window
class when an app transitions from the not running state to the running state, the running state to
the deactivated state, the deactivated state to the stopped state, the stopped state to the running
state, and the stopped state to the not running state.
The following diagram shows an overview of the .NET MAUI app lifecycle:
In the diagram, the gray oval indicates that the app isn't loaded into memory. The light blue ovals
indicate that the app is in memory. Text on arcs indicates events that are raised by .NET MAUI, that
provide notifications to the running app.
The execution state of an app depends on the app's history. For example, when an app is installed
for the first time, or a device is started, the app can be considered to be not running. When the app is
started, the Created and Activated events are raised and the app is running. If a different app
window gains focus, the Deactivated event is raised and the app is deactivated. If the user switches
to a different app or returns to the device's Home screen, so that the app window is no longer
visible, the Deactivated and Stopped events are raised and the app is stopped. If the user returns to
the app, the Resuming event is raised and app is running. Alternatively, an app might be terminated
by a user while it's running. In this situation the app is deactivated then stopped, the Destroying
event is raised, and the app is not running. Similarly, a device might terminate an app while it's
stopped, due to resource restrictions, and the Destroying event is raised and the app is not running.
In addition, .NET MAUI enables apps to be notified when platform lifecycle events are raised. For
more information, see Platform lifecycle events.
Created This event is raised after the native window has been
created. At this point the cross-platform window will have a
native window handler, but the window might not be
visible yet.
Activated This event is raised when the window has been activated,
and is, or will become, the focused window.
Stopped This event is raised when the window is no longer visible. Disconnect from any long running
There's no guarantee that an app will resume from this processes, or cancel any pending
state, because it may be terminated by the operating requests that might consume
system. device resources.
Resumed This event is raised when an app resumes after being Subscribe to any required events,
stopped. This event won't be raised the first time your app and refresh any content that's on
launches, and can only be raised if the Stopped event has the visible page.
previously been raised.
Destroying This event is raised when the native window is being Remove any event subscriptions
destroyed and deallocated. The same cross-platform that you've attached to the native
window might be used against a new native window when window.
the app is reopened.
These cross-platform events map to different platform events, and the following table shows this
mapping:
In addition, the Windows class also defines a Backgrounding event that's raised on iOS and Mac
Catalyst when the Window is closed or enters a background state. A BackgroundingEventArgs object
accompanies this event, and any string state should be persisted to the State property of the
BackgroundingEventArgs object, which the OS will preserve until it's time to resume the window.
When the window is resumed the state is provided by the IActivationState argument to the
CreateWindow override.
In addition to these events, the Window class also has the following overridable lifecycle methods:
OnCreated , which is invoked when the Created event is raised.
To subscribe to the Window lifecycle events, override the CreateWindow method in your App class to
create a Window instance on which you can subscribe to events:
C#
namespace MyMauiApp
public App()
InitializeComponent();
// Custom logic
};
return window;
Alternatively, to consume the lifecycle overrides, create a class that derives from the Window class
C#
namespace MyMauiApp
// Register services
The Window -derived class can then be consumed by overriding the CreateWindow method in your App
class to return a MyWindow instance.
2 Warning
) Important
Android
The following table lists the .NET MAUI delegates that are invoked in response to Android lifecycle
events being raised:
) Important
Each delegate has a corresponding identically named extension method, that can be called to
register a handler for the delegate.
C#
using Microsoft.Maui.LifecycleEvents;
namespace PlatformLifecycleDemo
builder
.UseMauiApp<App>()
.ConfigureLifecycleEvents(events =>
#if ANDROID
.OnStart((activity) =>
LogEvent(nameof(AndroidLifecycle.OnStart)))
.OnBackPressed((activity) =>
LogEvent(nameof(AndroidLifecycle.OnBackPressed)) && false)
.OnStop((activity) =>
LogEvent(nameof(AndroidLifecycle.OnStop))));
#endif
System.Diagnostics.Debug.WriteLine($"Lifecycle event:
{eventName}{(type == null ? string.Empty : $" ({type})")}");
return true;
});
return builder.Build();
For more information about the Android app lifecycle, see Understand the Activity Lifecycle on
developer.android.com.
iOS
The following table lists the .NET MAUI delegates that are invoked in response to iOS lifecycle events
being raised:
) Important
Each delegate has a corresponding identically named extension method, that can be called to
register a handler for the delegate.
To respond to an iOS lifecycle delegate being invoked, call the ConfigureLifecycleEvents method on
the MauiAppBuilder object in the CreateMauiapp method of your MauiProgram class. Then, on the
ILifecycleBuilder object, call the AddiOS method and specify the Action that registers handlers for
the required delegates:
C#
using Microsoft.Maui.LifecycleEvents;
namespace PlatformLifecycleDemo
builder
.UseMauiApp<App>()
.ConfigureLifecycleEvents(events =>
#if IOS
.OnActivated((app) =>
LogEvent(nameof(iOSLifecycle.OnActivated)))
.OnResignActivation((app) =>
LogEvent(nameof(iOSLifecycle.OnResignActivation)))
.DidEnterBackground((app) =>
LogEvent(nameof(iOSLifecycle.DidEnterBackground)))
.WillTerminate((app) =>
LogEvent(nameof(iOSLifecycle.WillTerminate))));
#endif
System.Diagnostics.Debug.WriteLine($"Lifecycle event:
{eventName}{(type == null ? string.Empty : $" ({type})")}");
return true;
});
return builder.Build();
For more information about the iOS app lifecycle, see Managing Your App's Life Cycle on
developer.apple.com.
Windows
The following table lists the .NET MAUI delegates that are invoked in response to Windows lifecycle
events being raised:
.NET MAUI exposes specific native Windows messages as a lifecycle event with the
OnPlatformMessage delegate. The WindowsPlatformMessageEventArgs object that accompanies this
delegate includes a MessageId property, of type uint . The value of this property can be examined to
determine which message has been passed to your app window. For more information about
windows messages, see Windows Messages (Get Started with Win32 and C++). For a list of window
message constants, see Window notifications.
) Important
Each delegate has a corresponding identically named extension method, that can be called to
register a handler for the delegate.
C#
using Microsoft.Maui.LifecycleEvents;
namespace PlatformLifecycleDemo
builder
.UseMauiApp<App>()
.ConfigureLifecycleEvents(events =>
#if WINDOWS
}));
#endif
System.Diagnostics.Debug.WriteLine($"Lifecycle event:
{eventName}{(type == null ? string.Empty : $" ({type})")}");
return true;
});
return builder.Build();
C#
using Microsoft.Maui.LifecycleEvents;
namespace PlatformLifecycleDemo
builder
.UseMauiApp<App>()
.ConfigureLifecycleEvents(events =>
#if WINDOWS
}));
#endif
});
return builder.Build();
Register an event handler for a platform lifecycle event that isn't exposed by .NET MAUI.
In the event handler for the platform lifecycle event, retrieve the ILifecycleEventService
instance and call its InvokeEvents method, specifying the platform event name as its argument.
Then, apps that want to receive notification of the platform lifecycle event should modify the
CreateMauiApp method of their MauiProgram class to call the ConfigureLifecycleEvents method on
the MauiAppBuilder object. Then, on the ILifecycleBuilder object, call the AddEvent method and
specify the platform event name and the Action that will be invoked when the platform event is
raised.
Example
The WinUI 3 Window.SizeChanged event occurs when the native app window has first rendered, or
has changed its rendering size. .NET MAUI doesn't expose this platform event as a lifecycle event.
However, apps can receive notification when this platform event is raised by using the following
approach:
C#
using Microsoft.Maui.LifecycleEvents;
...
builder
.UseMauiApp<App>()
.ConfigureLifecycleEvents(events =>
#if WINDOWS
.OnWindowCreated(window =>
window.SizeChanged += OnSizeChanged;
}));
#endif
});
return builder.Build();
In the event handler for the platform lifecycle event, retrieve the ILifecycleEventService
instance and call its InvokeEvents method, specifying the platform event name as its argument:
C#
using Microsoft.Maui.LifecycleEvents;
...
#if WINDOWS
ILifecycleEventService service =
MauiWinUIApplication.Current.Services.GetRequiredService<ILifecycleEventService>();
service.InvokeEvents(nameof(Microsoft.UI.Xaml.Window.SizeChanged));
#endif
The MauiWinUIApplication type on Windows can be used to access the native app instance via
its Current property. The MauiApplication type on Android can be used to access the native
app instance. Similarly, the MauiUIApplicationDelegate type on iOS can be used to access the
native app instance.
2 Warning
C#
using Microsoft.Maui.LifecycleEvents;
namespace PlatformLifecycleDemo
builder
.UseMauiApp<App>()
.ConfigureLifecycleEvents(events =>
#if WINDOWS
.OnWindowCreated(window =>
window.SizeChanged += OnSizeChanged;
}));
events.AddEvent(nameof(Microsoft.UI.Xaml.Window.SizeChanged),
() => LogEvent("Window SizeChanged"));
#endif
System.Diagnostics.Debug.WriteLine($"Lifecycle event:
{eventName}{(type == null ? string.Empty : $" ({type})")}");
return true;
});
return builder.Build();
The overall effect is that when a user changes the app window size on Windows, the action specified
in the AddEvent method is executed.
7 Note
The AddEvent method also has an overload that enables a delegate to be specified.
Behaviors
Article • 04/03/2023 • 9 minutes to read
.NET Multi-platform App UI (.NET MAUI) behaviors let you add functionality to user
interface controls without having to subclass them. Instead, the functionality is
implemented in a behavior class and attached to the control as if it was part of the
control itself.
Behaviors enable you to implement code that you would normally have to write as
code-behind, because it directly interacts with the API of the control in such a way that it
can be concisely attached to the control and packaged for reuse across more than one
application. They can be used to provide a full range of functionality to controls, such as:
Attached behaviors are static classes with one or more attached properties. For
more information about attached behaviors, see Attached behaviors.
.NET MAUI behaviors are classes that derive from the Behavior or Behavior<T>
class, where T is the type of the control to which the behavior should apply. For
more information, see .NET MAUI Behaviors.
Attached behaviors
Attached behaviors are static classes with one or more attached properties. An attached
property is a special type of bindable property. They are defined in one class but
attached to other objects, and they are recognizable in XAML as attributes that contain
a class and a property name separated by a period. For more information about
attached properties, see Attached properties.
An attached property can define a propertyChanged delegate that will be executed when
the value of the property changes, such as when the property is set on a control. When
the propertyChanged delegate executes, it's passed a reference to the control on which it
is being attached, and parameters that contain the old and new values for the property.
This delegate can be used to add new functionality to the control that the property is
attached to by manipulating the reference that is passed in, as follows:
1. The propertyChanged delegate casts the control reference, which is received as a
BindableObject, to the control type that the behavior is designed to enhance.
2. The propertyChanged delegate modifies properties of the control, calls methods of
the control, or registers event handlers for events exposed by the control, to
implement the core behavior functionality.
2 Warning
Attached behaviors are defined in a static class, with static properties and
methods. This makes it difficult to create attached behaviors that have state.
C#
BindableProperty.CreateAttached("AttachBehavior", typeof(bool),
typeof(AttachedNumericValidationBehavior), false, propertyChanged:
OnAttachBehaviorChanged);
return (bool)view.GetValue(AttachBehaviorProperty);
view.SetValue(AttachBehaviorProperty, value);
if (entry == null)
return;
if (attachBehavior)
entry.TextChanged += OnEntryTextChanged;
else
entry.TextChanged -= OnEntryTextChanged;
double result;
XAML
<ContentPage ...
xmlns:local="clr-namespace:BehaviorsDemos">
<Entry Placeholder="Enter a System.Double"
local:AttachedNumericValidationBehavior.AttachBehavior="true" />
</ContentPage>
C#
AttachedNumericValidationBehavior.SetAttachBehavior(entry, true);
The following screenshot shows the attached behavior responding to invalid input:
7 Note
Attached behaviors are written for a specific control type (or a superclass that can
apply to many controls), and they should only be added to a compatible control.
XAML
At runtime, the OnAttachBehaviorChanged method will be executed when the value of the
AttachBehavior attached property is set to false . The OnAttachBehaviorChanged method
will then de-register the event handler for the TextChanged event, ensuring that the
behavior isn't executed as you interact with the control.
1. Create a class that inherits from the Behavior or Behavior<T> class, where T is the
type of the control to which the behavior should apply.
2. Override the OnAttachedTo method to perform any required setup.
3. Override the OnDetachingFrom method to perform any required cleanup.
4. Implement the core functionality of the behavior.
C#
base.OnAttachedTo(bindable);
// Perform setup
base.OnDetachingFrom(bindable);
// Perform clean up
// Behavior implementation
The OnDetachingFrom method is called when the behavior is removed from the control.
This method receives a reference to the control to which it is attached, and is used to
perform any required cleanup. For example, you could unsubscribe from an event on a
control to prevent memory leaks.
The behavior can then be consumed by attaching it to the Behaviors collection of the
control.
The following example shows the NumericValidationBehavior class, which highlights the
value entered by the user into an Entry control in red if it's not a double :
C#
entry.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(entry);
}
entry.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(entry);
double result;
event to prevent memory leaks. The core functionality of the behavior is provided by the
OnEntryTextChanged method, which parses the value entered in the Entry and sets the
TextColor property to red if the value isn't a double .
) Important
.NET MAUI does not set the BindingContext of a behavior, because behaviors can
be shared and applied to multiple controls through styles.
XAML
<Entry.Behaviors>
<local:NumericValidationBehavior />
</Entry.Behaviors>
</Entry>
C#
entry.Behaviors.Add(new NumericValidationBehavior());
The following screenshot shows the .NET MAUI behavior responding to invalid input:
2 Warning
.NET MAUI behaviors are written for a specific control type (or a superclass that can
apply to many controls), and they should only be added to a compatible control.
Attempting to attach a .NET MAUI behavior to an incompatible control will result in
an exception being thrown.
1. Add an attached property to the behavior class that will be used to control the
addition or removal of the behavior to the control to which the behavior will be
attached. Ensure that the attached property registers a propertyChanged delegate
that will be executed when the value of the property changes.
2. Create a static getter and setter for the attached property.
3. Implement logic in the propertyChanged delegate to add and remove the behavior.
C#
BindableProperty.CreateAttached("AttachBehavior", typeof(bool),
typeof(NumericValidationStyleBehavior), false, propertyChanged:
OnAttachBehaviorChanged);
return (bool)view.GetValue(AttachBehaviorProperty);
view.SetValue(AttachBehaviorProperty, value);
if (entry == null)
return;
if (attachBehavior)
entry.Behaviors.Add(new NumericValidationStyleBehavior());
else
if (toRemove != null)
entry.Behaviors.Remove(toRemove);
...
XAML
<Style.Setters>
<Setter
Property="local:NumericValidationStyleBehavior.AttachBehavior" Value="true"
/>
</Style.Setters>
</Style>
The Style can be applied to an Entry by setting its Style property to the style using the
StaticResource markup extension:
XAML
7 Note
While you can add bindable properties to a behavior that is set or queried in XAML,
if you do create behaviors that have state they should not be shared between
controls in a Style in a ResourceDictionary.
C#
if (toRemove != null)
entry.Behaviors.Remove(toRemove);
C#
entry.Behaviors.Clear();
7 Note
.NET MAUI behaviors are not implicitly removed from controls when pages are
popped from the navigation stack. Instead, they must be explicitly removed prior to
pages going out of scope.
Data binding
Article • 04/04/2023 • 2 minutes to read
A .NET Multi-platform App UI (.NET MAUI) app consists of one or more pages, each of
which typically contains multiple user-interface objects called views. One of the primary
tasks of the app is to keep these views synchronized, and to keep track of the various
values or selections that they represent. Often the views represent values from an
underlying data source, and users manipulate these views to change that data. When
the view changes, the underlying data must reflect that change, and similarly, when the
underlying data changes, that change must be reflected in the view.
To handle this successfully, the app must be notified of changes in these views or the
underlying data. The common solution is to define events that signal when a change
occurs. An event handler can then be installed that is notified of these changes. It
responds by transferring data from one object to another. However, when there are
many views, there must also be many event handlers, which results in a lot of boilerplate
code.
Data binding automates this task, and renders the event handlers unnecessary. Data
bindings can be implemented either in XAML or code, but they are much more common
in XAML where they help to reduce the size of the code-behind file. By replacing
procedural code in event handlers with declarative code or markup, the app is simplified
and clarified.
Data binding is therefore the technique of linking properties of two objects so that
changes in one property are automatically reflected in the other property. One of the
two objects involved in a data binding is almost always an element that derives from
View and forms part of the visual interface of a page. The other object is either:
Data bindings between two View derivatives are often shown in these articles, for
purposes of clarity and simplicity. However, the same principles can be applied to data
bindings between a View and other objects. When an application is built using the
Model-View-ViewModel (MVVM) architecture, the class with underlying data is often
called a viewmodel.
) Important
.NET MAUI marshals binding updates to the UI thread. When using MVVM this
enables you to update data-bound viewmodel properties from any thread, with
.NET MAUI's binding engine bringing the updates to the UI thread.
Basic bindings
Article • 04/03/2023 • 7 minutes to read
A .NET Multi-platform App UI (.NET MAUI) data binding links a pair of properties
between two objects, at least one of which is usually a user-interface object. These two
objects are called the target and the source:
The target is the object (and property) on which the data binding is set.
The source is the object (and property) referenced by the data binding.
In the simplest case, data flows from the source to the target, which means that the
value of the target property is set from the value of the source property. However, in
some cases, data can alternatively flow from the target to the source, or in both
directions.
) Important
The target is always the object on which the data binding is set even if it's
providing data rather than receiving data.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicCodeBindingPage"
<Label x:Name="label"
Text="TEXT"
FontSize="48"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
Without data bindings, you would set the ValueChanged event of the Slider to an event
handler that accesses the Value property of the Slider and sets that value to the
Rotation property of the Label. The data binding automates this task, and so the event
You can set a binding on an instance of any class that derives from BindableObject,
which includes Element, VisualElement, View, and View derivatives. The binding is always
set on the target object. The binding references the source object. To set the data
binding, use the following two members of the target class:
In this example, the Label is the binding target, and the Slider is the binding source.
Changes in the Slider source affect the rotation of the Label target. Data flows from the
source to the target.
methods defined by the BindableObjectExtensions class. The code-behind for the XAML
uses a simpler SetBinding extension method from the BindableObjectExtensions class:
C#
public BasicCodeBindingPage()
InitializeComponent();
label.BindingContext = slider;
label.SetBinding(Label.RotationProperty, "Value");
The Label object is the binding target so that's the object on which this property is set
and on which the method is called. The BindingContext property indicates the binding
source, which is the Slider. The SetBinding method is called on the binding target but
specifies both the target property and the source property. The target property is
specified as a BindableProperty object: Label.RotationProperty . The source property is
specified as a string and indicates the Value property of Slider.
) Important
The target property must be backed by a bindable property. Therefore, the target
object must be an instance of a class that derives from BindableObject. For more
information, see Bindable properties.
The source property is specified as a string. Internally, reflection is used to access the
actual property. In this particular case, however, the Value property is also backed by a
bindable property.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BasicXamlBindingPage"
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="Center"
BindingContext="{x:Reference Name=slider}"
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
Just as in code, the data binding is set on the target object, which is the Label. Two
XAML markup extensions are used to define the data binding:
For more information about XAML markup extensions, see Consume XAML markup
extensions.
7 Note
The source property is specified with the Path property of the Binding markup
extension, which corresponds with the Path property of the Binding class.
XAML markup extensions such as x:Reference and Binding can have content property
attributes defined, which for XAML markup extensions means that the property name
doesn't need to appear. The Name property is the content property of x:Reference , and
the Path property is the content property of Binding , which means that they can be
eliminated from the expressions:
XAML
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="Center"
BindingContext="{x:Reference slider}"
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeCodeBindingPage"
Title="Alternative Code Binding">
<Label x:Name="label"
Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
In this example, the Slider is defined to control the Scale property of the Label. For that
reason, the Slider is set for a range of -2 to 2.
The code-behind file sets the binding with the SetBinding method, with the second
argument being a constructor for the Binding class:
C#
public AlternativeCodeBindingPage()
InitializeComponent();
The Binding constructor has 6 parameters, so the source parameter is specified with a
named argument. The argument is the slider object.
7 Note
The VisualElement class also defines ScaleX and ScaleY properties, which can
scale the VisualElement differently in the horizontal and vertical directions.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.AlternativeXamlBindingPage"
Title="Alternative XAML Binding">
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="Center"
Path=Value}" />
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
In this example, the Binding markup extension has two properties set, Source and Path ,
separated by a comma. The Source property is set to an embedded x:Reference
markup extension that otherwise has the same syntax as setting the BindingContext .
The content property of the Binding markup extension is Path , but the Path= part of
the markup extension can only be eliminated if it is the first property in the expression.
To eliminate the Path= part, you need to swap the two properties:
XAML
Although XAML markup extensions are usually delimited by curly braces, they can also
be expressed as object elements:
XAML
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label.Scale>
Path="Value" />
</Label.Scale>
</Label>
In this example, the Source and Path properties are regular XAML attributes. The values
appear within quotation marks and the attributes are not separated by a comma. The
x:Reference markup extension can also become an object element:
XAML
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label.Scale>
<Binding Path="Value">
<Binding.Source>
</Binding.Source>
</Binding>
</Label.Scale>
</Label>
This syntax isn't common, but sometimes it's necessary when complex objects are
involved.
The examples shown so far set the BindingContext property and the Source property of
Binding to an x:Reference markup extension to reference another view on the page.
These two properties are of type Object , and they can be set to any object that includes
properties that are suitable for binding sources. You can also set the BindingContext or
Source property to an x:Static markup extension to reference the value of a static
7 Note
The BindingContext property can also be set to a Binding object so that the
Source and Path properties of Binding define the binding context.
) Important
The BindingContext property value is inherited through the visual tree.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.BindingContextInheritancePage"
Title="BindingContext Inheritance">
<StackLayout Padding="10">
<StackLayout VerticalOptions="Fill"
BindingContext="{x:Reference slider}">
<Label Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="End"
<BoxView Color="#800000FF"
WidthRequest="180"
HeightRequest="40"
HorizontalOptions="Center"
VerticalOptions="Start"
</StackLayout>
<Slider x:Name="slider"
Maximum="360" />
</StackLayout>
</ContentPage>
In this example, the BindingContext property of the StackLayout is set to the slider
object. This binding context is inherited by both the Label and the BoxView, both of
which have their Rotation properties set to the Value property of the Slider:
Binding mode
Article • 12/23/2022 • 7 minutes to read
Every .NET Multi-platform App UI (.NET MAUI) bindable property has a default binding
mode that is set when the bindable property is created, and which is available from the
DefaultBindingMode property of the BindableProperty object. This default binding mode
indicates the mode in effect when that property is a data-binding target. The default
binding mode for most properties such as Rotation , Scale , and Opacity is OneWay .
When these properties are data-binding targets, then the target property is set from the
source.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingDemos.ReverseBindingPage"
Title="Reverse Binding">
<Label x:Name="label"
Text="TEXT"
FontSize="80"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Slider x:Name="slider"
VerticalOptions="Center"
Path=Opacity}" />
</StackLayout>
</ContentPage>
In this example, the Label is the data-binding source, and the Slider is the target. The
binding references the Opacity property of the Label, which has a default value of 1.
Therefore, the Slider is initialized to the value 1 from the initial Opacity value of Label.
This is shown in the following screenshot:
In addition, the Slider continues to work. This is because the default binding mode for
the Value property of Slider is TwoWay . This means that when the Value property is a
data-binding target, then the target is set from the source but the source is also set
from the target. This allows the Slider to be set from the initial Opacity value.
7 Note
Bindable properties don't signal a property change unless the property actually
changes. This prevents an infinite loop.
If the default binding mode on the target property is not suitable for a particular data
binding, it's possible to override it by setting the Mode property of Binding (or the Mode
property of the Binding markup extension) to one of the members of the BindingMode
enumeration:
Default
TwoWay — data goes both ways between source and target
OneTime — data goes from source to target, but only when the BindingContext
changes
Two-way bindings
Most bindable properties have a default binding mode of OneWay but some properties
have a default binding mode of TwoWay , including the following:
On property of SwitchCell
These properties are defined as TwoWay because when data bindings are used with the
Model-View-ViewModel (MVVM) pattern, the viewmodel class is the data-binding
source, and the view, which consists of views such as Slider, are data-binding targets.
MVVM bindings resemble the example above, because it's likely that you want each
view on the page to be initialized with the value of the corresponding property in the
viewmodel, but changes in the view should also affect the viewmodel property.
One-way-to-source bindings
Read-only bindable properties have a default binding mode of OneWayToSource . For
example, the SelectedItem property of ListView has a default binding mode of
OneWayToSource . This is because a binding on the SelectedItem property should result in
One-time bindings
Target properties with a binding mode of OneTime are updated only when the binding
context changes. For bindings on these target properties, this simplifies the binding
infrastructure because it is not necessary to monitor changes in the source properties.
In the following example, data bindings allow you to select a color using three Slider
elements for the hue, saturation, and luminosity:
C#
Color color;
string name;
float hue;
float saturation;
float luminosity;
get
return hue;
set
if (hue != value)
get
return saturation;
set
if (saturation != value)
get
return luminosity;
set
if (luminosity != value)
get
return color;
set
if (color != value)
color = value;
hue = color.GetHue();
saturation = color.GetSaturation();
luminosity = color.GetLuminosity();
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Hue"));
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Saturation"));
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Luminosity"));
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Color"));
Name = NamedColor.GetNearestColorName(color);
get
return name;
private set
if (name != value)
name = value;
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Name"));
value, the Color property is recalculated, and PropertyChanged events are raised for all
four properties. When the Color property changes, the static GetNearestColorName
method in the NamedColor class obtains the closest named color and sets the Name
property.
When a viewmodel is set as a binding source, the binding infrastructure attaches a
handler to the PropertyChanged event. In this way, the binding can be notified of
changes to properties, and can then set the target properties from the changed values.
However, when a target property (or the Binding definition on a target property) has a
BindingMode of OneTime , it is not necessary for the binding infrastructure to attach a
handler on the PropertyChanged event. The target property is updated only when the
BindingContext changes and not when the source property itself changes.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.SimpleColorSelectorPage">
<ContentPage.BindingContext>
</ContentPage.BindingContext>
<ContentPage.Resources>
<Style TargetType="Slider">
</Style>
</ContentPage.Resources>
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
Grid.Row="0" />
<StackLayout Grid.Row="1"
Margin="10, 0">
HorizontalTextAlignment="Center" />
</StackLayout>
</Grid>
</ContentPage>
In this example, the HslColorViewModel is instantiated, and Color property set, and set
as the page's BindingContext . The BoxView, Label, and three Slider views inherit the
binding context from the ContentPage. These views are all binding targets that
reference source properties in the viewmodel. For the Color property of the BoxView,
and the Text property of the Label, the data bindings are OneWay - the properties in the
view are set from the properties in the viewmodel. The Value property of the Slider,
however, uses a TwoWay binding mode. This enables each Slider to be set from the
viewmodel, and also for the viewmodel to be set from each Slider.
When the example is first run, the BoxView, Label, and three Slider elements are all set
from the viewmodel based on the initial Color property set when the viewmodel was
instantiated:
As you manipulate the sliders, the BoxView and Label are updated accordingly.
XAML
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Path=Value,
Mode=TwoWay}" />
In this example, it might be expected that the Slider would be initialized to the initial
value of the Scale property, which is 1, but that doesn't happen. When a TwoWay
binding is initialized, the target is set from the source first, which means that the Scale
property is set to the Slider default value of 0. When the TwoWay binding is set on the
Slider, then the Slider is initially set from the source.
XAML
<Label Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Path=Value,
Mode=OneWayToSource}" />
Now the Slider is initialized to 1 (the default value of Scale ) but manipulating the Slider
doesn't affect the Scale property.
7 Note
The VisualElement class also defines ScaleX and ScaleY properties, which can
scale the VisualElement differently in the horizontal and vertical directions.
A very useful application of overriding the default binding mode with a TwoWay binding
mode involves the SelectedItem property of ListView. The default binding mode is
OneWayToSource . When a data binding is set on the SelectedItem property to reference a
source property in a viewmodel, then that source property is set from the ListView
selection. However, in some circumstances, you might also want the ListView to be
initialized from the viewmodel.
String formatting
Article • 12/23/2022 • 3 minutes to read
In a .NET Multi-platform App UI (.NET MAUI) app, it's sometimes convenient to use data
bindings to display the string representation of an object or value. For example, you
might want to use a Label to display the current value of a Slider. In this data binding,
the Slider is the source, and the target is the Text property of the Label.
String formatting in code is typically accomplished with the static String.Format method.
The formatting string includes formatting codes specific to various types of objects, and
you can include other text along with the values being formatted. For more information,
see Formatting Types in .NET for more information on string formatting.
String formatting can also be accomplished with data bindings by setting the
StringFormat property of Binding (or the StringFormat property of the Binding markup
extension) to a standard .NET formatting string with a placeholder:
XAML
Path=Value,
StringFormat='The slider value is {0:F2}'}" />
In XAML the formatting string is delimited by single-quote characters to help the XAML
parser avoid treating the curly braces as another XAML markup extension. In this
example, the formatting specification of F2 causes the value to be displayed with two
decimal places.
7 Note
Using the StringFormat property only makes sense when the target property is of
type string , and the binding mode is OneWay or TwoWay . For two-way bindings, the
StringFormat is only applicable for values passing from the source to the target.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="DataBindingDemos.StringFormattingPage"
Title="String Formatting">
<ContentPage.Resources>
<Style TargetType="Label">
</Style>
<Style TargetType="BoxView">
</Style>
</ContentPage.Resources>
<StackLayout Margin="10">
Path=Value,
<BoxView />
Path=Time,
<BoxView />
Path=Text,
<BoxView />
</StackLayout>
<BoxView />
</StackLayout>
</StackLayout>
</ContentPage>
In this example, the bindings on the Slider and TimePicker show the use of format
specifications particular to double and TimeSpan data types. The StringFormat that
displays the text from the Entry view demonstrates how to specify double quotation
marks in the formatting string with the use of the " HTML entity.
The next section in the XAML file is a StackLayout with a BindingContext set to an
x:Static markup extension that references the static DateTime.Now property. The first
XAML
This simply displays the DateTime value of the BindingContext with default formatting.
The second binding displays the Ticks property of DateTime , while the other two
bindings display the DateTime itself with specific formatting.
7 Note
If you need to display left or right curly braces in your formatting string, use a pair
of them. For example, StringFormat='{{0:MMMM}}' .
The last section sets the BindingContext to the value of Math.PI and displays it with
default formatting and two different types of numeric formatting:
ViewModels and string formatting
When you're using Label and StringFormat to display the value of a view that is also the
target of a viewmodel, you can either define the binding from the view to the Label or
from the viewmodel to the Label. In general, the second approach is best because it
verifies that the bindings between the view and viewmodel are working.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.BetterColorSelectorPage"
<ContentPage.BindingContext>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Slider">
</Style>
<Style TargetType="Label">
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Margin="20">
HeightRequest="100"
WidthRequest="100"
HorizontalOptions="Center" />
</StackLayout>
</StackLayout>
</ContentPage>
In this example, there are three pairs of Slider and Label elements that are bound to the
same source property in the HslColorViewModel object. Each Label that accompanies a
Slider has a StringFormat property to display each Slider value:
Binding path
Article • 12/23/2022 • 3 minutes to read
In .NET Multi-platform App UI (.NET MAUI), the Path property of the Binding class (or
the Path property of the Binding markup extension) can be set to a single property, to
a sub-property (a property of a property), or to a member of a collection.
XAML
<TimePicker x:Name="timePicker">
XAML
Path=Time.TotalSeconds}
The Time and TotalSeconds properties are simply connected with a period.
7 Note
The items in the Path string always refer to properties and not to the types of these
properties.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:globe="clr-
namespace:System.Globalization;assembly=netstandard"
x:Class="DataBindingDemos.PathVariationsPage"
Title="Path Variations"
x:Name="page">
<ContentPage.Resources>
<Style TargetType="Label">
</Style>
</ContentPage.Resources>
Path=Time.TotalSeconds,
Path=DateTimeFormat.DayNames[3],
<Label>
<Label.Text>
<Binding Path="DateTimeFormat.DayNames[3]"
<Binding.Source>
<globe:CultureInfo>
<x:Arguments>
<x:String>fr-FR</x:String>
</x:Arguments>
</globe:CultureInfo>
</Binding.Source>
</Binding>
</Label.Text>
</Label>
</StackLayout>
</ContentPage>
In the second Label, the binding source is the page itself. The Content property is of
type StackLayout, which has a Children property of type IList<View> , which has a
Count property indicating the number of children.
XAML
Path=DateTimeFormat.DayNames[3],
The fourth Label does something similar but for the culture associated with France. The
Source property of the binding is set to CultureInfo object with a constructor:
XAML
<Label>
<Label.Text>
<Binding Path="DateTimeFormat.DayNames[3]"
<Binding.Source>
<globe:CultureInfo>
<x:Arguments>
<x:String>fr-FR</x:String>
</x:Arguments>
</globe:CultureInfo>
</Binding.Source>
</Binding>
</Label.Text>
</Label>
For more information about specifying constructor arguments in XAML, see Pass
constructor arguments.
The last Label is similar to the second, except that it references one of the children of
the StackLayout:
XAML
Path=Content.Children[1].Text.Length,
That child is a Label, which has a Text property of type String , which has a Length
property. The first Label reports the TimeSpan set in the TimePicker, so when that text
changes, the final Label changes as well:
XAML
StringFormat='{0}'}" />
XAML
Path=Content,
StringFormat='{0}'}" />
As .NET MAUI processes the binding path, it installs a PropertyChanged handler on any
object in the path that implements the INotifyPropertyChanged interface. For example,
the final binding reacts to a change in the first Label because the Text property
changes. If a property in the binding path does not implement INotifyPropertyChanged ,
any changes to that property will be ignored. Some changes could entirely invalidate the
binding path, so you should use this technique only when the string of properties and
sub-properties never become invalid.
Binding value converters
Article • 04/03/2023 • 9 minutes to read
.NET Multi-platform App UI (.NET MAUI) data bindings usually transfer data from a
source property to a target property, and in some cases from the target property to the
source property. This transfer is straightforward when the source and target properties
are of the same type, or when one type can be converted to the other type through an
implicit conversion. When that is not the case, a type conversion must take place.
In the String formatting article, you saw how you can use the StringFormat property of a
data binding to convert any type into a string. For other types of conversions, you need
to write some specialized code in a class that implements the IValueConverter interface.
Classes that implement IValueConverter are called value converters, but they are also
often referred to as binding converters or binding value converters.
C#
return (int)value != 0;
return (bool)value ? 1 : 0;
You then set an instance of this class to the Converter property of the Binding class or
to the Converter property of the Binding markup extension. This class becomes part of
the data binding.
The Convert method is called when data moves from the source to the target in OneWay
or TwoWay bindings. The value parameter is the object or value from the data-binding
source. The method must return a value of the type of the data-binding target. The
method shown here casts the value parameter to an int and then compares it with 0
for a bool return value.
The ConvertBack method is called when data moves from the target to the source in
TwoWay or OneWayToSource bindings. ConvertBack performs the opposite conversion: It
assumes the value parameter is a bool from the target, and converts it to an int return
value for the source.
7 Note
The following example demonstrates how to use this value converter in a data binding:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.EnableButtonsPage"
Title="Enable Buttons">
<ContentPage.Resources>
</ContentPage.Resources>
<Entry x:Name="entry1"
Text=""
VerticalOptions="Center" />
<Button Text="Search"
HorizontalOptions="Center"
VerticalOptions="Center"
Path=Text.Length,
Converter={StaticResource intToBool}}"
/>
<Entry x:Name="entry2"
Text=""
Placeholder="enter destination"
VerticalOptions="Center" />
<Button Text="Submit"
HorizontalOptions="Center"
VerticalOptions="Center"
Path=Text.Length,
Converter={StaticResource intToBool}}"
/>
</StackLayout>
</ContentPage>
7 Note
If you know that a value converter will only be used in OneWay bindings, then the
ConvertBack method can simply return null .
The Convert method shown above assumes that the value argument is of type int and
the return value must be of type bool . Similarly, the ConvertBack method assumes that
the value argument is of type bool and the return value is int . If that is not the case, a
runtime exception will occur.
You can write value converters to be more generalized and to accept several different
types of data. The Convert and ConvertBack methods can use the as or is operators
with the value parameter, or can call GetType on that parameter to determine its type,
and then do something appropriate. The expected type of each method's return value is
given by the targetType parameter. Sometimes, value converters are used with data
bindings of different target types. In this case the value converter can use the
targetType argument to perform a conversion for the correct type.
If the conversion being performed is different for different cultures, use the culture
parameter for this purpose.
C#
return ((T)value).Equals(TrueObject);
The following example demonstrates how this converter can be used to display the
value of a Switch view. Although it's common to instantiate value converters as
resources in a resource dictionary, this example demonstrates an alternative. Here, each
value converter is instantiated between Binding.Converter property-element tags. The
x:TypeArguments indicates the generic argument, and TrueObject and FalseObject are
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.SwitchIndicatorsPage"
Title="Switch Indicators">
<ContentPage.Resources>
<Style TargetType="Label">
</Style>
<Style TargetType="Switch">
</Style>
</ContentPage.Resources>
<StackLayout Orientation="Horizontal"
VerticalOptions="Center">
<Label>
<Label.Text>
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter
x:TypeArguments="x:String"
TrueObject="Of
course!"
FalseObject="No
way!" />
</Binding.Converter>
</Binding>
</Label.Text>
</Label>
</StackLayout>
<StackLayout Orientation="Horizontal"
VerticalOptions="Center">
<Label>
<Label.Text>
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter
x:TypeArguments="x:String"
TrueObject="Yes"
FalseObject="No" />
</Binding.Converter>
</Binding>
</Label.Text>
<Label.TextColor>
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter
x:TypeArguments="Color"
TrueObject="Green"
FalseObject="Red"
/>
</Binding.Converter>
</Binding>
</Label.TextColor>
</Label>
</StackLayout>
<StackLayout Orientation="Horizontal"
VerticalOptions="Center">
<Label FontSize="18"
VerticalOptions="Center">
<Label.Style>
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter
x:TypeArguments="Style">
<local:BoolToObjectConverter.TrueObject>
<Style TargetType="Label">
<Setter Property="Text"
Value="Indubitably!" />
<Setter Property="FontAttributes"
Value="Italic, Bold" />
<Setter Property="TextColor"
Value="Green" />
</Style>
</local:BoolToObjectConverter.TrueObject>
<local:BoolToObjectConverter.FalseObject>
<Style TargetType="Label">
<Setter Property="FontAttributes"
Value="None" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</local:BoolToObjectConverter.FalseObject>
</local:BoolToObjectConverter>
</Binding.Converter>
</Binding>
</Label.Style>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
In this example, in the last of the three Switch and Label pairs, the generic argument is
set to a Style, and entire Style objects are provided for the values of TrueObject and
FalseObject . These override the implicit style for Label set in the resource dictionary, so
the properties in that style are explicitly assigned to the Label. Toggling the Switch
causes the corresponding Label to reflect the change:
7 Note
It's also possible to use triggers to implement changes in the user-interface based
on other views. For more information, see Triggers.
C#
public class RgbColorViewModel : INotifyPropertyChanged
Color color;
string name;
set
if (color.Red != value)
set
if (color.Green != value)
set
if (color.Blue != value)
set
if (color != value)
color = value;
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Red"));
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Green"));
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Blue"));
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Color"));
Name = NamedColor.GetNearestColorName(color);
private set
if (name != value)
name = value;
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Name"));
The Red , Green , and Blue property values can range between 0 and 1. However, you
might prefer that the components be displayed as two-digit hexadecimal values. To
display these as hexadecimal values in XAML, they must be multiplied by 255, converted
to an integer, and then formatted with a specification of "X2" in the StringFormat
property. Multiplying by 255 and converting to an integer can be performed by the
value converter. To make the value converter as generalized as possible, the
multiplication factor can be specified with the ConverterParameter property, which
means that it enters the Convert and ConvertBack methods as the parameter argument:
C#
if (parameter is float)
return (float)parameter;
return (int)parameter;
return float.Parse((string)parameter);
return 1;
In this example, the Convert method converts from a float to int while multiplying by
the parameter value. The ConvertBack method divides the integer value argument by
parameter and returns a float result.
The type of the parameter argument is likely to be different depending on whether the
data binding is defined in XAML or code. If the ConverterParameter property of Binding
is set in code, it's likely to be set to a numeric value:
C#
binding.ConverterParameter = 255;
XAML
Converter={StaticResource doubleToInt},
ConverterParameter=255,
While 255 looks like a number, because ConverterParameter is of type Object , the XAML
parser treats 255 as a string. For this reason the value converter includes a separate
GetParameter method that handles cases for parameter being of type float , int , or
string .
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.RgbColorSelectorPage"
<ContentPage.BindingContext>
</ContentPage.BindingContext>
<ContentPage.Resources>
<Style TargetType="Slider">
</Style>
<Style TargetType="Label">
</Style>
</ContentPage.Resources>
<StackLayout Margin="20">
HeightRequest="100"
WidthRequest="100"
HorizontalOptions="Center" />
Converter={StaticResource floatToInt},
ConverterParameter=255,
Converter={StaticResource floatToInt},
ConverterParameter=255,
<Label>
<Label.Text>
<Binding Path="Blue"
StringFormat="Blue = {0:X2}"
Converter="{StaticResource floatToInt}">
<Binding.ConverterParameter>
<x:Single>255</x:Single>
</Binding.ConverterParameter>
</Binding>
</Label.Text>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
The values of the Red and Green properties are displayed with a Binding markup
extension. The Blue property, however, instantiates the Binding class to demonstrate
how an explicit float value can be set to ConverterParameter property:
Relative bindings
Article • 04/03/2023 • 5 minutes to read
.NET Multi-platform App UI (.NET MAUI) relative bindings provide the ability to set the
binding source relative to the position of the binding target. They are created with the
RelativeSource markup extension, and set as the Source property of a binding
expression.
AncestorType , of type Type , the type of ancestor to look for, when the Mode
property is FindAncestor .
7 Note
TemplatedParent indicates the element to which the template, in which the bound
element exists, is applied. For more information, see Bind to a templated parent.
Self indicates the element on which the binding is being set, allowing you to bind
one property of that element to another property on the same element. For more
information, see Bind to self.
FindAncestor indicates the ancestor in the visual tree of the bound element. This
For more information about .NET MAUI markup extensions, see Consume XAML markup
extensions.
Bind to self
The Self relative binding mode is used bind a property of an element to another
property on the same element:
XAML
<BoxView Color="Red"
WidthRequest="200"
HorizontalOptions="Center" />
In this example, the BoxView sets its WidthRequest property to a fixed size, and the
HeightRequest property binds to the WidthRequest property. Therefore, both properties
are equal and so a square is drawn:
) Important
XAML
<ContentPage ...
<StackLayout>
...
</ListView>
</StackLayout>
</ContentPage>
In this example, the BindingContext of the page is set to the DefaultViewModel property
of itself. This property is defined in the code-behind file for the page, and provides a
viewmodel instance. The ListView binds to the Employees property of the viewmodel.
Bind to an ancestor
The FindAncestor and FindAncestorBindingContext relative binding modes are used to
bind to parent elements, of a certain type, in the visual tree. The FindAncestor mode is
used to bind to a parent element, which derives from the Element type. The
FindAncestorBindingContext mode is used to bind to the BindingContext of a parent
element.
2 Warning
The AncestorType property must be set to a Type when using the FindAncestor
and FindAncestorBindingContext relative binding modes, otherwise a
XamlParseException is thrown.
If the Mode property isn't explicitly set, setting the AncestorType property to a type that
derives from Element will implicitly set the Mode property to FindAncestor . Similarly,
setting the AncestorType property to a type that does not derive from Element will
implicitly set the Mode property to FindAncestorBindingContext .
7 Note
Relative bindings that use the FindAncestorBindingContext mode will be reapplied
when the BindingContext of any ancestors change.
The following XAML shows an example where the Mode property will be implicitly set to
FindAncestorBindingContext :
XAML
<ContentPage ...
<StackLayout>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
VerticalOptions="Center" />
<Button Text="Delete"
Command="{Binding Source={RelativeSource
AncestorType={x:Type local:PeopleViewModel}}, Path=DeleteEmployeeCommand}"
CommandParameter="{Binding}"
HorizontalOptions="End" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
In this example, the BindingContext of the page is set to the DefaultViewModel property
of itself. This property is defined in the code-behind file for the page, and provides a
viewmodel instance. The ListView binds to the Employees property of the viewmodel.
The DataTemplate, which defines the appearance of each item in the ListView, contains a
Button. The button's Command property is bound to the DeleteEmployeeCommand in its
parent's viewmodel. Tapping a Button deletes an employee:
XAML
In this example, the Label.Text property binds to the Text property of the second Entry
that's encountered on the upward path, starting at the target element of the binding.
7 Note
The AncestorLevel property should be set to 1 to find the ancestor nearest to the
binding target element.
The following XAML shows an example of the TemplatedParent relative binding mode:
XAML
<ContentPage ...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewControlTemplate">
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
...>
<Grid>
...
... />
... />
... />
</Grid>
</Frame>
</ControlTemplate>
</ContentPage.Resources>
<StackLayout>
<controls:CardView BorderColor="DarkGray"
CardTitle="John Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource
CardViewControlTemplate}" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Jane Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource
CardViewControlTemplate}" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Xamarin Monkey"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource
CardViewControlTemplate}" />
</StackLayout>
</ContentPage>
In this example, the Frame, which is the root element of the ControlTemplate, has its
BindingContext set to the runtime object instance to which the template is applied.
Therefore, the Frame and its children resolve their binding expressions against the
properties of each CardView object:
For more information about control templates, see Control templates.
Binding fallbacks
Article • 04/03/2023 • 3 minutes to read
Sometimes data bindings fail, because the binding source can't be resolved, or because
the binding succeeds but returns a null value. While these scenarios can be handled
with value converters, or other additional code, data bindings can be made more robust
by defining fallback values to use if the binding process fails. In a .NET Multi-platform
App UI (.NET MAUI) app this can be accomplished by defining the FallbackValue and
TargetNullValue properties in a binding expression. Because these properties reside in
the BindingBase class, they can be used with bindings, multi-bindings, compiled
bindings, and with the Binding markup extension.
7 Note
XAML
... />
XAML
... />
7 Note
It's not possible to set the FallbackValue property with a binding expression.
When the FallbackValue property isn't set in a binding expression and the binding path
or part of the path isn't resolved, BindableProperty.DefaultValue is set on the target.
However, when the FallbackValue property is set and the binding path or part of the
path isn't resolved, the value of the FallbackValue value property is set on the target:
Therefore, in this example the Label displays "Population size unknown" because the
bound object lacks a Population property.
) Important
XAML
...>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
...
... />
...
... />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The bindings on the Image and Label both define TargetNullValue values (delimited by
single-quote characters) that will be applied if the binding path returns null . Therefore,
the values defined by the TargetNullValue properties will be displayed for any objects in
the collection where the ImageUrl and Location properties are not defined.
XAML
... />
... />
7 Note
It's not possible to set the TargetNullValue property with a binding expression.
When the TargetNullValue property isn't set in a binding expression, a source value of
null will be converted if a value converter is defined, formatted if a StringFormat is
defined, and the result is then set on the target. However, when the TargetNullValue
property is set, a source value of null will be converted if a value converter is defined,
and if it's still null after the conversion, the value of the TargetNullValue property is set
on the target:
Therefore, in this example the Image and Label objects display their TargetNullValue
when their source objects are null .
) Important
.NET Multi-platform App UI (.NET MAUI) multi-bindings provide the ability to attach a
collection of Binding objects to a single binding target property. They're created with
the MultiBinding class, which evaluates all of its Binding objects and returns a single
value through a IMultiValueConverter instance provided by your app. In addition,
MultiBinding reevaluates all of its Binding objects when any of the bound data
changes.
The Bindings property is the content property of the MultiBinding class, and therefore
doesn't need to be explicitly set from XAML.
In addition, the MultiBinding class inherits the following properties from the
BindingBase class:
FallbackValue , of type object , which represents the value to use when the multi-
) Important
Individual bindings in the Bindings collection can have their own value converters.
The value of the Mode property determines the functionality of the MultiBinding , and is
used as the binding mode for all the bindings in the collection unless an individual
binding overrides the property. For example, if the Mode property on a MultiBinding
object is set to TwoWay , then all the bindings in the collection are considered TwoWay
unless you explicitly set a different Mode value on one of the bindings.
Define a IMultiValueConverter
The IMultiValueConverter interface enables custom logic to be applied to a
MultiBinding . To associate a converter with a MultiBinding , create a class that
implements the IMultiValueConverter interface, and then implement the Convert and
ConvertBack methods:
C#
return false;
return false;
else if (!b)
return false;
return true;
return null;
if (b)
else
return null;
The Convert method converts source values to a value for the binding target. .NET
MAUI calls this method when it propagates values from source bindings to the binding
target. This method accepts four arguments:
values , of type object[] , is an array of values that the source bindings in the
MultiBinding produces.
The Convert method returns an object that represents a converted value. This method
should return:
The ConvertBack method converts a binding target to the source binding values. This
method accepts four arguments:
value , of type object , is the value that the binding target produces.
targetTypes , of type Type[] , is the array of types to convert to. The array length
indicates the number and types of values that are suggested for the method to
return.
parameter , of type object , is the converter parameter to use.
culture , of type CultureInfo , is the culture to use in the converter.
The ConvertBack method returns an array of values of type object[] that have been
converted from the target values back to the source values. This method should return:
to provide a value for the source binding at index i , and that no value is to be set
on it.
Binding.DoNothing at position i to indicate that no value is to be set on the
source binding at index i .
null to indicate that the converter can't perform the conversion or that it doesn't
Consume a IMultiValueConverter
A IMultiValueConverter is typically consumed by instantiating it in a resource dictionary,
and then referencing it using the StaticResource markup extension to set the
MultiBinding.Converter property:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.MultiBindingConverterPage"
<ContentPage.Resources>
</ContentPage.Resources>
<CheckBox>
<CheckBox.IsChecked>
<Binding Path="Employee.IsSuspended"
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</ContentPage>
In this example, the MultiBinding object uses the AllTrueMultiConverter instance to set
the CheckBox.IsChecked property to true , provided that the three Binding objects
evaluate to true . Otherwise, the CheckBox.IsChecked property is set to false .
CheckBox is unchecked by the user, which sets the source binding values to the value of
the CheckBox.IsChecked property.
C#
public MultiBindingConverterCodePage()
new Binding("Employee1.IsOver16"),
new Binding("Employee1.HasPassedTest"),
},
});
Content = checkBox;
Format strings
A MultiBinding can format any multi-binding result that's displayed as a string, with the
StringFormat property. This property can be set to a standard .NET formatting string,
XAML
<Label>
<Label.Text>
</MultiBinding>
</Label.Text>
</Label>
7 Note
If the format string starts with the { character, the XAML parser will confuse it for a
markup extension. To avoid this ambiguity, prefix the format string with an empty
set of curly braces.
In this example, the StringFormat property combines the three bound values into a
single string that's displayed by the Label.
C#
new Binding("Employee1.Forename"),
new Binding("Employee1.MiddleName"),
new Binding("Employee1.Surname")
},
});
) Important
The number of parameters in a composite string format can't exceed the number of
child Binding objects in the MultiBinding .
When setting the Converter and StringFormat properties, the converter is applied to
the data value first, and then the StringFormat is applied.
For more information about string formatting in .NET MAUI, see String formatting.
that the converter did not produce a value. A MultiBinding will use its TargetNullValue
when the Convert method of an IMultiValueConverter instance returns null , which
indicates that the converter can't perform the conversion.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.NestedMultiBindingPage"
<ContentPage.Resources>
</ContentPage.Resources>
<CheckBox>
<CheckBox.IsChecked>
</MultiBinding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</ContentPage>
In this example, the MultiBinding object uses its AnyTrueMultiConverter instance to set
the CheckBox.IsChecked property to true , provided that all of the Binding objects in the
inner MultiBinding object evaluate to true , or if the Binding object in the outer
MultiBinding object evaluates to true . Otherwise, the CheckBox.IsChecked property is
set to false .
XAML
<ContentPage ...
xmlns:local="clr-namespace:DataBindingDemos">
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewExpanderControlTemplate">
BackgroundColor="{Binding CardColor}"
RowDefinitions="Auto,Auto"
Padding="8">
<local:Expander.IsVisible>
<MultiBinding Converter="{StaticResource
AllTrueConverter}">
</MultiBinding>
</local:Expander.IsVisible>
<Grid>
</Grid>
<Grid>
</Grid>
</local:Expander>
</ControlTemplate>
</ContentPage.Resources>
<StackLayout>
<controls:CardViewExpander BorderColor="DarkGray"
CardTitle="John Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource
CardViewExpanderControlTemplate}"
IsEnabled="True"
IsExpanded="True" />
</StackLayout>
</ContentPage>
In this example, the TemplatedParent relative binding mode is used to bind from within
a control template to the runtime object instance to which the template is applied. The
Expander , which is the root element of the ControlTemplate, has its BindingContext set
to the runtime object instance to which the template is applied. Therefore, the Expander
and its children resolve their binding expressions, and Binding objects, against the
properties of the CardViewExpander object. The MultiBinding uses the
AllTrueMultiConverter instance to set the Expander.IsVisible property to true
provided that the two Binding objects evaluate to true . Otherwise, the
Expander.IsVisible property is set to false .
For more information about relative bindings, see Relative bindings. For more
information about control templates, see Control templates.
Commanding
Article • 02/09/2023 • 13 minutes to read
In a .NET Multi-platform App UI (.NET MAUI) app that uses the Model-View-ViewModel
(MVVM) pattern, data bindings are defined between properties in the viewmodel, which
is typically a class that derives from INotifyPropertyChanged , and properties in the view,
which is typically the XAML file. Sometimes an app has needs that go beyond these
property bindings by requiring the user to initiate commands that affect something in
the viewmodel. These commands are generally signaled by button clicks or finger taps,
and traditionally they are processed in the code-behind file in a handler for the Clicked
event of the Button or the Tapped event of a TapGestureRecognizer.
To allow a data binding between a Button and a viewmodel, the Button defines two
properties:
To use the command interface, you define a data binding that targets the Command
property of the Button where the source is a property in the viewmodel of type
ICommand . The viewmodel contains code associated with that ICommand property that is
executed when the button is clicked. You can set the CommandParameter property to
arbitrary data to distinguish between multiple buttons if they are all bound to the same
ICommand property in the viewmodel.
Many other views also define Command and CommandParameter properties. All these
commands can be handled within a viewmodel using an approach that doesn't depend
on the user-interface object in the view.
ICommands
The ICommand interface is defined in the System.Windows.Input namespace, and
consists of two methods and one event:
C#
To use the command interface, your viewmodel should contain properties of type
ICommand :
C#
The viewmodel must also reference a class that implements the ICommand interface. In
the view, the Command property of a Button is bound to that property:
XAML
When the user presses the Button, the Button calls the Execute method in the ICommand
object bound to its Command property.
When the binding is first defined on the Command property of the Button, and when the
data binding changes in some way, the Button calls the CanExecute method in the
ICommand object. If CanExecute returns false , then the Button disables itself. This
indicates that the particular command is currently unavailable or invalid.
The Button also attaches a handler on the CanExecuteChanged event of ICommand . The
event is raised from within the viewmodel. When that event is raised, the Button calls
CanExecute again. The Button enables itself if CanExecute returns true and disables
2 Warning
Do not use the IsEnabled property of Button if you're using the command
interface.
When your viewmodel defines a property of type ICommand , the viewmodel must also
contain or reference a class that implements the ICommand interface. This class must
contain or reference the Execute and CanExecute methods, and fire the
CanExecuteChanged event whenever the CanExecute method might return a different
value. You can use the Command or Command<T> class included in .NET MAUI to implement
the ICommand interface. These classes allow you to specify the bodies of the Execute and
CanExecute methods in class constructors.
Tip
Basic commanding
The following examples demonstrate basic commands implemented in a viewmodel.
The PersonViewModel class defines three properties named Name , Age , and Skills that
define a person:
C#
string name;
double age;
string skills;
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
C#
PersonViewModel personEdit;
bool isEditing;
···
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
In this example, changes to the three ICommand properties and the Persons property do
not result in PropertyChanged events being raised. These properties are all set when the
class is first created and do not change.
The following example shows the XAML that consumes the PersonCollectionViewModel :
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.PersonEntryPage"
Title="Person Entry">
<ContentPage.BindingContext>
<local:PersonCollectionViewModel />
</ContentPage.BindingContext>
<Grid Margin="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Button Text="New"
Grid.Row="0"
Command="{Binding NewCommand}"
HorizontalOptions="Start" />
<Grid Grid.Row="1"
IsEnabled="{Binding IsEditing}">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<StackLayout Orientation="Horizontal"
Grid.Row="1" Grid.Column="1">
Maximum="100" />
VerticalOptions="Center" />
</StackLayout>
</Grid>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Button Text="Submit"
Grid.Column="0"
Command="{Binding SubmitCommand}"
VerticalOptions="Center" />
<Button Text="Cancel"
Grid.Column="1"
Command="{Binding CancelCommand}"
VerticalOptions="Center" />
</Grid>
<ListView Grid.Row="3"
</Grid>
</ContentPage>
Command property bound to the NewCommand property in the viewmodel, an entry form
The following screenshot shows the Submit button enabled after an age has been set:
When the user first presses the New button, this enables the entry form but disables the
New button. The user then enters a name, age, and skills. At any time during the editing,
the user can press the Cancel button to start over. Only when a name and a valid age
have been entered is the Submit button enabled. Pressing this Submit button transfers
the person to the collection displayed by the ListView. After either the Cancel or Submit
button is pressed, the entry form is cleared and the New button is enabled again.
All the logic for the New, Submit, and Cancel buttons is handled in
PersonCollectionViewModel through definitions of the NewCommand , SubmitCommand , and
CancelCommand properties. The constructor of the PersonCollectionViewModel sets these
A constructor of the Command class allows you to pass arguments of type Action and
Func<bool> corresponding to the Execute and CanExecute methods. This action and
C#
public class PersonCollectionViewModel : INotifyPropertyChanged
···
public PersonCollectionViewModel()
execute: () =>
PersonEdit.PropertyChanged += OnPersonEditPropertyChanged;
IsEditing = true;
RefreshCanExecutes();
},
canExecute: () =>
return !IsEditing;
});
···
(SubmitCommand as Command).ChangeCanExecute();
void RefreshCanExecutes()
(NewCommand as Command).ChangeCanExecute();
(SubmitCommand as Command).ChangeCanExecute();
(CancelCommand as Command).ChangeCanExecute();
···
When the user clicks the New button, the execute function passed to the Command
constructor is executed. This creates a new PersonViewModel object, sets a handler on
that object's PropertyChanged event, sets IsEditing to true , and calls the
RefreshCanExecutes method defined after the constructor.
Besides implementing the ICommand interface, the Command class also defines a method
named ChangeCanExecute . A viewmodel should call ChangeCanExecute for an ICommand
property whenever anything happens that might change the return value of the
CanExecute method. A call to ChangeCanExecute causes the Command class to fire the
CanExecuteChanged method. The Button has attached a handler for that event and
responds by calling CanExecute again, and then enabling itself based on the return value
of that method.
When the execute method of NewCommand calls RefreshCanExecutes , the NewCommand
property gets a call to ChangeCanExecute , and the Button calls the canExecute method,
which now returns false because the IsEditing property is now true .
The PropertyChanged handler for the new PersonViewModel object calls the
ChangeCanExecute method of SubmitCommand :
C#
···
public PersonCollectionViewModel()
···
execute: () =>
Persons.Add(PersonEdit);
PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
PersonEdit = null;
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
PersonEdit.Age > 0;
});
···
···
The canExecute function for SubmitCommand is called every time there's a property
changed in the PersonViewModel object being edited. It returns true only when the
Name property is at least one character long, and Age is greater than 0. At that time, the
Submit button becomes enabled.
The execute function for Submit removes the property-changed handler from the
PersonViewModel , adds the object to the Persons collection, and returns everything to its
initial state.
The execute function for the Cancel button does everything that the Submit button
does except add the object to the collection:
C#
···
public PersonCollectionViewModel()
···
execute: () =>
PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
PersonEdit = null;
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
return IsEditing;
});
···
The canExecute method returns true at any time a PersonViewModel is being edited.
7 Note
You can continue to use the Command class for these shared ICommand properties. The
class defines an alternative constructor that accepts execute and canExecute methods
with parameters of type Object . This is how the CommandParameter is passed to these
methods. However, when specifying a CommandParameter , it's easiest to use the generic
Command<T> class to specify the type of the object set to CommandParameter . The execute
and canExecute methods that you specify have parameters of that type.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.DecimalKeypadPage"
Title="Decimal Keyboard">
<ContentPage.BindingContext>
<local:DecimalKeypadViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<Style TargetType="Button">
</Style>
</ContentPage.Resources>
<Grid WidthRequest="240"
HeightRequest="480"
ColumnSpacing="2"
RowSpacing="2"
HorizontalOptions="Center"
VerticalOptions="Center">
Margin="0,0,10,0"
FontSize="32"
LineBreakMode="HeadTruncation"
VerticalTextAlignment="Center"
HorizontalTextAlignment="End" />
<Button Text="CLEAR"
<Button Text="⇦"
Grid.Row="1" Grid.Column="2"
<Button Text="7"
Grid.Row="2" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="7" />
<Button Text="8"
Grid.Row="2" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="8" />
<Button Text="9"
Grid.Row="2" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="9" />
<Button Text="4"
Grid.Row="3" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="4" />
<Button Text="5"
Grid.Row="3" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="5" />
<Button Text="6"
Grid.Row="3" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="6" />
<Button Text="1"
Grid.Row="4" Grid.Column="0"
Command="{Binding DigitCommand}"
CommandParameter="1" />
<Button Text="2"
Grid.Row="4" Grid.Column="1"
Command="{Binding DigitCommand}"
CommandParameter="2" />
<Button Text="3"
Grid.Row="4" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="3" />
<Button Text="0"
Command="{Binding DigitCommand}"
CommandParameter="0" />
<Button Text="·"
Grid.Row="5" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="." />
</Grid>
</ContentPage>
C#
···
private set
if (entry != value)
entry = value;
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Entry"));
get
return entry;
The button corresponding to the ClearCommand is always enabled and sets the entry
back to "0":
C#
···
public DecimalKeypadViewModel()
execute: () =>
Entry = "0";
RefreshCanExecutes();
});
···
void RefreshCanExecutes()
((Command)BackspaceCommand).ChangeCanExecute();
((Command)DigitCommand).ChangeCanExecute();
···
C#
···
public DecimalKeypadViewModel()
···
execute: () =>
if (Entry == "")
Entry = "0";
RefreshCanExecutes();
},
canExecute: () =>
···
···
The logic for the execute function for the Backspace button ensures that the Entry is at
least a string of "0".
The DigitCommand property is bound to 11 buttons, each of which identifies itself with
the CommandParameter property. The DigitCommand is set to an instance of the
Command<T> class. When using the commanding interface with XAML, the
CommandParameter properties are usually strings, which is type of the generic argument.
The execute and canExecute functions then have arguments of type string :
C#
···
public DecimalKeypadViewModel()
···
Entry += arg;
Entry = Entry.Substring(1);
RefreshCanExecutes();
},
});
···
The execute method appends the string argument to the Entry property. However, if the
result begins with a zero (but not a zero and a decimal point) then that initial zero must
be removed using the Substring function. The canExecute method returns false only if
the argument is the decimal point (indicating that the decimal point is being pressed)
and Entry already contains a decimal point. All the execute methods call
RefreshCanExecutes , which then calls ChangeCanExecute for both DigitCommand and
ClearCommand . This ensures that the decimal point and backspace buttons are enabled or
.NET Multi-platform App UI (.NET MAUI) data bindings have two main issues:
1. Ensure that XAML compilation is enabled. For more information about XAML
compilation, see XAML Compilation.
2. Set an x:DataType attribute on a VisualElement to the type of the object that the
VisualElement and its children will bind to.
7 Note
It's recommended to set the x:DataType attribute at the same level in the view
hierarchy as the BindingContext is set. However, this attribute can be re-defined at
any location in a view hierarchy.
To use compiled bindings, the x:DataType attribute must be set to a string literal, or a
type using the x:Type markup extension. At XAML compile time, any invalid binding
expressions will be reported as build errors. However, the XAML compiler will only
report a build error for the first invalid binding expression that it encounters. Any valid
binding expressions that are defined on the VisualElement or its children will be
compiled, regardless of whether the BindingContext is set in XAML or code. Compiling a
binding expression generates compiled code that will get a value from a property on the
source, and set it on the property on the target that's specified in the markup. In
addition, depending on the binding expression, the generated code may observe
changes in the value of the source property and refresh the target property, and may
push changes from the target back to the source.
) Important
Compiled bindings are disabled for any binding expressions that define the Source
property. This is because the Source property is always set using the x:Reference
markup extension, which can't be resolved at compile time.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.CompiledColorSelectorPage"
x:DataType="local:HslColorViewModel"
<ContentPage.BindingContext>
</ContentPage.BindingContext>
...
<StackLayout>
... />
<StackLayout Margin="10, 0">
</StackLayout>
</StackLayout>
</ContentPage>
The ContentPage instantiates the HslColorViewModel and initializes the Color property
within property element tags for the BindingContext property. The ContentPage also
defines the x:DataType attribute as the viewmodel type, indicating that any binding
expressions in the ContentPage view hierarchy will be compiled. This can be verified by
changing any of the binding expressions to bind to a non-existent viewmodel property,
which will result in a build error. While this example sets the x:DataType attribute to a
string literal, it can also be set to a type with the x:Type markup extension. For more
information about the x:Type markup extension, see x:Type Markup Extension.
) Important
The BoxView, Label elements, and Slider views inherit the binding context from the
ContentPage. These views are all binding targets that reference source properties in the
viewmodel. For the BoxView.Color property, and the Label.Text property, the data
bindings are OneWay – the properties in the view are set from the properties in the
viewmodel. However, the Slider.Value property uses a TwoWay binding. This allows
each Slider to be set from the viewmodel, and also for the viewmodel to be set from
each Slider.
When the example is first run, the BoxView, Label elements, and Slider elements are all
set from the viewmodel based on the initial Color property set when the viewmodel
was instantiated. As the sliders are manipulated, the BoxView and Label elements are
updated accordingly:
For more information about this color selector, see ViewModels and property-change
notifications.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.CompiledColorListPage"
<Grid>
...
<ListView x:Name="colorListView"
ItemsSource="{x:Static local:NamedColor.All}"
... >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:NamedColor">
<ViewCell>
<StackLayout Orientation="Horizontal">
... />
... />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
... />
</Grid>
</ContentPage>
Colors class, and to store them with their names in a collection that is accessible from
the static All property. Therefore, the ListView is filled with all of the NamedColor
instances. For each item in the ListView, the binding context for the item is set to a
NamedColor object. The BoxView and Label elements in the ViewCell are bound to
NamedColor properties.
When the example is first run, the ListView is populated with NamedColor instances.
When an item in the ListView is selected, the BoxView.Color property is set to the color
of the selected item in the ListView:
Selecting other items in the ListView updates the color of the BoxView.
Careful structuring of x:DataType attributes can therefore lead to a page using compiled
and classic bindings. Alternatively, the x:DataType attribute can be re-defined at any
point in a view hierarchy to null using the x:Null markup extension. Doing this
indicates that any binding expressions within the view hierarchy will use classic bindings.
The following example demonstrates this approach:
XAML
<StackLayout x:DataType="local:HslColorViewModel">
<StackLayout.BindingContext>
</StackLayout.BindingContext>
VerticalOptions="FillAndExpand" />
<StackLayout x:DataType="{x:Null}"
Margin="10, 0">
</StackLayout>
</StackLayout>
The root StackLayout sets the x:DataType attribute to be the HslColorViewModel type,
indicating that any binding expression in the root StackLayout view hierarchy will be
compiled. However, the inner StackLayout redefines the x:DataType attribute to null
with the x:Null markup expression. Therefore, the binding expressions within the inner
StackLayout use classic bindings. Only the BoxView, within the root StackLayout view
hierarchy, uses compiled bindings.
For more information about the x:Null markup expression, see x:Null Markup
Extension.
Performance
Compiled bindings improve data binding performance, with the performance benefit
varying:
a classic binding.
A compiled binding that doesn't use property-change notification (i.e. a OneTime
binding) is resolved approximately 20 times quicker than a classic binding.
Setting the BindingContext on a compiled binding that uses property change
notification (i.e. a OneWay , OneWayToSource , or TwoWay binding) is approximately 5
times quicker than setting the BindingContext on a classic binding.
Setting the BindingContext on a compiled binding that doesn't use property
change notification (i.e. a OneTime binding) is approximately 7 times quicker than
setting the BindingContext on a classic binding.
A .NET Multi-platform App UI (.NET MAUI) drag and drop gesture recognizer enables
items, and their associated data packages, to be dragged from one onscreen location to
another location using a continuous gesture. Drag and drop can take place in a single
application, or it can start in one application and end in another.
The drag source, which is the element on which the drag gesture is initiated, can provide
data to be transferred by populating a data package object. When the drag source is
released, drop occurs. The drop target, which is the element under the drag source, then
processes the data package.
) Important
Enable drag
In .NET MAUI, drag gesture recognition is provided by the DragGestureRecognizer class.
This class defines the following properties:
CanDrag, of type bool , which indicates whether the element the gesture
recognizer is attached to can be a drag source. The default value of this property is
true .
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The DragStartingEventArgs object that accompanies the DragStarting event defines the
following properties:
Handled, of type bool , indicates whether the event handler has handled the event
or whether .NET MAUI should continue its own processing.
Cancel, of type bool , indicates whether the event should be canceled.
Data, of type DataPackage, indicates the data package that accompanies the drag
source. This is a read-only property.
XAML
<Image Source="monkeyface.png">
<Image.GestureRecognizers>
<DragGestureRecognizer />
</Image.GestureRecognizers>
</Image>
Text controls. Text values can be dragged from CheckBox, DatePicker, Editor, Entry,
Label, RadioButton, Switch, and TimePicker objects.
Image controls. Images can be dragged from Button, Image, and ImageButton
controls.
The following table shows the properties that are read, and any conversion that's
attempted, when a drag is initiated on a text control:
Editor Text
Entry Text
Label Text
For content other than text and images, you'll need to build a data package yourself.
Data packages are represented by the DataPackage class, which defines the following
properties:
XAML
<Path Stroke="Black"
StrokeThickness="4">
<Path.GestureRecognizers>
</Path.GestureRecognizers>
<Path.Data>
</Path.Data>
</Path>
C#
The DragStartingEventArgs object that accompanies the DragStarting event has a Data
property, of type DataPackage . In this example, the Text property of the DataPackage
object is set to a string . The DataPackage can then be accessed on drop, to retrieve the
string .
Store data in the property bag
Any data, including images and text, can be associated with a drag source by storing the
data in the DataPackage.Properties collection. You can add the data in the handler for
the DragStarting event.
XAML
<Rectangle Stroke="Red"
Fill="DarkBlue"
StrokeThickness="4"
HeightRequest="200"
WidthRequest="200">
<Rectangle.GestureRecognizers>
</Rectangle.GestureRecognizers>
</Rectangle>
C#
The DragStartingEventArgs object that accompanies the DragStarting event has a Data
property, of type DataPackage . The Properties collection of the DataPackage object,
which is a Dictionary<string, object> collection, can be modified to store any required
data. In this example, the Properties dictionary is modified to store a Square object
that represents the size of the Rectangle against a "Square" key.
Enable drop
In .NET MAUI, drop gesture recognition is provided by the DropGestureRecognizer class.
This class defines the following properties:
AllowDrop, of type bool , which indicates whether the element the gesture
recognizer is attached to can be a drop target. The default value of this property is
true .
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The DropGestureRecognizer class also defines DragOver, DragLeave, and Drop events
that fire if the AllowDrop property is true . When a DropGestureRecognizer recognizes a
drag source over the drop target, it executes the DragOverCommand and invokes the
DragOver event. Then, if the drag source is dragged off the drop target, the
DropGestureRecognizer executes the DragLeaveCommand and invokes the DragLeave
event. Finally, when the DropGestureRecognizer recognizes a drop gesture over the
drop target, it executes the DropCommand and invokes the Drop event.
The DragEventArgs class, which accompanies the DragOver and DragLeave events,
defines the following properties:
Data, of type DataPackage , which contains the data associated with the drag
source. This property is read-only.
AcceptedOperation, of type DataPackageOperation , which specifies which
operations are allowed by the drop target.
For information about the DataPackageOperation enumeration, see Handle the DragOver
event.
The DropEventArgs class that accompanies the Drop event defines the following
properties:
XAML
<Image BackgroundColor="Silver"
HeightRequest="300"
WidthRequest="250">
<Image.GestureRecognizers>
<DropGestureRecognizer />
</Image.GestureRecognizers>
</Image>
In this example, when a drag source is dropped on the Image drop target, the drag
source will be copied to the drop target if the drag source is an ImageSource . .NET MAUI
automatically copies dragged images, and text, to compatible drop targets.
Copy , indicates that the drag source content will be copied to the drop target.
) Important
XAML
<Image BackgroundColor="Silver"
HeightRequest="300"
WidthRequest="250">
<Image.GestureRecognizers>
</Image.GestureRecognizers>
</Image>
C#
e.AcceptedOperation = DataPackageOperation.None;
Text controls. Text values can be dropped onto CheckBox, DatePicker, Editor, Entry,
Label, RadioButton, Switch, and TimePicker objects.
Image controls. Images can be dropped onto Button, Image, and ImageButton
controls.
The following table shows the properties that are set and any conversion that's
attempted when a text-based drag source is dropped on a text control:
Editor Text
Entry Text
Control Property Conversion
Label Text
For content other than text and images, you'll need to process the data package
yourself.
The DropEventArgs class that accompanies the Drop event defines a Data property, of
type DataPackageView . This property represents a read-only version of the data package.
The following example shows a Drop event handler that retrieves text from the data
package for a Path:
C#
In this example, text data is retrieved from the data package using the GetTextAsync
method. An action based on the text value can then be taken.
The following example shows a Drop event handler that retrieves data from the property
bag of a data package for a Rectangle:
C#
In this example, the Square object is retrieved from the property bag of the data
package, by specifying the "Square" dictionary key. An action based on the retrieved
value can then be taken.
Recognize a pan gesture
Article • 02/09/2023 • 3 minutes to read
A .NET Multi-platform App UI (.NET MAUI) pan gesture recognizer detects the
movement of fingers around the screen and can be used to apply that movement to
content. A typical scenario for the pan gesture is to horizontally and vertically pan an
image, so that all of the image content can be viewed when it's being displayed in a
viewport smaller than the image dimensions. This is accomplished by moving the image
within the viewport.
The PanGestureRecognizer class also defines a PanUpdated event that's raised when the
detected pan gesture changes. The PanUpdatedEventArgs object that accompanies this
event defines the following properties:
GestureId, of type int , which represents the id of the gesture that raised the
event.
StatusType, of type GestureStatus , which indicates if the event has been raised for
a newly started gesture, a running gesture, a completed gesture, or a canceled
gesture.
TotalX, of type double , which indicates the total change in the X direction since the
beginning of the gesture.
TotalY, of type double , which indicates the total change in the Y direction since the
beginning of the gesture.
Create a PanGestureRecognizer
To make a View recognize a pan gesture, create a PanGestureRecognizer object, handle
the PanUpdated event, and add the new gesture recognizer to the GestureRecognizers
collection on the view. The following code example shows a PanGestureRecognizer
attached to an Image:
XAML
<Image Source="monkey.jpg">
<Image.GestureRecognizers>
</Image.GestureRecognizers>
</Image>
The code for the OnPanUpdated event handler should be added to the code-behind file:
C#
C#
};
image.GestureRecognizers.Add(panGesture);
C#
double x, y;
public PanContainer()
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);
switch (e.StatusType)
case GestureStatus.Running:
break;
case GestureStatus.Completed:
x = Content.TranslationX;
y = Content.TranslationY;
break;
In this example, the OnPanUpdated method updates the viewable content of the wrapped
view, based on the user's pan gesture. This is achieved by using the values of the TotalX
and TotalY properties of the PanUpdatedEventArgs instance to calculate the direction
and distance of the pan. The DeviceDisplay.MainDisplayInfo.Width and
DeviceDisplay.MainDisplayInfo.Height properties provide the screen width and screen
height values of the device. The wrapped user element is then panned by setting its
TranslationX and TranslationY properties to the calculated values. When panning
content in an element that does not occupy the full screen, the height and width of the
viewport can be obtained from the element's Height and Width properties.
The PanContainer class can be wrapped around a View so that a recognized pan gesture
will pan the wrapped view. The following XAML example shows the PanContainer
wrapping an Image:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PanGesture"
x:Class="PanGesture.MainPage">
<AbsoluteLayout>
<local:PanContainer>
</local:PanContainer>
</AbsoluteLayout>
</ContentPage>
In this example, when the Image receives a pan gesture, the displayed image will be
panned.
Recognize a pinch gesture
Article • 02/09/2023 • 3 minutes to read
A .NET Multi-platform App UI (.NET MAUI) pinch gesture recognizer is used for
performing interactive zoom. A common scenario for the pinch gesture is to perform
interactive zoom of an image at the pinch location. This is accomplished by scaling the
content of the viewport.
Scale, of type double , which indicates the relative size of the pinch gesture since
the last update was received.
ScaleOrigin, of type Point , which indicates the updated origin of the pinch's
gesture.
Status, of type GestureStatus, which indicates if the event has been raised for a
newly started gesture, a running gesture, a completed gesture, or a canceled
gesture.
Create a PinchGestureRecognizer
To make a View recognize a pinch gesture, create a PinchGestureRecognizer object,
handle the PinchUpdated event, and add the new gesture recognizer to the
GestureRecognizers collection on the view. The following code example shows a
XAML
<Image Source="waterfront.jpg">
<Image.GestureRecognizers>
</Image.GestureRecognizers>
</Image>
The code for the OnPinchUpdated event handler should be added to the code-behind
file:
C#
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
C#
};
image.GestureRecognizers.Add(pinchGesture);
C#
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
public PinchToZoomContainer()
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
if (e.Status == GestureStatus.Started)
// and zero the components for the center point of the translate
transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
if (e.Status == GestureStatus.Running)
if (e.Status == GestureStatus.Completed)
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
In this example, the OnPinchUpdated method updates the zoom level of the wrapped
view, based on the user's pinch gesture. This is achieved by using the values of the
Scale, ScaleOrigin and Status properties of the PinchGestureUpdatedEventArgs object to
calculate the scale factor to be applied at the origin of the pinch gesture. The wrapped
view is then zoomed at the origin of the pinch gesture by setting its TranslationX ,
TranslationY , and Scale properties to the calculated values.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PinchGesture;assembly=PinchGesture"
x:Class="PinchGesture.HomePage">
<Grid>
<local:PinchToZoomContainer>
</local:PinchToZoomContainer>
</Grid>
</ContentPage>
In this example, when the Image receives a pinch gesture, the displayed image will be
zoomed-in or out.
Recognize a pointer gesture
Article • 02/09/2023 • 3 minutes to read
A .NET Multi-platform App UI (.NET MAUI) pointer gesture recognizer detects when the
pointer enters, exits, and moves within a view and is implemented with the
PointerGestureRecognizer class. This class defines the following properties:
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
PointerEntered, that's raised when the pointer enters the bounding area of the
view.
PointerExited, that's raised when the pointer that's in the view's bounding area
leaves that bounding area.
PointerMoved, that's raised when the pointer moves while remaining within the
bounding area of the view.
) Important
Create a PointerGestureRecognizer
To make a View recognize pointer gestures, create a PointerGestureRecognizer object,
handle the required events, and add the gesture recognizer to the GestureRecognizers
collection on the view.
Alternatively, create a PointerGestureRecognizer object, and bind
the required commands to ICommand implementations, and add the gesture recognizer
to the GestureRecognizers collection on the view.
XAML
<Image Source="dotnet_bot.png">
<Image.GestureRecognizers>
<PointerGestureRecognizer PointerEntered="OnPointerEntered"
PointerExited="OnPointerExited"
PointerMoved="OnPointerMoved" />
</Image.GestureRecognizers>
</Image>
The code for the event handlers should be added to the code-behind file:
C#
C#
};
};
};
image.GestureRecognizers.Add(pointerGestureRecognizer);
C#
The Element? argument defines the element the position should be obtained relative to.
Supplying a null value as this argument means that the GetPosition method returns a
Point? object that defines the position of the pointer gesture inside the window.
Recognize a swipe gesture
Article • 02/09/2023 • 4 minutes to read
A .NET Multi-platform App UI (.NET MAUI) swipe gesture recognizer detects when a
finger is moved across the screen in a horizontal or vertical direction, and is often used
to initiate navigation through content.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The SwipeGestureRecognizer also defines a Swiped event that's raised when a swipe is
recognized. The SwipedEventArgs object that accompanies the Swiped event defines the
following properties:
Create a SwipeGestureRecognizer
To make a View recognize a swipe gesture, create a SwipeGestureRecognizer object, set
the Direction property to a SwipeDirection enumeration value ( Left , Right , Up , or
Down ), optionally set the Threshold property, handle the Swiped event, and add the new
XAML
<BoxView Color="Teal" ...>
<BoxView.GestureRecognizers>
</BoxView.GestureRecognizers>
</BoxView>
C#
leftSwipeGesture.Swiped += OnSwiped;
boxView.GestureRecognizers.Add(leftSwipeGesture);
XAML
Similarly, swipes that occur on the vertical axis can be recognized by setting the
Direction property to Up and Down :
C#
XAML
<BoxView Color="Teal" ...>
<BoxView.GestureRecognizers>
</BoxView.GestureRecognizers>
</BoxView>
C#
leftSwipeGesture.Swiped += OnSwiped;
rightSwipeGesture.Swiped += OnSwiped;
upSwipeGesture.Swiped += OnSwiped;
downSwipeGesture.Swiped += OnSwiped;
boxView.GestureRecognizers.Add(leftSwipeGesture);
boxView.GestureRecognizers.Add(rightSwipeGesture);
boxView.GestureRecognizers.Add(upSwipeGesture);
boxView.GestureRecognizers.Add(downSwipeGesture);
Respond to a swipe
A recognized swipe can be responded to by a handler for the Swiped event:
C#
switch (e.Direction)
case SwipeDirection.Left:
// Handle the swipe
break;
case SwipeDirection.Right:
break;
case SwipeDirection.Up:
break;
case SwipeDirection.Down:
// Handle the swipe
break;
The SwipedEventArgs can be examined to determine the direction of the swipe, with
custom logic responding to the swipe as required. The direction of the swipe can be
obtained from the Direction property of the event arguments, which will be set to one of
the values of the SwipeDirection enumeration. In addition, the event arguments also
have a Parameter property that will be set to the value of the CommandParameter
property, if defined.
C#
public SwipeContainer()
GestureRecognizers.Add(GetSwipeGestureRecognizer(SwipeDirection.Left));
GestureRecognizers.Add(GetSwipeGestureRecognizer(SwipeDirection.Right));
GestureRecognizers.Add(GetSwipeGestureRecognizer(SwipeDirection.Up));
GestureRecognizers.Add(GetSwipeGestureRecognizer(SwipeDirection.Down));
SwipeGestureRecognizer GetSwipeGestureRecognizer(SwipeDirection
direction)
return swipe;
The SwipeContainer class creates SwipeGestureRecognizer objects for all four swipe
directions, and attaches Swipe event handlers. These event handlers invoke the Swipe
event defined by the SwipeContainer .
The following XAML code example shows the SwipeContainer class wrapping a BoxView:
XAML
<StackLayout>
</local:SwipeContainer>
</StackLayout>
In this example, when the BoxView receives a swipe gesture, the Swiped event in the
SwipeGestureRecognizer is raised. This is handled by the SwipeContainer class, which
raises its own Swipe event. This Swipe event is handled on the page. The
SwipedEventArgs can then be examined to determine the direction of the swipe, with
custom logic responding to the swipe as required.
C#
};
stackLayout.Add(swipeContainer);
A .NET Multi-platform App UI (.NET MAUI) tap gesture recognizer is used for tap
detection and is implemented with the TapGestureRecognizer class. This class defines
the following properties:
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The TapGestureRecognizer class also defines a Tapped event that's raised when a tap is
recognized. The TappedEventArgs object that accompanies the Tapped event defines a
Parameter property of type object that indicates the value passed by the
CommandParameter property, if defined. The TappedEventArgs object also defines a
2 Warning
Create a TapGestureRecognizer
To make a View recognize a tap gesture, create a TapGestureRecognizer object, handle
the Tapped event, and add the new gesture recognizer to the GestureRecognizers
collection on the view. The following code example shows a TapGestureRecognizer
attached to an Image:
XAML
<Image Source="dotnet_bot.png">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapGestureRecognizerTapped"
NumberOfTapsRequired="2" />
</Image.GestureRecognizers>
</Image>
The code for the OnTapGestureRecognizerTapped event handler should be added to the
code-behind file:
C#
C#
};
image.GestureRecognizers.Add(tapGestureRecognizer);
By default the Image will respond to single taps. When the NumberOfTapsRequired
property is set to greater than one, the event handler will only be executed if the taps
occur within a set period of time. If the second (or subsequent) taps don't occur within
that period, they're effectively ignored.
The following example shows a TapGestureRecognizer that detects taps with the
secondary mouse button:
XAML
<Image Source="dotnet_bot.png">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapGestureRecognizerTapped"
Buttons="Secondary" />
</Image.GestureRecognizers>
</Image>
The event handler for the Tapped event can determine which button triggered the
gesture:
C#
if (args.Buttons == ButtonsMask.Secondary)
// Do something
C#
Buttons = ButtonsMask.Secondary
};
if (args.Buttons == ButtonsMask.Secondary)
// Do something
};
image.GestureRecognizers.Add(tapGestureRecognizer);
2 Warning
XAML
<TapGestureRecognizer Tapped="OnTapGestureRecognizerTapped"
Buttons="Primary,Secondary" />
C#
};
C#
The Element? argument defines the element the position should be obtained relative to.
Supplying a null value as this argument means that the GetPosition method returns a
Point? object that defines the position of the tap gesture inside the window.
Bindable properties
Article • 12/23/2022 • 7 minutes to read
.NET Multi-platform App UI (.NET MAUI) bindable properties extend Common Language
Runtime (CLR) property functionality by backing a property with a BindableProperty
type, instead of with a field. The purpose of bindable properties is to provide a property
system that supports data binding, styles, templates, and values set through parent-
child relationships. In addition, bindable properties can provide default values, validation
of property values, and callbacks that monitor property changes.
that is the identifier of the bindable property. For example, the corresponding bindable
property identifier for the Label.Text property is Label.TextProperty .
All BindableProperty instances must be created on the UI thread. This means that only
code that runs on the UI thread can get or set the value of a bindable property.
However, BindableProperty instances can be accessed from other threads by marshaling
to the UI thread.
Create a property
To create a BindableProperty instance, the containing class must derive from the
BindableObject class. However, the BindableObject class is high in the class hierarchy, so
the majority of classes used for UI functionality support bindable properties.
) Important
The naming convention for bindable properties is that the bindable property
identifier must match the property name specified in the Create method, with
"Property" appended to it.
The following code shows an example of a bindable property, with an identifier and
values for the four required parameters:
C#
The binding mode. This is used to specify the direction in which property value
changes will propagate. In the default binding mode, changes will propagate from
the source to the target.
A validation delegate that will be invoked when the property value is set. For more
information, see Validation callbacks.
A property changed delegate that will be invoked when the property value has
changed. For more information, see Detect property changes.
A property changing delegate that will be invoked when the property value will
change. This delegate has the same signature as the property changed delegate.
A coerce value delegate that will be invoked when the property value has changed.
For more information, see Coerce value callbacks.
A Func that's used to initialize a default property value. For more information, see
Create a default value with a Func.
Create accessors
Property accessors are required to use property syntax to access a bindable property.
The Get accessor should return the value that's contained in the corresponding
bindable property. This can be achieved by calling the GetValue method, passing in the
bindable property identifier on which to get the value, and then casting the result to the
required type. The Set accessor should set the value of the corresponding bindable
property. This can be achieved by calling the SetValue method, passing in the bindable
property identifier on which to set the value, and the value to set.
The following code example shows accessors for the IsExpanded bindable property:
C#
The following code example demonstrates a XAML namespace for a custom type that
contains a bindable property, which is defined within the same assembly as the
application code that's referencing the custom type:
XAML
...
</ContentPage>
The namespace declaration is used when setting the IsExpanded bindable property, as
demonstrated in the following XAML code example:
XAML
<Expander IsExpanded="true">
...
</Expander>
C#
IsExpanded = true
};
Advanced scenarios
When creating a BindableProperty instance, there are a number of optional parameters
that can be set to enable advanced bindable property scenarios. This section explores
these scenarios.
The following code example shows how the IsExpanded bindable property registers the
OnIsExpandedChanged method as a property-changed callback method:
C#
public static readonly BindableProperty IsExpandedProperty =
BindableProperty.Create(nameof(IsExpanded), typeof(bool),
typeof(Expander), false, propertyChanged: OnIsExpandedChanged);
...
Validation callbacks
A static validation callback method can be registered with a bindable property by
specifying the validateValue parameter for the BindableProperty.Create method. The
specified callback method will be invoked when the value of the bindable property is
set.
The following code example shows how the Angle bindable property registers the
IsValidValue method as a validation callback method:
C#
...
double result;
Validation callbacks are provided with a value, and should return true if the value is
valid for the property, otherwise false . An exception will be raised if a validation
callback returns false , which you should handle. A typical use of a validation callback
method is constraining the values of integers or doubles when the bindable property is
set. For example, the IsValidValue method checks that the property value is a double
within the range 0 to 360.
Coerce value callbacks
A static coerce value callback method can be registered with a bindable property by
specifying the coerceValue parameter for the BindableProperty.Create method. The
specified callback method will be invoked when the value of the bindable property
changes.
) Important
The BindableObject type has a CoerceValue method that can be called to force a
reevaluation of the value of its BindableProperty argument, by invoking its coerce
value callback.
Coerce value callbacks are used to force a reevaluation of a bindable property when the
value of the property changes. For example, a coerce value callback can be used to
ensure that the value of one bindable property is not greater than the value of another
bindable property.
The following code example shows how the Angle bindable property registers the
CoerceAngle method as a coerce value callback method:
C#
BindableProperty.Create("MaximumAngle", typeof(double),
typeof(MainPage), 360.0, propertyChanged: ForceCoerceValue);
...
input = page.MaximumAngle;
return input;
bindable.CoerceValue(AngleProperty);
The CoerceAngle method checks the value of the MaximumAngle property, and if the
Angle property value is greater than it, it coerces the value to the MaximumAngle
property value. In addition, when the MaximumAngle property changes the coerce value
callback is invoked on the Angle property by calling the CoerceValue method.
C#
.NET Multi-platform App UI (.NET MAUI) attached properties enable an object to assign
a value for a property that its own class doesn't define. For example, child elements can
use attached properties to inform their parent element of how they are to be presented
in the user interface. The Grid layout enables the row and column of a child to be
specified by setting the Grid.Row and Grid.Column attached properties. Grid.Row and
Grid.Column are attached properties because they are set on elements that are children
of a Grid, rather than on the Grid itself.
When there's a need to have a property setting mechanism available for classes
other than the defining class.
When the class represents a service that needs to be easily integrated with other
classes.
Create a property
When creating an attached property for use on other types, the class where the property
is created does not have to derive from BindableObject. However, the target property
for accessors should be of, or derive from, BindableObject.
The naming convention for attached properties is that the attached property
identifier must match the property name specified in the CreateAttached method,
with "Property" appended to it.
C#
This creates an attached property named HasShadowProperty , of type bool . The property
is owned by the Shadow class, and has a default value of false .
For more information about creating bindable properties, including parameters that can
be specified during creation, see Create a bindable property.
Create accessors
Static Get PropertyName and Set PropertyName methods are required as accessors for
the attached property, otherwise the property system will be unable to use the attached
property. The Get PropertyName accessor should conform to the following signature:
C#
The Get PropertyName accessor should return the value that's contained in the
corresponding BindableProperty field for the attached property. This can be achieved by
calling the GetValue method, passing in the bindable property identifier on which to get
the value, and then casting the resulting value to the required type.
C#
The Set PropertyName accessor should set the value of the corresponding
BindableProperty field for the attached property. This can be achieved by calling the
SetValue method, passing in the bindable property identifier on which to set the value,
For both accessors, the target object should be of, or derive from, BindableObject.
The following code example shows accessors for the HasShadow attached property:
C#
The following example demonstrates a XAML namespace for a custom type that
contains an attached property, which is defined within the same assembly as the app
code that's referencing the custom type:
XAML
...
</ContentPage>
The namespace declaration is then used when setting the attached property on a
specific control, as demonstrated in the following XAML:
XAML
C#
XAML
</Style.Setters>
</Style>
The Style can be applied to a Label by setting its Style property to the Style instance
using the StaticResource markup extension, as demonstrated in the following code
example:
XAML
Advanced scenarios
When creating an attached property, there are some optional parameters that can be
set to enable advanced attached property scenarios. This includes detecting property
changes, validating property values, and coercing property values. For more information,
see Advanced scenarios.
Publish and subscribe to messages
Article • 11/08/2022 • 4 minutes to read
Events in .NET implement the publish-subscribe pattern, and are the most simple and
straightforward approach for a communication layer between components if loose
coupling is not required, such as a control and the page that contains it. However, the
publisher and subscriber lifetimes are coupled by object references to each other, and
the subscriber type must have a reference to the publisher type. This can create memory
management issues, especially when there are short lived objects that subscribe to an
event of a static or long-lived object. If the event handler isn't removed, the subscriber
will be kept alive by the reference to it in the publisher, and this will prevent or delay the
garbage collection of the subscriber.
The .NET Multi-platform App UI (.NET MAUI) MessagingCenter class implements the
publish-subscribe pattern, allowing message-based communication between
components that are inconvenient to link by object and type references. This mechanism
allows publishers and subscribers to communicate without having a reference to each
other, helping to reduce dependencies between them.
) Important
) Important
Internally, the MessagingCenter class uses weak references. This means that it will
not keep objects alive, and will allow them to be garbage collected. Therefore, it
should only be necessary to unsubscribe from a message when a class no longer
wishes to receive the message.
Publish a message
MessagingCenter messages are strings. Publishers notify subscribers of a message with
one of the MessagingCenter.Send overloads. The following code example publishes a Hi
message:
C#
MessagingCenter.Send<MainPage>(this, "Hi");
In this example the Send method specifies a generic argument that represents the
sender. To receive the message, a subscriber must also specify the same generic
argument, indicating that they are listening for a message from that sender. In addition,
this example specifies two method arguments:
C#
In this example, the Send method specifies two generic arguments. The first is the type
that's sending the message, and the second is the type of the payload data being sent.
To receive the message, a subscriber must also specify the same generic arguments. This
enables multiple messages that share a message identity but send different payload
data types to be received by different subscribers. In addition, this example specifies a
third method argument that represents the payload data to be sent to the subscriber. In
this case the payload data is a string .
The Send method will publish the message, and any payload data, using a fire-and-
forget approach. Therefore, the message is sent even if there are no subscribers
registered to receive the message. In this situation, the sent message is ignored.
Subscribe to a message
Subscribers can register to receive a message using one of the
MessagingCenter.Subscribe overloads. The following code example shows an example of
this:
C#
});
In this example, the Subscribe method subscribes the this object to Hi messages that
are sent by the MainPage type, and executes a callback delegate in response to receiving
the message. The callback delegate, specified as a lambda expression, could be code
that updates the UI, saves some data, or triggers some other operation.
7 Note
A subscriber might not need to handle every instance of a published message, and
this can be controlled by the generic type arguments that are specified on the
Subscribe method.
The following example shows how to subscribe to a message that contains payload
data:
C#
});
In this example, the Subscribe method subscribes to Hi messages that are sent by the
MainPage type, whose payload data is a string . A callback delegate is executed in
response to receiving such a message, that displays the payload data in an alert.
) Important
The delegate that's executed by the Subscribe method will be executed on the
same thread that publishes the message using the Send method.
C#
MessagingCenter.Unsubscribe<MainPage>(this, "Hi");
In this example, the Unsubscribe method unsubscribes the this object from the Hi
message sent by the MainPage type.
Messages containing payload data should be unsubscribed from using the Unsubscribe
overload that specifies two generic arguments:
C#
MessagingCenter.Unsubscribe<MainPage, string>(this, "Hi");
In this example, the Unsubscribe method unsubscribes the this object from the Hi
message sent by the MainPage type, whose payload data is a string .
Resource dictionaries
Article • 04/03/2023 • 8 minutes to read
XAML resources that are stored in a ResourceDictionary can be referenced and applied
to elements by using the StaticResource or DynamicResource markup extension. In C#,
resources can also be defined in a ResourceDictionary and then referenced and applied
to elements by using a string-based indexer.
Tip
Create resources
Every VisualElement derived object has a Resources property, which is a
ResourceDictionary that can contain resources. Similarly, an Application derived object
has a Resources property, which is a ResourceDictionary that can contain resources.
A .NET MAUI app can contain only a single class that derives from Application , but
often makes use of many classes that derive from VisualElement, including pages,
layouts, and views. Any of these objects can have its Resources property set to a
ResourceDictionary containing resources. Choosing where to put a particular
ResourceDictionary impacts where the resources can be used:
XAML
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResourceDictionaryDemo.App">
<Application.Resources>
<Thickness x:Key="PageMargin">20</Thickness>
<Color x:Key="AppBackgroundColor">AliceBlue</Color>
<Color x:Key="NavigationBarColor">#1976D2</Color>
<Color x:Key="NavigationBarTextColor">White</Color>
<Color x:Key="NormalTextColor">Black</Color>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor"
<Setter Property="BarTextColor"
</Style>
<Style TargetType="ContentPage"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
</Style>
</Application.Resources>
</Application>
In this example, the resource dictionary defines a Thickness resource, multiple Color
resources, and two implicit Style resources.
) Important
The following XAML example shows how to consume resources, and also define an
additional resource in a StackLayout:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResourceDictionaryDemo.MainPage"
Title="Main page">
Spacing="6">
<StackLayout.Resources>
<Style TargetType="Button">
</Style>
</StackLayout.Resources>
<Button Text="Navigate"
Clicked="OnNavigateButtonClicked" />
</StackLayout>
</ContentPage>
In this example, the ContentPage object consumes the implicit style defined in the
application level resource dictionary. The StackLayout object consumes the PageMargin
resource defined in the application level resource dictionary, while the Button object
consumes the implicit style defined in the StackLayout resource dictionary. This results in
the appearance shown in the following screenshot:
) Important
The requested key is checked for in the resource dictionary, if it exists, for the
element that sets the property. If the requested key is found, its value is returned
and the lookup process terminates.
If a match isn't found, the lookup process searches the visual tree upwards,
checking the resource dictionary of each parent element. If the requested key is
found, its value is returned and the lookup process terminates. Otherwise the
process continues upwards until the root element is reached.
If a match isn't found at the root element, the application level resource dictionary
is examined.
If a match still isn't found, a XamlParseException is thrown.
Override resources
When resources share keys, resources defined lower in the visual tree will take
precedence over those defined higher up. For example, setting an AppBackgroundColor
resource to AliceBlue at the application level will be overridden by a page level
AppBackgroundColor resource set to Teal . Similarly, a page level AppBackgroundColor
resource will be overridden by a layout or view level AppBackgroundColor resource.
7 Note
XAML
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<DataTemplate x:Key="PersonDataTemplate">
<ViewCell>
<Grid RowSpacing="6"
ColumnSpacing="6">
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
TextColor="{StaticResource NormalTextColor}"
FontAttributes="Bold" />
<Label Grid.Column="1"
Text="{Binding Age}"
<Label Grid.Column="2"
Text="{Binding Location}"
TextColor="{StaticResource NormalTextColor}"
HorizontalTextAlignment="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ResourceDictionary>
XAML
<ContentPage ...>
<ContentPage.Resources>
</ContentPage.Resources>
...
</ContentPage>
This syntax does not instantiate the MyResourceDictionary class. Instead, it references
the XAML file. For that reason, when setting the Source property, a code-behind file
isn't required, and the x:Class attribute can be removed from the root tag of the
MyResourceDictionary.xaml file.
) Important
2 Warning
The following code example shows two resource dictionaries being added to the
MergedDictionaries collection of a page level ResourceDictionary:
XAML
<ContentPage ...
xmlns:local="clr-namespace:ResourceDictionaryDemo"
xmlns:theme="clr-namespace:MyThemes;assembly=MyThemes">
<ContentPage.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<local:MyResourceDictionary />
<theme:DefaultTheme />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
In this example, a resource dictionary from the same assembly, and a resource dictionary
from an external assembly, are merged into the page level resource dictionary. In
addition, you can also add other ResourceDictionary objects within the
MergedDictionaries property-element tags, and other resources outside of those tags.
) Important
When merged ResourceDictionary resources share identical x:Key attribute values, .NET
MAUI uses the following resource precedence:
MergedDictionaries property.
Tip
XAML-based resource dictionaries that are backed by code-behind files can then be
consumed from C# by adding them to the MergedDictionaries collection of the resource
dictionary:
C#
Resources.MergedDictionaries.Add(new MyMauiApp.Resources.Styles.MyColors());
Resources.MergedDictionaries.Add(new MyMauiApp.Resources.Styles.MyStyles());
.NET Multi-platform App UI (.NET MAUI) Shell reduces the complexity of app
development by providing the fundamental features that most apps require, including:
1. FlyoutItem or TabBar. A FlyoutItem represents one or more items in the flyout, and
should be used when the navigation pattern for the app requires a flyout. A TabBar
represents the bottom tab bar, and should be used when the navigation pattern
for the app begins with bottom tabs and doesn't require a flyout. For more
information about flyout items, see .NET MAUI Shell flyout. For more information
about tab bars, see .NET MAUI Shell tabs.
2. Tab, which represents grouped content, navigable by bottom tabs. For more
information, see .NET MAUI Shell tabs.
3. ShellContent, which represents the ContentPage objects for each tab. For more
information, see .NET MAUI Shell pages.
These objects don't represent any user interface, but rather the organization of the app's
visual hierarchy. Shell will take these objects and produce the navigation user interface
for the content.
7 Note
In this example, some flyout items are duplicated as tab bar items. However, there are
also items that can only be accessed from the flyout. Selecting a flyout item results in
the bottom tab that represents the item being selected and displayed:
7 Note
When the flyout isn't open the bottom tab bar can be considered to be the top
level of navigation in the app.
Each tab on the tab bar displays a ContentPage. However, if a bottom tab contains more
than one page, the pages are navigable by the top tab bar:
Within each tab, additional ContentPage objects that are known as detail pages, can be
navigated to:
Shell uses a URI-based navigation experience that uses routes to navigate to any page
in the app, without having to follow a set navigation hierarchy. In addition, it also
provides the ability to navigate backwards without having to visit all of the pages on the
navigation stack. For more information, see .NET MAUI Shell navigation.
Search
.NET MAUI Shell includes integrated search functionality that's provided by the
SearchHandler class. Search capability can be added to a page by adding a subclassed
SearchHandler object to it. This results in a search box being added at the top of the
page. When data is entered into the search box, the search suggestions area is
populated with data:
Then, when a result is selected from the search suggestions area, custom logic can be
executed such as navigating to a detail page.
A .NET Multi-platform App UI (.NET MAUI) Shell app can be created with the .NET MAUI
App project template, and then by describing the visual hierarchy of the app in the
AppShell class.
For a step-by-step walkthrough of how to create a Shell app, see Create a .NET MAUI
app.
1. FlyoutItem or TabBar. A FlyoutItem represents one or more items in the flyout, and
should be used when the navigation pattern for the app requires a flyout. A TabBar
represents the bottom tab bar, and should be used when the navigation pattern
for the app begins with bottom tabs and doesn't require a flyout. Every FlyoutItem
object or TabBar object is a child of the Shell object.
2. Tab, which represents grouped content, navigable by bottom tabs. Every Tab object
is a child of a FlyoutItem object or TabBar object.
3. ShellContent, which represents the ContentPage objects for each tab. Every
ShellContent object is a child of a Tab object. When more than one ShellContent
object is present in a Tab, the objects will be navigable by top tabs.
These objects don't represent any user interface, but rather the organization of the app's
visual hierarchy. Shell will take these objects and produce the navigation user interface
for the content.
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
...
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
Icon="cat.png"
<ShellContent Title="Dogs"
Icon="dog.png"
</Tab>
<!--
Shell has implicit conversion operators that enable the Shell visual
hierarchy to be simplified.
which can only ever contain Tab objects, which can only ever contain
ShellContent objects.
-->
<ShellContent Title="Monkeys"
Icon="monkey.png"
<ShellContent Title="Elephants"
Icon="elephant.png"
ContentTemplate="{DataTemplate views:ElephantsPage}"
/>
<ShellContent Title="Bears"
Icon="bear.png"
</FlyoutItem>
...
</Shell>
When run, this XAML displays the CatsPage , because it's the first item of content
declared in the subclassed Shell class:
Pressing the hamburger icon, or swiping from the left, displays the flyout:
Multiple items are displayed on the flyout because the FlyoutDisplayOptions property is
set to AsMultipleItems . For more information, see Flyout display options.
) Important
The navigation experience provided by .NET Multi-platform App UI (.NET MAUI) Shell is
based on flyouts and tabs. A flyout is the optional root menu for a Shell app, and is fully
customizable. It's accessible through an icon or by swiping from the side of the screen.
The flyout consists of an optional header, flyout items, optional menu items, and an
optional footer:
Flyout items
One or more flyout items can be added to the flyout, and each flyout item is
represented by a FlyoutItem object. Each FlyoutItem object should be a child of the
subclassed Shell object. Flyout items appear at the top of the flyout when a flyout
header isn't present.
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xaminals.Controls"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<FlyoutItem Title="Cats"
Icon="cat.png">
<Tab>
</Tab>
</FlyoutItem>
<FlyoutItem Title="Dogs"
Icon="dog.png">
<Tab>
</Tab>
</FlyoutItem>
</Shell>
The FlyoutItem.Title property, of type string , defines the title of the flyout item. The
FlyoutItem.Icon property, of type ImageSource , defines the icon of the flyout item:
In this example, each ShellContent object can only be accessed through flyout items,
and not through tabs. This is because by default, tabs will only be displayed if the flyout
item contains more than one tab.
) Important
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xaminals.Controls"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<ShellContent Title="Cats"
Icon="cat.png"
<ShellContent Title="Dogs"
Icon="dog.png"
</Shell>
This implicit conversion automatically wraps each ShellContent object in Tab objects,
which are wrapped in FlyoutItem objects.
7 Note
All FlyoutItem objects in a subclassed Shell object are automatically added to the
Shell.FlyoutItems collection, which defines the list of items that will be shown in
the flyout.
AsSingleItem , indicates that the item will be visible as a single item. This is the
default value of the FlyoutDisplayOptions property.
AsMultipleItems , indicates that the item and its direct children will be visible in the
flyout as a group of items.
A flyout item for each Tab object within a FlyoutItem can be displayed by setting the
FlyoutItem.FlyoutDisplayOptions property to AsMultipleItems :
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xaminals.Controls"
xmlns:views="clr-namespace:Xaminals.Views"
FlyoutHeaderBehavior="CollapseOnScroll"
x:Class="Xaminals.AppShell">
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
Icon="cat.png"
<ShellContent Title="Dogs"
Icon="dog.png"
</Tab>
<ShellContent Title="Monkeys"
Icon="monkey.png"
<ShellContent Title="Elephants"
Icon="elephant.png"
ContentTemplate="{DataTemplate views:ElephantsPage}"
/>
<ShellContent Title="Bears"
Icon="bear.png"
</FlyoutItem>
<ShellContent Title="About"
Icon="info.png"
</Shell>
In this example, flyout items are created for the Tab object that's a child of the
FlyoutItem object, and the ShellContent objects that are children of the FlyoutItem
object. This occurs because each ShellContent object that's a child of the FlyoutItem
object is automatically wrapped in a Tab object. In addition, a flyout item is created for
the final ShellContent object, which is automatically wrapped in a Tab object, and then in
a FlyoutItem object.
7 Note
Tabs are displayed when a FlyoutItem contains more than one ShellContent object.
This results in the following flyout items:
XAML
<Shell ...>
...
<Shell.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="0.2*,0.8*">
Margin="5"
HeightRequest="45" />
<Label Grid.Column="1"
Text="{Binding Title}"
FontAttributes="Italic"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</Shell.ItemTemplate>
</Shell>
Shell provides the Title and FlyoutIcon properties to the BindingContext of the
ItemTemplate .
In addition, Shell includes three style classes, which are automatically applied to
FlyoutItem objects. For more information, see Style FlyoutItem and MenuItem objects.
XAML
<DataTemplate x:Key="FlyoutTemplate">
<Grid x:Name="FlyoutItemLayout"
ColumnSpacing="{OnPlatform WinUI=0}"
RowSpacing="{OnPlatform WinUI=0}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Transparent" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="{AppThemeBinding Light=Black,
Dark=White}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image x:Name="FlyoutItemImage"
Source="{Binding FlyoutIcon}"
VerticalOptions="Center"
<Image.Margin>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.Platforms>
<On Platform="WinUI"
Value="12,0,12,0" />
</OnPlatform.Platforms>
</OnPlatform>
</Image.Margin>
</Image>
<Label x:Name="FlyoutItemLabel"
Grid.Column="1"
Text="{Binding Title}"
FontAttributes="{OnPlatform iOS=Bold}"
HorizontalOptions="{OnPlatform WinUI=Start}"
HorizontalTextAlignment="{OnPlatform WinUI=Start}"
VerticalTextAlignment="Center">
<Label.TextColor>
<OnPlatform x:TypeArguments="Color">
<OnPlatform.Platforms>
<On Platform="Android"
Value="{AppThemeBinding Light=Black,
Dark=White}" />
</OnPlatform.Platforms>
</OnPlatform>
</Label.TextColor>
<Label.Margin>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.Platforms>
<On Platform="Android"
</OnPlatform.Platforms>
</OnPlatform>
</Label.Margin>
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<OnPlatform.Platforms>
<On Platform="Android"
Value="sans-serif-medium" />
</OnPlatform.Platforms>
</OnPlatform>
</Label.FontFamily>
</Label>
</Grid>
</DataTemplate>
This template can be used for as a basis for making alterations to the existing flyout
layout, and also shows the visual states that are implemented for flyout items.
In addition, the Grid, Image, and Label elements all have x:Name values and so can be
targeted with the Visual State Manager. For more information, see Set state on multiple
elements.
7 Note
XAML
<Shell ...
x:Name="shell">
...
<Shell.FlyoutContent>
IsGrouped="True"
ItemsSource="{Binding FlyoutItems}">
<CollectionView.ItemTemplate>
<DataTemplate>
TextColor="White"
FontSize="18" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Shell.FlyoutContent>
</Shell>
In this example, the flyout content is replaced with a CollectionView that displays the
title of each item in the FlyoutItems collection.
7 Note
XAML
<Shell ...
x:Name="shell">
...
<Shell.FlyoutContentTemplate>
<DataTemplate>
IsGrouped="True"
ItemsSource="{Binding FlyoutItems}">
<CollectionView.ItemTemplate>
<DataTemplate>
TextColor="White"
FontSize="18" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</DataTemplate>
</Shell.FlyoutContentTemplate>
</Shell>
) Important
A flyout header can optionally be displayed above your flyout content, and a flyout
footer can optionally be displayed below your flyout content. If your flyout content
is scrollable, Shell will attempt to honor the scroll behavior of your flyout header.
Menu items
Menu items can be optionally added to the flyout, and each menu item is represented
by a MenuItem object. The position of MenuItem objects on the flyout is dependent upon
their declaration order in the Shell visual hierarchy. Therefore, any MenuItem objects
declared before FlyoutItem objects will appear before the FlyoutItem objects in the
flyout, and any MenuItem objects declared after FlyoutItem objects will appear after the
FlyoutItem objects in the flyout.
The MenuItem class has a Clicked event, and a Command property. Therefore, MenuItem
objects enable scenarios that execute an action in response to the MenuItem being
tapped.
MenuItem objects can be added to the flyout as shown in the following example:
XAML
<Shell ...>
...
<MenuItem Text="Help"
IconImageSource="help.png"
Command="{Binding HelpCommand}"
CommandParameter="https://learn.microsoft.com/dotnet/maui/fundamentals/shell
" />
</Shell>
This example adds a MenuItem object to the flyout, beneath all the flyout items:
The MenuItem object executes an ICommand named HelpCommand , which opens the URL
specified by the CommandParameter property in the system web browser.
7 Note
The BindingContext of each MenuItem is inherited from the subclassed Shell object.
XAML
<Shell ...>
<Shell.MenuItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="0.2*,0.8*">
Margin="5"
HeightRequest="45" />
<Label Grid.Column="1"
Text="{Binding Text}"
FontAttributes="Italic"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</Shell.MenuItemTemplate>
...
<MenuItem Text="Help"
IconImageSource="help.png"
Command="{Binding HelpCommand}"
CommandParameter="https://learn.microsoft.com/xamarin/xamarin-
forms/app-fundamentals/shell" />
</Shell>
This example attaches the DataTemplate to each MenuItem object, displaying the title of
the MenuItem object in italics:
7 Note
The default template for FlyoutItem objects can also be used for MenuItem objects. For
more information, see Default template for FlyoutItems.
XAML
<Style TargetType="Label"
Class="FlyoutItemLabelStyle">
<Setter Property="TextColor"
Value="Black" />
<Setter Property="HeightRequest"
Value="100" />
</Style>
<Style TargetType="Image"
Class="FlyoutItemImageStyle">
<Setter Property="Aspect"
Value="Fill" />
</Style>
<Style TargetType="Layout"
Class="FlyoutItemLayoutStyle"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="Teal" />
</Style>
These styles will automatically be applied to FlyoutItem and MenuItem objects, without
having to set their StyleClass properties to the style class names.
In addition, custom style classes can be defined and applied to FlyoutItem and MenuItem
objects. For more information about style classes, see Style classes.
Flyout header
The flyout header is the content that optionally appears at the top of the flyout, with its
appearance being defined by an object that can be set with the Shell.FlyoutHeader
bindable property:
XAML
<Shell ...>
<Shell.FlyoutHeader>
<controls:FlyoutHeader />
</Shell.FlyoutHeader>
</Shell>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xaminals.Controls.FlyoutHeader"
HeightRequest="200">
<Grid BackgroundColor="Black">
<Image Aspect="AspectFill"
Source="store.jpg"
Opacity="0.6" />
<Label Text="Animals"
TextColor="White"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
</Grid>
</ContentView>
XAML
<Shell ...>
<Shell.FlyoutHeaderTemplate>
<DataTemplate>
<Grid BackgroundColor="Black"
HeightRequest="200">
<Image Aspect="AspectFill"
Source="store.jpg"
Opacity="0.6" />
<Label Text="Animals"
TextColor="White"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</Shell.FlyoutHeaderTemplate>
</Shell>
By default, the flyout header will be fixed in the flyout while the content below will scroll
if there are enough items. However, this behavior can be changed by setting the
Shell.FlyoutHeaderBehavior bindable property to one of the FlyoutHeaderBehavior
enumeration members:
Default – indicates that the default behavior for the platform will be used. This is
the default value of the FlyoutHeaderBehavior property.
Fixed – indicates that the flyout header remains visible and unchanged at all
times.
Scroll – indicates that the flyout header scrolls out of view as the user scrolls the
items.
CollapseOnScroll – indicates that the flyout header collapses to a title only, as the
The following example shows how to collapse the flyout header as the user scrolls:
XAML
<Shell ...
FlyoutHeaderBehavior="CollapseOnScroll">
...
</Shell>
Flyout footer
The flyout footer is the content that optionally appears at the bottom of the flyout, with
its appearance being defined by an object that can be set with the Shell.FlyoutFooter
bindable property:
XAML
<Shell ...>
<Shell.FlyoutFooter>
<controls:FlyoutFooter />
</Shell.FlyoutFooter>
</Shell>
XAML
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="Xaminals.Controls.FlyoutFooter">
<StackLayout>
<Label Text="Xaminals"
TextColor="GhostWhite"
FontAttributes="Bold"
HorizontalOptions="Center" />
TextColor="GhostWhite"
HorizontalOptions="Center" />
</StackLayout>
</ContentView>
) Important
The previous XAML example defined a new XAML namespace named sys :
xmlns:sys="clr-namespace:System;assembly=netstandard" . This XAML namespace
maps sys to the .NET System namespace. The mapping allows you to use the .NET
types defined in that namespace, such as DateTime , in the XAML. For more
information, see XAML Namespaces.
XAML
<Shell ...>
<Shell.FlyoutFooterTemplate>
<DataTemplate>
<StackLayout>
<Label Text="Xaminals"
TextColor="GhostWhite"
FontAttributes="Bold"
HorizontalOptions="Center" />
TextColor="GhostWhite"
HorizontalOptions="Center" />
</StackLayout>
</DataTemplate>
</Shell.FlyoutFooterTemplate>
</Shell>
The flyout footer is fixed to the bottom of the flyout, and can be any height. In addition,
the footer never obscures any menu items.
XAML
<Shell ...
FlyoutWidth="400"
FlyoutHeight="200">
...
</Shell>
This enables scenarios such as expanding the flyout across the entire screen, or reducing
the height of the flyout so that it doesn't obscure the tab bar.
Flyout icon
By default, Shell apps have a hamburger icon which, when pressed, opens the flyout.
This icon can be changed by setting the Shell.FlyoutIcon bindable property, of type
ImageSource , to an appropriate icon:
XAML
<Shell ...
FlyoutIcon="flyouticon.png">
...
</Shell>
Flyout background
The background color of the flyout can be set with the Shell.FlyoutBackgroundColor
bindable property:
XAML
<Shell ...
FlyoutBackgroundColor="AliceBlue">
...
</Shell>
7 Note
XAML
<Shell ...
FlyoutBackground="LightGray">
...
</Shell>
In this example, the flyout background is painted with a light gray SolidColorBrush.
XAML
<Shell ...>
<Shell.FlyoutBackground>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,1">
<GradientStop Color="#8A2387"
Offset="0.1" />
<GradientStop Color="#E94057"
Offset="0.6" />
<GradientStop Color="#F27121"
Offset="1.0" />
</LinearGradientBrush>
</Shell.FlyoutBackground>
...
</Shell>
The aspect ratio of the background image can be configured by setting the
FlyoutBackgroundImageAspect bindable property, of type Aspect , to one of the Aspect
enumeration members:
AspectFill - clips the image so that it fills the display area while preserving the
aspect ratio.
AspectFit - letterboxes the image, if required, so that the image fits into the
display area, with blank space added to the top/bottom or sides depending on
whether the image is wide or tall. This is the default value of the
FlyoutBackgroundImageAspect property.
Fill - stretches the image to completely and exactly fill the display area. This may
result in image distortion.
XAML
<Shell ...
FlyoutBackgroundImage="photo.jpg"
FlyoutBackgroundImageAspect="AspectFill">
...
</Shell>
This results in a background image appearing in the flyout, below the flyout header:
Flyout backdrop
The backdrop of the flyout, which is the appearance of the flyout overlay, can be
specified by setting the Shell.FlyoutBackdrop attached property to a Brush:
XAML
<Shell ...
FlyoutBackdrop="Silver">
...
</Shell>
) Important
The FlyoutBackdrop attached property can be set on any Shell element, but will
only be applied when it's set on Shell, FlyoutItem, or TabBar objects.
XAML
<Shell ...>
<Shell.FlyoutBackdrop>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,1">
<GradientStop Color="#8A2387"
Offset="0.1" />
<GradientStop Color="#E94057"
Offset="0.6" />
<GradientStop Color="#F27121"
Offset="1.0" />
</LinearGradientBrush>
</Shell.FlyoutBackdrop>
...
</Shell>
Flyout behavior
The flyout can be accessed through the hamburger icon or by swiping from the side of
the screen. However, this behavior can be changed by setting the Shell.FlyoutBehavior
attached property to one of the FlyoutBehavior enumeration members:
Flyout – indicates that the flyout can be opened and closed by the user. This is the
overlap content.
XAML
<Shell ...
FlyoutBehavior="Disabled">
...
</Shell>
7 Note
XAML
<Shell ...
FlyoutVerticalScrollMode="Disabled">
...
</Shell>
FlyoutItem selection
When a Shell app that uses a flyout is first run, the Shell.CurrentItem property will be
set to the first FlyoutItem object in the subclassed Shell object. However, the property
can be set to another FlyoutItem, as shown in the following example:
XAML
<Shell ...
CurrentItem="{x:Reference aboutItem}">
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
...
</FlyoutItem>
<ShellContent x:Name="aboutItem"
Title="About"
Icon="info.png"
</Shell>
This example sets the CurrentItem property to the ShellContent object named
aboutItem , which results in it being selected and displayed. In this example, an implicit
conversion is used to wrap the ShellContent object in a Tab object, which is wrapped in
a FlyoutItem object.
C#
CurrentItem = aboutItem;
In this example, the CurrentItem property is set in the subclassed Shell class.
Alternatively, the CurrentItem property can be set in any class through the
Shell.Current static property:
C#
Shell.Current.CurrentItem = aboutItem;
7 Note
An app may enter a state where selecting a flyout item is not a valid operation. In
such cases, the FlyoutItem can be disabled by setting its IsEnabled property to
false . This will prevent users from being able to select the flyout item.
FlyoutItem visibility
Flyout items are visible in the flyout by default. However, an item can be hidden in the
flyout with the FlyoutItemIsVisible property, and removed from the flyout with the
IsVisible property:
FlyoutItemIsVisible , of type bool , indicates if the item is hidden in the flyout, but
is still reachable with the GoToAsync navigation method. The default value of this
property is true .
IsVisible , of type bool , indicates if the item should be removed from the visual
tree and therefore not appear in the flyout. Its default value is true .
XAML
<Shell ...>
<FlyoutItem ...
FlyoutItemIsVisible="False">
...
</FlyoutItem>
</Shell>
7 Note
XAML
<Shell ...
FlyoutIsPresented="{Binding IsFlyoutOpen}">
</Shell>
C#
Shell.Current.FlyoutIsPresented = false;
The navigation experience provided by .NET Multi-platform App UI (.NET MAUI) Shell is
based on flyouts and tabs. The top level of navigation in a Shell app is either a flyout or
a bottom tab bar, depending on the navigation requirements of the app. When the
navigation experience for an app begins with bottom tabs, the child of the subclassed
Shell object should be a TabBar object, which represents the bottom tab bar.
A TabBar object can contain one or more Tab objects, with each Tab object representing
a tab on the bottom tab bar. Each Tab object can contain one or more ShellContent
objects, with each ShellContent object displaying a single ContentPage. When more
than one ShellContent object is present in a Tab object, the ContentPage objects are
navigable by top tabs. Within a tab, you can navigate to other ContentPage objects that
are known as detail pages.
) Important
Single page
A single page Shell app can be created by adding a Tab object to a TabBar object. Within
the Tab object, a ShellContent object should be set to a ContentPage object:
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab>
</Tab>
</TabBar>
</Shell>
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<Tab>
</Tab>
</Shell>
This implicit conversion automatically wraps the ShellContent object in a Tab object,
which is wrapped in a TabBar object.
) Important
Bottom tabs
If there are multiple Tab objects in a single TabBar object, Tab objects are rendered as
bottom tabs:
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Cats"
Icon="cat.png">
</Tab>
<Tab Title="Dogs"
Icon="dog.png">
</Tab>
</TabBar>
</Shell>
The Title property, of type string , defines the tab title. The Icon property, of type
ImageSource , defines the tab icon:
When there are more than five tabs on a TabBar, a More tab will appear, which can be
used to access the other tabs:
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<ShellContent Title="Cats"
Icon="cat.png"
<ShellContent Title="Dogs"
Icon="dog.png"
</TabBar>
</Shell>
This implicit conversion automatically wraps each ShellContent object in a Tab object.
) Important
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
<ShellContent Title="Dogs"
</Tab>
<Tab Title="Monkeys"
Icon="monkey.png">
</Tab>
</TabBar>
</Shell>
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
Icon="cat.png"
<ShellContent Title="Dogs"
Icon="dog.png"
</Tab>
<ShellContent Title="Monkeys"
Icon="monkey.png"
</TabBar>
</Shell>
This implicit conversion automatically wraps the third ShellContent object in a Tab
object.
Tab appearance
The Shell class defines the following attached properties that control the appearance of
tabs:
TabBarBackgroundColor , of type Color, that defines the background color for the
tab bar. If the property is unset, the BackgroundColor property value is used.
TabBarDisabledColor , of type Color, that defines the disabled color for the tab bar.
All of these properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings, and styled.
The following example shows a XAML style that sets different tab bar color properties:
XAML
<Style TargetType="TabBar">
<Setter Property="Shell.TabBarBackgroundColor"
Value="CornflowerBlue" />
<Setter Property="Shell.TabBarTitleColor"
Value="Black" />
<Setter Property="Shell.TabBarUnselectedColor"
Value="AntiqueWhite" />
</Style>
In addition, tabs can also be styled using Cascading Style Sheets (CSS). For more
information, see .NET MAUI Shell specific properties.
Tab selection
When a Shell app that uses a tab bar is first run, the Shell.CurrentItem property will be
set to the first Tab object in the subclassed Shell object. However, the property can be
set to another Tab, as shown in the following example:
XAML
<Shell ...
CurrentItem="{x:Reference dogsItem}">
<TabBar>
<ShellContent Title="Cats"
Icon="cat.png"
<ShellContent x:Name="dogsItem"
Title="Dogs"
Icon="dog.png"
</TabBar>
</Shell>
This example sets the CurrentItem property to the ShellContent object named dogsItem ,
which results in it being selected and displayed. In this example, an implicit conversion is
used to wrap each ShellContent object in a Tab object.
C#
CurrentItem = dogsItem;
In this example, the CurrentItem property is set in the subclassed Shell class.
Alternatively, the CurrentItem property can be set in any class through the
Shell.Current static property:
C#
Shell.Current.CurrentItem = dogsItem;
While this property can be set on a subclassed Shell object, it's typically set on any
ShellContent or ContentPage objects that want to make the tab bar invisible:
XAML
<TabBar>
<Tab Title="Domestic"
Icon="paw.png">
<ShellContent Title="Cats"
<ShellContent Shell.TabBarIsVisible="false"
Title="Dogs"
</Tab>
<Tab Title="Monkeys"
Icon="monkey.png">
</Tab>
</TabBar>
In this example, the tab bar is hidden when the upper Dogs tab is selected.
In addition, Tab objects can be hidden by setting the IsVisible bindable property to
false :
XAML
<TabBar>
<ShellContent Title="Cats"
Icon="cat.png"
<ShellContent Title="Dogs"
Icon="dog.png"
ContentTemplate="{DataTemplate views:DogsPage}"
IsVisible="False" />
<ShellContent Title="Monkeys"
Icon="monkey.png"
</TabBar>
A ShellContent object represents the ContentPage object for each FlyoutItem or Tab.
When more than one ShellContent object is present in a Tab object, the ContentPage
objects will be navigable by top tabs. Within a page, additional ContentPage objects
that are known as detail pages, can be navigated to.
In addition, the Shell class defines attached properties that can be used to configure the
appearance of pages in .NET Multi-platform App UI (.NET MAUI) Shell apps. This
includes setting page colors, setting the page presentation mode, disabling the
navigation bar, disabling the tab bar, and displaying views in the navigation bar.
Display pages
In .NET MAUI Shell apps, pages are typically created on demand in response to
navigation. This is accomplished by using the DataTemplate markup extension to set the
ContentTemplate property of each ShellContent object to a ContentPage object:
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<ShellContent Title="Cats"
Icon="cat.png"
<ShellContent Title="Dogs"
Icon="dog.png"
<ShellContent Title="Monkeys"
Icon="monkey.png"
</TabBar>
</Shell>
In this example, Shell's implicit conversion operators are used to remove the Tab objects
from the visual hierarchy. However, each ShellContent object is rendered in a tab:
7 Note
The BindingContext of each ShellContent object is inherited from the parent Tab
object.
Within each ContentPage object, additional ContentPage objects can be navigated to.
For more information about navigation, see .NET MAUI Shell navigation.
2 Warning
ContentPage objects that are created at app startup can lead to a poor startup
experience.
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Xaminals.Views"
x:Class="Xaminals.AppShell">
<TabBar>
<ShellContent Title="Cats"
Icon="cat.png">
<views:CatsPage />
</ShellContent>
<ShellContent Title="Dogs"
Icon="dog.png">
<views:DogsPage />
</ShellContent>
<ShellContent Title="Monkeys"
Icon="monkey.png">
<views:MonkeysPage />
</ShellContent>
</TabBar>
</Shell>
In this example, CatsPage , DogsPage , and MonkeysPage are all created at app startup,
rather than on demand in response to navigation.
7 Note
The Content property is the content property of the ShellContent class, and
therefore does not need to be explicitly set.
BackgroundColor , of type Color, that defines the background color in the Shell
chrome. The color will not fill in behind the Shell content.
DisabledColor , of type Color, that defines the color to shade text and icons that
are disabled.
ForegroundColor , of type Color, that defines the color to shade text and icons.
TitleColor , of type Color, that defines the color used for the title of the current
page.
UnselectedColor , of type Color, that defines the color used for unselected text and
All of these properties are backed by BindableProperty objects, which mean that the
properties can be targets of data bindings, and styled using XAML styles. In addition, the
properties can be set using Cascading Style Sheets (CSS). For more information, see .NET
MAUI Shell specific properties.
7 Note
There are also properties that enable tab colors to be defined. For more
information, see Tab appearance.
The following XAML shows setting the color properties in a subclassed Shell class:
XAML
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xaminals.AppShell"
BackgroundColor="#455A64"
ForegroundColor="White"
TitleColor="White"
DisabledColor="#B4FFFFFF"
UnselectedColor="#95FFFFFF">
</Shell>
In this example, the color values will be applied to all pages in the Shell app, unless
overridden at the page level.
Because the color properties are attached properties, they can also be set on individual
pages, to set the colors on that page:
XAML
<ContentPage ...
Shell.BackgroundColor="Gray"
Shell.ForegroundColor="White"
Shell.TitleColor="Blue"
Shell.DisabledColor="#95FFFFFF"
Shell.UnselectedColor="#B4FFFFFF">
</ContentPage>
XAML
<Style x:Key="DomesticShell"
TargetType="Element" >
<Setter Property="Shell.BackgroundColor"
Value="#039BE6" />
<Setter Property="Shell.ForegroundColor"
Value="White" />
<Setter Property="Shell.TitleColor"
Value="White" />
<Setter Property="Shell.DisabledColor"
Value="#B4FFFFFF" />
<Setter Property="Shell.UnselectedColor"
Value="#95FFFFFF" />
</Style>
For more information about XAML styles, see Style apps using XAML.
ModalAnimated indicates that the page will be displayed as a modal page, with a
navigation animation.
ModalNotAnimated indicates that the page will be displayed as a modal page,
) Important
The PresentationMode type is a flags enumeration. This means that a combination
of enumeration members can be applied in code. However, for ease of use in
XAML, the ModalAnimated member is a combination of the Animated and Modal
members, and the ModalNotAnimated member is a combination of the NotAnimated
and Modal members. For more information about flag enumerations, see
Enumeration types as bit flags.
XAML
<ContentPage ...
Shell.PresentationMode="Modal">
...
</ContentPage>
In this example, the ContentPage is set to be displayed as a modal page, when the page
is navigated to with the GoToAsync method.
While this property can be set on a subclassed Shell object, it can also be set on any
pages that want to enable the navigation bar shadow. For example, the following XAML
shows enabling the navigation bar shadow from a ContentPage:
XAML
<ContentPage ...
Shell.NavBarHasShadow="true">
...
</ContentPage>
While this property can be set on a subclassed Shell object, it's typically set on any
pages that want to make the navigation bar invisible. For example, the following XAML
shows disabling the navigation bar from a ContentPage:
XAML
<ContentPage ...
Shell.NavBarIsVisible="false">
...
</ContentPage>
While this property can be set on a subclassed Shell object, it can also be set on any
pages that want to display a view in the navigation bar. For example, the following
XAML shows displaying an Image in the navigation bar of a ContentPage:
XAML
<ContentPage ...>
<Shell.TitleView>
<Image Source="logo.png"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Shell.TitleView>
...
</ContentPage>
) Important
If the navigation bar has been made invisible, with the NavBarIsVisible attached
property, the title view will not be displayed.
Many views won't appear in the navigation bar unless the size of the view is specified
with the WidthRequest and HeightRequest properties, or the location of the view is
specified with the HorizontalOptions and VerticalOptions properties.
The TitleView attached property can be set to display a layout class that contains
multiple views. Similarly, because the ContentView class ultimately derives from the View
class, the TitleView attached property can be set to display a ContentView that contains
a single view.
Page visibility
Shell respects page visibility, set with the IsVisible property. Therefore, when a page's
IsVisible property is set to false it won't be visible in the Shell app and it won't be
Navigation is performed by invoking the GoToAsync method, from the Shell class. When
navigation is about to be performed, the Navigating event is fired, and the Navigated
event is fired when navigation completes.
7 Note
Navigation can still be performed between pages in a Shell app by using the
Navigation property. For more information, see Perform modeless navigation.
Routes
Navigation is performed in a Shell app by specifying a URI to navigate to. Navigation
URIs can have three components:
A route, which defines the path to content that exists as part of the Shell visual
hierarchy.
A page. Pages that don't exist in the Shell visual hierarchy can be pushed onto the
navigation stack from anywhere within a Shell app. For example, a details page
won't be defined in the Shell visual hierarchy, but can be pushed onto the
navigation stack as required.
One or more query parameters. Query parameters are parameters that can be
passed to the destination page while navigating.
When a navigation URI includes all three components, the structure is: //route/page?
queryParameters
Register routes
Routes can be defined on FlyoutItem, TabBar, Tab, and ShellContent objects, through
their Route properties:
XAML
<Shell ...>
<FlyoutItem ...
Route="animals">
<Tab ...
Route="domestic">
<ShellContent ...
Route="cats" />
<ShellContent ...
Route="dogs" />
</Tab>
<ShellContent ...
Route="monkeys" />
<ShellContent ...
Route="elephants" />
<ShellContent ...
Route="bears" />
</FlyoutItem>
<ShellContent ...
Route="about" />
...
</Shell>
7 Note
All items in the Shell hierarchy have a route associated with them. If you don't set a
route, one is generated at runtime. However, generated routes are not guaranteed
to be consistent across different app sessions.
The above example creates the following route hierarchy, which can be used in
programmatic navigation:
animals
domestic
cats
dogs
monkeys
elephants
bears
about
To navigate to the ShellContent object for the dogs route, the absolute route URI is
//animals/domestic/dogs . Similarly, to navigate to the ShellContent object for the about
route, the absolute route URI is //about .
2 Warning
C#
Routing.RegisterRoute("monkeydetails", typeof(MonkeyDetailPage));
Routing.RegisterRoute("beardetails", typeof(BearDetailPage));
Routing.RegisterRoute("catdetails", typeof(CatDetailPage));
Routing.RegisterRoute("dogdetails", typeof(DogDetailPage));
Routing.RegisterRoute("elephantdetails", typeof(ElephantDetailPage));
This example registers detail pages, that aren't defined in the Shell subclass, as routes.
These detail pages can then be navigated to using URI-based navigation, from anywhere
within the app. The routes for such pages are known as global routes.
2 Warning
C#
Routing.RegisterRoute("monkeys/details", typeof(MonkeyDetailPage));
Routing.RegisterRoute("bears/details", typeof(BearDetailPage));
Routing.RegisterRoute("cats/details", typeof(CatDetailPage));
Routing.RegisterRoute("dogs/details", typeof(DogDetailPage));
Routing.RegisterRoute("elephants/details", typeof(ElephantDetailPage));
This example enables contextual page navigation, where navigating to the details
route from the page for the monkeys route displays the MonkeyDetailPage . Similarly,
navigating to the details route from the page for the elephants route displays the
ElephantDetailPage . For more information, see Contextual navigation.
7 Note
Pages whose routes have been registered with the Routing.RegisterRoute method
can be deregistered with the Routing.UnRegisterRoute method, if required.
Perform navigation
To perform navigation, a reference to the Shell subclass must first be obtained. This
reference can be obtained by casting the App.Current.MainPage property to a Shell
object, or through the Shell.Current property. Navigation can then be performed by
calling the GoToAsync method on the Shell object. This method navigates to a
ShellNavigationState and returns a Task that will complete once the navigation
) Important
When a route from the Shell visual hierarchy is navigated to, a navigation stack isn't
created. However, when a page that's not in the Shell visual hierarchy is navigated
to, a navigation stack is created.
The current navigation state of the Shell object can be retrieved through the
Shell.Current.CurrentState property, which includes the URI of the displayed route in
Absolute routes
Navigation can be performed by specifying a valid absolute URI as an argument to the
GoToAsync method:
C#
await Shell.Current.GoToAsync("//animals/monkeys");
This example navigates to the page for the monkeys route, with the route being defined
on a ShellContent object. The ShellContent object that represents the monkeys route is a
child of a FlyoutItem object, whose route is animals .
Relative routes
Navigation can also be performed by specifying a valid relative URI as an argument to
the GoToAsync method. The routing system will attempt to match the URI to a
ShellContent object. Therefore, if all the routes in an app are unique, navigation can be
performed by only specifying the unique route name as a relative URI.
Format Description
route The route hierarchy will be searched for the specified route, upwards from the current
position. The matching page will be pushed to the navigation stack.
/route The route hierarchy will be searched from the specified route, downwards from the
current position. The matching page will be pushed to the navigation stack.
//route The route hierarchy will be searched for the specified route, upwards from the current
position. The matching page will replace the navigation stack.
///route The route hierarchy will be searched for the specified route, downwards from the
current position. The matching page will replace the navigation stack.
The following example navigates to the page for the monkeydetails route:
C#
await Shell.Current.GoToAsync("monkeydetails");
In this example, the monkeyDetails route is searched for up the hierarchy until the
matching page is found. When the page is found, it's pushed to the navigation stack.
Contextual navigation
Relative routes enable contextual navigation. For example, consider the following route
hierarchy:
monkeys
details
bears
details
When the registered page for the monkeys route is displayed, navigating to the details
route will display the registered page for the monkeys/details route. Similarly, when the
registered page for the bears route is displayed, navigating to the details route will
display the registered page for the bears/details route. For information on how to
register the routes in this example, see Register page routes.
Backwards navigation
Backwards navigation can be performed by specifying ".." as the argument to the
GoToAsync method:
C#
await Shell.Current.GoToAsync("..");
C#
await Shell.Current.GoToAsync("../route");
In this example, backwards navigation is performed, and then navigation to the specified
route.
) Important
Navigating backwards and into a specified route is only possible if the backwards
navigation places you at the current location in the route hierarchy to navigate to
the specified route.
Similarly, it's possible to navigate backwards multiple times, and then navigate to a
specified route:
C#
await Shell.Current.GoToAsync("../../route");
In this example, backwards navigation is performed twice, and then navigation to the
specified route.
In addition, data can be passed through query properties when navigating backwards:
C#
await Shell.Current.GoToAsync($"..?parameterToPassBack=
{parameterValueToPassBack}");
In this example, backwards navigation is performed, and the query parameter value is
passed to the query parameter on the previous page.
7 Note
For more information about passing data when navigating, see Pass data.
Invalid routes
The following route formats are invalid:
Format Explanation
Format Explanation
//page or Global routes currently can't be the only page on the navigation stack. Therefore,
///page absolute routing to global routes is unsupported.
2 Warning
Debugging navigation
Some of the Shell classes are decorated with the DebuggerDisplayAttribute , which
specifies how a class or field is displayed by the debugger. This can help to debug
navigation requests by displaying data related to the navigation request. For example,
the following screenshot shows the CurrentItem and CurrentState properties of the
Shell.Current object:
In this example, the CurrentItem property, of type FlyoutItem, displays the title and
route of the FlyoutItem object. Similarly, the CurrentState property, of type
ShellNavigationState , displays the URI of the displayed route within the Shell app.
Navigation stack
The Tab class defines a Stack property, of type IReadOnlyList<Page> , which represents
the current navigation stack within the Tab. The class also provides the following
overridable navigation methods:
is called.
OnPushAsync , returns Task , and is called when INavigation.PushAsync is called.
C#
base.OnRemovePage(page);
// Custom logic
In this example, MyTab objects should be consumed in your Shell visual hierarchy instead
of Tab objects.
Navigation events
The Shell class defines the Navigating event, which is fired when navigation is about to
be performed, either due to programmatic navigation or user interaction. The
ShellNavigatingEventArgs object that accompanies the Navigating event provides the
following properties:
The Shell class also defines the Navigated event, which is fired when navigation has
completed. The ShellNavigatedEventArgs object that accompanies the Navigated event
provides the following properties:
Property Type Description
) Important
The OnNavigating method is called when the Navigating event fires. Similarly, the
OnNavigated method is called when the Navigated event fires. Both methods can
be overridden in your Shell subclass to intercept navigation requests.
Unknown
Push
Pop
PopToRoot
Insert
Remove
ShellItemChanged
ShellSectionChanged
ShellContentChanged
C#
base.OnNavigating(args);
if (args.Source == ShellNavigationSource.Pop)
args.Cancel();
// }
Navigation deferral
Shell navigation can be intercepted and completed or canceled based on user choice.
This can be achieved by overriding the OnNavigating method in your Shell subclass, and
by calling the GetDeferral method on the ShellNavigatingEventArgs object. This
method returns a ShellNavigatingDeferral token that has a Complete method, which
can be used to complete the navigation request:
C#
// ...
base.OnNavigating(args);
if (result != "Yes")
args.Cancel();
token.Complete();
In this example, an action sheet is displayed that invites the user to complete the
navigation request, or cancel it. Navigation is canceled by invoking the Cancel method
on the ShellNavigatingEventArgs object. Navigation is completed by invoking the
Complete method on the ShellNavigatingDeferral token that was retrieved by the
GetDeferral method on the ShellNavigatingEventArgs object.
2 Warning
Pass data
Primitive data can be passed as string-based query parameters when performing URI-
based programmatic navigation. This is achieved by appending ? after a route, followed
by a query parameter id, = , and a value:
C#
await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}");
This example retrieves the currently selected elephant in the CollectionView, and
navigates to the elephantdetails route, passing elephantName as a query parameter.
Object-based navigation data can be passed with a GoToAsync overload that specifies an
IDictionary<string, object> argument:
C#
{ "Bear", animal }
};
This example retrieves the currently selected bear in the CollectionView, as an Animal .
The Animal object is added to a Dictionary with the key Bear . Then, navigation to the
beardetails route is performed, with the Dictionary being passed as a navigation
parameter.
1. The class that represents the page being navigated to, or the class for the page's
BindingContext , can be decorated with a QueryPropertyAttribute for each query
parameter. For more information, see Process navigation data using query
property attributes.
2. The class that represents the page being navigated to, or the class for the page's
BindingContext , can implement the IQueryAttributable interface. For more
information, see Process navigation data using a single method.
navigation parameter:
C#
[QueryProperty(nameof(Bear), "Bear")]
Animal bear;
set
bear = value;
OnPropertyChanged();
public BearDetailPage()
InitializeComponent();
BindingContext = this;
In this example the first argument for the QueryPropertyAttribute specifies the name of
the property that will receive the data, with the second argument specifying the
parameter id. Therefore, the QueryPropertyAttribute in the above example specifies that
the Bear property will receive the data passed in the Bear navigation parameter in the
GoToAsync method call.
7 Note
The following example shows a view model class that implements the
IQueryAttributable interface:
C#
OnPropertyChanged("Monkey");
...
In this example, the ApplyQueryAttributes method retrieves the object that corresponds
to the Monkey key in the query dictionary, which was passed as an argument to the
GoToAsync method call.
) Important
String-based query parameter values that are received via the IQueryAttributable
interface aren't automatically URL decoded.
await Shell.Current.GoToAsync($"elephantdetails?name=
{elephantName}&location={elephantLocation}");
This code example retrieves the currently selected elephant in the CollectionView, and
navigates to the elephantdetails route, passing elephantName and elephantLocation as
query parameters.
To receive multiple items of data, the class that represents the page being navigated to,
or the class for the page's BindingContext , can be decorated with a
QueryPropertyAttribute for each string-based query parameter:
C#
[QueryProperty(nameof(Name), "name")]
[QueryProperty(nameof(Location), "location")]
set
// Custom logic
set
// Custom logic
...
In this example, the class is decorated with a QueryPropertyAttribute for each query
parameter. The first QueryPropertyAttribute specifies that the Name property will receive
the data passed in the name query parameter, while the second QueryPropertyAttribute
specifies that the Location property will receive the data passed in the location query
parameter. In both cases, the query parameter values are specified in the URI in the
GoToAsync method call.
C#
string location =
HttpUtility.UrlDecode(query["location"].ToString());
...
...
In this example, the ApplyQueryAttributes method retrieves the value of the name and
location query parameters from the URI in the GoToAsync method call.
7 Note
Command , of type ICommand , which is executed when the back button is pressed.
CommandParameter , of type object , which is the parameter that's passed to the
Command .
IconOverride , of type ImageSource , the icon used for the back button.
IsEnabled , of type boolean , indicates whether the back button is enabled. The
All of these properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
The following code shows an example of redefining back button appearance and
behavior:
XAML
<ContentPage ...>
<Shell.BackButtonBehavior>
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>
The Command property is set to an ICommand to be executed when the back button is
pressed, and the IconOverride property is set to the icon that's used for the back
button:
.NET MAUI Shell search
Article • 04/03/2023 • 5 minutes to read
.NET Multi-platform App UI (.NET MAUI) Shell includes integrated search functionality
that's provided by the SearchHandler class. Search capability can be added to a page by
setting the Shell.SearchHandler attached property to a subclassed SearchHandler
object. This results in a search box being added at the top of the page:
When a query is entered into the search box, the Query property is updated, and on
each update the OnQueryChanged method is executed. This method can be overridden to
populate the search suggestions area with data:
Then, when a result is selected from the search suggestions area, the OnItemSelected
method is executed. This method can be overridden to respond appropriately, such as
by navigating to a detail page.
Create a SearchHandler
Search functionality can be added to a Shell app by subclassing the SearchHandler class,
and overriding the OnQueryChanged and OnItemSelected methods:
C#
base.OnQueryChanged(oldValue, newValue);
if (string.IsNullOrWhiteSpace(newValue))
ItemsSource = null;
else
ItemsSource = Animals
.Where(animal =>
animal.Name.ToLower().Contains(newValue.ToLower()))
.ToList<Animal>();
base.OnItemSelected(item);
await Task.Delay(1000);
// The following route works because route names are unique in this
app.
await Shell.Current.GoToAsync($"{GetNavigationTarget()}?name=
{((Animal)item).Name}");
string GetNavigationTarget()
The OnQueryChanged override has two arguments: oldValue , which contains the previous
search query, and newValue , which contains the current search query. The search
suggestions area can be updated by setting the SearchHandler.ItemsSource property to
an IEnumerable collection that contains items that match the current search query.
When a search result is selected by the user, the OnItemSelected override is executed
and the SelectedItem property is set. In this example, the method navigates to another
page that displays data about the selected Animal . For more information about
navigation, see Shell navigation.
7 Note
Consume a SearchHandler
The subclassed SearchHandler can be consumed by setting the Shell.SearchHandler
attached property to an object of the subclassed type, on the consuming page:
XAML
<ContentPage ...
xmlns:controls="clr-namespace:Xaminals.Controls">
<Shell.SearchHandler>
ShowsResults="true"
DisplayMemberName="Name" />
</Shell.SearchHandler>
...
</ContentPage>
C#
ShowsResults = true,
DisplayMemberName = "Name"
});
The ShowsResults property is set to true , so that search suggestions are displayed as
the user enters a search query:
As the search query changes, the search suggestions area is updated:
When a search result is selected, the MonkeyDetailPage is navigated to, and a detail page
about the selected monkey is displayed:
Define search results item appearance
In addition to displaying string data in the search results, the appearance of each
search result item can be defined by setting the SearchHandler.ItemTemplate property to
a DataTemplate:
XAML
<ContentPage ...
xmlns:controls="clr-namespace:Xaminals.Controls">
<Shell.SearchHandler>
ShowsResults="true">
<controls:AnimalSearchHandler.ItemTemplate>
<DataTemplate>
<Grid Padding="10"
ColumnDefinitions="0.15*,0.85*">
HeightRequest="40"
WidthRequest="40" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold"
VerticalOptions="Center" />
</Grid>
</DataTemplate>
</controls:AnimalSearchHandler.ItemTemplate>
</controls:AnimalSearchHandler>
</Shell.SearchHandler>
...
</ContentPage>
The elements specified in the DataTemplate define the appearance of each item in the
suggestions area. In this example, layout within the DataTemplate is managed by a Grid.
The Grid contains an Image object, and a Label object, that both bind to properties of
each Monkey object.
The following screenshot shows the result of templating each item in the suggestions
area:
enumeration members:
Collapsible – the search box is hidden until the user performs an action to reveal
it. On iOS the search box is revealed by vertically bouncing the page content, and
on Android the search box is revealed by tapping the question mark icon.
Expanded – the search box is visible and fully expanded. This is the default value of
the SearchBoxVisibility property.
) Important
XAML
<ContentPage ...
xmlns:controls="clr-namespace:Xaminals.Controls">
<Shell.SearchHandler>
<controls:AnimalSearchHandler SearchBoxVisibility="Hidden"
... />
</Shell.SearchHandler>
...
</ContentPage>
When a search box has input focus, tapping elsewhere on the screen dismisses the
onscreen keyboard, and the search box loses input focus. This can also be achieved
programmatically by calling the Unfocus method. When a search box loses focus, the
Unfocused event is fired and the overridable OnUnfocus method is called.
The focus state of a search box can be retrieved through the IsFocused property, which
returns true if a SearchHandler currently has input focus.
SearchHandler keyboard
The keyboard that's presented when users interact with a SearchHandler can be set
programmatically via the Keyboard property, to one of the following properties from the
Keyboard class:
Chat – used for texting and places where emoji are useful.
XAML
The Keyboard class also has a Create factory method that can be used to customize a
keyboard by specifying capitalization, spellcheck, and suggestion behavior.
KeyboardFlags enumeration values are specified as arguments to the method, with a
customized Keyboard being returned. The KeyboardFlags enumeration contains the
following values:
CapitalizeWord – indicates that the first letter of each word will be automatically
capitalized.
CapitalizeCharacter – indicates that every character will be automatically
capitalized.
CapitalizeNone – indicates that no automatic capitalization will occur.
All – indicates that spellcheck, word completions, and sentence capitalization will
occur on entered text.
The following XAML code example shows how to customize the default Keyboard to
offer word completions and capitalize every entered character:
XAML
<SearchHandler.Keyboard>
<Keyboard x:FactoryMethod="Create">
<x:Arguments>
<KeyboardFlags>Suggestions,CapitalizeCharacter</KeyboardFlags>
</x:Arguments>
</Keyboard>
</SearchHandler.Keyboard>
</SearchHandler>
Shell apps respect the .NET Multi-platform App UI (.NET MAUI) lifecycle, and additionally
fire an Appearing event when a page is about to appear on the screen, and a
Disappearing event when a page is about to disappear from the screen. These events
are propagated to pages, and can be handled by overriding the OnAppearing() or
OnDisappearing() methods on the page.
7 Note
In a Shell app, the Appearing and Disappearing events are raised from cross-
platform code, prior to platform code making a page visible, or removing a page
from the screen.
Modeless navigation
In a Shell app, pushing a page onto the navigation stack will result in the currently
visible ShellContent object, and its page content, raising the Disappearing event.
Similarly, popping the last page from the navigation stack will result in the newly visible
ShellContent object, and its page content, raising the Appearing event.
For more information about modeless navigation, see Perform modeless navigation.
Modal navigation
In a Shell app, pushing a modal page onto the modal navigation stack will result in all
visible Shell objects raising the Disappearing event. Similarly, popping the last modal
page from the modal navigation stack will result in all visible Shell objects raising the
Appearing event.
For more information about modal navigation, see Perform modal navigation.
Target multiple platforms from .NET
MAUI single project
Article • 03/03/2023 • 8 minutes to read
.NET Multi-platform App UI (.NET MAUI) single project takes the platform-specific
development experiences you typically encounter while developing apps and abstracts
them into a single shared project that can target Android, iOS, macOS, and Windows.
A single shared project that can target Android, iOS, macOS, Tizen, and Windows.
A simplified debug target selection for running your .NET MAUI apps.
Shared resource files within the single project.
A single app manifest that specifies the app title, ID, and version.
Access to platform-specific APIs and tools when required.
A single cross-platform app entry point.
.NET MAUI single project is enabled using multi-targeting and the use of SDK-style
projects.
Resource files
Resource management for cross-platform app development has traditionally been
problematic, because each platform has its own approach to managing resources. For
example, each platform has differing image requirements that typically involves creating
multiple versions of each image at different resolutions. Therefore, a single image
typically has to be duplicated multiple times at different resolutions, with the resulting
images having to use different filename and folder conventions on each platform.
.NET MAUI single project enables resource files to be stored in a single location while
being consumed on each platform. This includes fonts, images, the app icon, the splash
screen, raw assets, and CSS files for styling .NET MAUI apps. Each image resource file is
used as a source image, from which images of the required resolutions are generated
for each platform at build time.
7 Note
iOS Asset Catalogs are currently unsupported in .NET MAUI single projects.
Resource files should typically be placed in the Resources folder of your .NET MAUI app
project, or child folders of the Resources folder, and must have their build action set
correctly. The following table shows the build actions for each resource file type:
Fonts MauiFont
Images MauiImage
7 Note
XAML files are also stored in your .NET MAUI app project, and are automatically
assigned the MauiXaml build action when created by project and item templates.
However, only XAML resource dictionaries will typically be placed in the Resources
folder of the app project.
When a resource file is added to a .NET MAUI app project, a corresponding entry for the
resource is created in the project file, with the exception of CSS files. The following
screenshot shows a typical Resources folder containing child-folders for each resource
type:
The build action for a resource file will be correctly set, if the resource has been added
to the correct Resources child folder.
Child folders of the Resources folder can be designated for each resource type by editing
the project file for your app:
XML
<ItemGroup>
</ItemGroup>
The wildcard character ( * ) indicates that all the files within the folder will be treated as
being of the specified resource type. In addition, it's possible to include all files from
child folders:
XML
<ItemGroup>
</ItemGroup>
In this example, the double wildcard character ('**') specifies that the Images folder can
contain child folders. Therefore, <MauiImage Include="Resources\Images\**\*" />
specifies that any files in the Resources\Images folder, or any child folders of the Images
folder, will be used as source images from which images of the required resolution are
generated for each platform.
App icons
An app icon can be added to your app project by dragging an image into the
Resources\AppIcon folder of the project, where its build action will automatically be set
to MauiIcon. This creates a corresponding entry in your project file:
XML
At build time, the app icon can be resized to the correct sizes for the target platform
and device. The resized app icons are then added to your app package. App icons are
resized to multiple resolutions because they have multiple uses, including being used to
represent the app on the device, and in the app store.
For more information, see Add an app icon to a .NET MAUI app project.
Images
An image can be added to your app project by dragging it into the Resources\Images
folder of the project, where its build action will automatically be set to MauiImage. This
creates a corresponding entry in your project file:
XML
At build time, images can be resized to the correct resolutions for the target platform
and device. The resulting images are then added to your app package.
For more information, see Add images to a .NET MAUI app project.
Fonts
A true type format (TTF) or open type font (OTF) font can be added to your app project
by dragging it into the Resources\Fonts folder of your project, where its build action will
automatically be set to MauiFont. This creates a corresponding entry per font in your
project file:
XML
Splash screen
A splash screen can be added to your app project by dragging an image into the
Resources\Splash folder of the project, where its build action will automatically be set to
MauiSplashScreen. This creates a corresponding entry in your project file:
XML
<ItemGroup>
</ItemGroup>
At build time, the splash screen image is resized to the correct size for the target
platform and device. The resized splash screen is then added to your app package.
For more information, see Add a splash screen to a .NET MAUI app project.
Raw assets
A raw asset file, such as HTML, JSON, and video, can be added to your app project by
dragging it into the Resources\Raw folder of your project, where its build action will
automatically be set to MauiAsset. This creates a corresponding entry per asset in your
project file:
XML
XAML
CSS files
.NET MAUI apps can be partially styled with Cascading Style Sheet (CSS) files. CSS files
can be added to your app project by dragging them into any folder of your project, and
setting their build action to MauiCss in the Properties window.
CSS files must be loaded by the StyleSheet class before being added to a
ResourceDictionary:
XAML
<Application ...>
<Application.Resources>
</Application.Resources>
</Application>
App manifest
Each platform uses its own native app manifest file to specify information such as the
app title, ID, version, and more. .NET MAUI single project enables you to specify this
common app data in a single location in the project file.
To specify the shared app manifest data for a project, open the shortcut menu for the
project in Solution Explorer, and then choose Properties. The app title, ID, and version
can then be specified in MAUI Shared > General:
At build time the shared app manifest data is merged with platform-specific data in the
native app manifest file, to produce the manifest file for the app package. For more
information, see Project configuration in .NET MAUI - MAUI Shared.
Platform-specific code
A .NET MAUI app project contains a Platforms folder, with each child folder representing
a platform that .NET MAUI can target:
The folders for each platform contain platform-specific resources, and code that starts
the app on each platform:
At build time, the build system only includes the code from each folder when building
for that specific platform. For example, when you build for Android the files in the
Platforms\Android folder will be built into the app package, but the files in the other
Platforms folders won't be. This approach uses multi-targeting to target multiple
platforms from a single project. Multi-targeting can be combined with partial classes
and partial methods to invoke native platform functionality from cross-platform code.
For more information, see Invoke platform code.
In addition to this default multi-targeting approach, .NET MAUI apps can also be multi-
targeted based on your own filename and folder criteria. This enables you to structure
your .NET MAUI app project so that you don't have to place your platform code into
child-folders of the Platforms folder. For more information, see Configure multi-
targeting.
C#
#if ANDROID
handler.NativeView.SetBackgroundColor(Colors.Red.ToNative());
#elif IOS
handler.NativeView.BackgroundColor =
Colors.Red.ToNative();
handler.NativeView.BorderStyle =
UIKit.UITextBorderStyle.Line;
#elif WINDOWS
handler.NativeView.Background = Colors.Red.ToNative();
#endif
C#
namespace MyMauiApp;
builder
.UseMauiApp<App>();
return builder.Build();
C#
namespace MyMauiApp;
public App()
InitializeComponent();
In the preceding example, the MainPage property is set to the AppShell object. AppShell
is a subclassed Shell class that describes the visual hierarchy of the app. For more
information, see Create a .NET MAUI Shell app.
Control templates
Article • 04/03/2023 • 13 minutes to read
.NET Multi-platform App UI (.NET MAUI) control templates enable you to define the
visual structure of ContentView derived custom controls, and ContentPage derived
pages. Control templates separate the user interface (UI) for a custom control, or page,
from the logic that implements the control or page. Additional content can also be
inserted into the templated custom control, or templated page, at a pre-defined
location.
For example, a control template can be created that redefines the UI provided by a
custom control. The control template can then be consumed by the required custom
control instance. Alternatively, a control template can be created that defines any
common UI that will be used by multiple pages in an app. The control template can then
be consumed by multiple pages, with each page still displaying its unique content.
Create a ControlTemplate
The following example shows the code for a CardView custom control:
C#
...
The CardView class, which derives from the ContentView class, represents a custom
control that displays data in a card-like layout. The class contains properties, which are
backed by bindable properties, for the data it displays. However, the CardView class
does not define any UI. Instead, the UI will be defined with a control template. For more
information about creating ContentView derived custom controls, see ContentView.
A control template is created with the ControlTemplate type. When you create a
ControlTemplate, you combine View objects to build the UI for a custom control, or
page. A ControlTemplate must have only one View as its root element. However, the
root element usually contains other View objects. The combination of objects makes up
the control's visual structure.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewControlTemplate">
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
CornerRadius="5"
HasShadow="True"
Padding="8"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Frame IsClippedToBounds="True"
BorderColor="{Binding BorderColor}"
BackgroundColor="{Binding IconBackgroundColor}"
CornerRadius="38"
HeightRequest="60"
WidthRequest="60"
HorizontalOptions="Center"
VerticalOptions="Center">
Margin="-20"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill" />
</Frame>
<Label Grid.Column="1"
Text="{Binding CardTitle}"
FontAttributes="Bold"
FontSize="18"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start" />
<BoxView Grid.Row="1"
Grid.ColumnSpan="2"
BackgroundColor="{Binding BorderColor}"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
Text="{Binding CardDescription}"
VerticalTextAlignment="Start"
VerticalOptions="Fill"
HorizontalOptions="Fill" />
</Grid>
</Frame>
</ControlTemplate>
</ContentPage.Resources>
...
</ContentPage>
When a ControlTemplate is declared as a resource, it must have a key specified with the
x:Key attribute so that it can be identified in the resource dictionary. In this example,
the root element of the CardViewControlTemplate is a Frame object. The Frame object
uses the RelativeSource markup extension to set its BindingContext to the runtime
object instance to which the template will be applied, which is known as the templated
parent. The Frame object uses a combination of Grid, Frame, Image, Label, and BoxView
objects to define the visual structure of a CardView object. The binding expressions of
these objects resolve against CardView properties, due to inheriting the BindingContext
from the root Frame element. For more information about the RelativeSource markup
extension, see Relative bindings.
Consume a ControlTemplate
A ControlTemplate can be applied to a ContentView derived custom control by setting
its ControlTemplate property to the control template object. Similarly, a ControlTemplate
can be applied to a ContentPage derived page by setting its ControlTemplate property
to the control template object. At runtime, when a ControlTemplate is applied, all of the
controls that are defined in the ControlTemplate are added to the visual tree of the
templated custom control, or templated page.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
...>
<StackLayout Margin="30">
<controls:CardView BorderColor="DarkGray"
CardTitle="John Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource
CardViewControlTemplate}" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Jane Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource
CardViewControlTemplate}" />
</StackLayout>
</ContentPage>
In this example, the controls in the CardViewControlTemplate become part of the visual
tree for each CardView object. Because the root Frame object for the control template
sets its BindingContext to the templated parent, the Frame and its children resolve their
binding expressions against the properties of each CardView object.
) Important
The ContentProperty for the TemplateBinding markup extension is Path. Therefore, the
"Path=" part of the markup extension can be omitted if the path is the first item in the
TemplateBinding expression. For more information about using these properties in a
binding expression, see Data binding.
2 Warning
The following XAML example shows a ControlTemplate for CardView objects, that uses
the TemplateBinding markup extension:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewControlTemplate">
BorderColor="{TemplateBinding BorderColor}"
CornerRadius="5"
HasShadow="True"
Padding="8"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<Frame IsClippedToBounds="True"
BorderColor="{TemplateBinding BorderColor}"
BackgroundColor="{TemplateBinding
IconBackgroundColor}"
CornerRadius="38"
HeightRequest="60"
WidthRequest="60"
HorizontalOptions="Center"
VerticalOptions="Center">
Margin="-20"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill" />
</Frame>
<Label Grid.Column="1"
Text="{TemplateBinding CardTitle}"
FontAttributes="Bold"
FontSize="18"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start" />
<BoxView Grid.Row="1"
Grid.ColumnSpan="2"
BackgroundColor="{TemplateBinding BorderColor}"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
Text="{TemplateBinding CardDescription}"
VerticalTextAlignment="Start"
VerticalOptions="Fill"
HorizontalOptions="Fill" />
</Grid>
</Frame>
</ControlTemplate>
</ContentPage.Resources>
...
</ContentPage>
The following XAML example shows an implicit style that consumes the
CardViewControlTemplate :
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewControlTemplate">
...
</ControlTemplate>
<Style TargetType="controls:CardView">
<Setter Property="ControlTemplate"
</Style>
</ContentPage.Resources>
<StackLayout Margin="30">
<controls:CardView BorderColor="DarkGray"
CardTitle="John Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png" />
<controls:CardView BorderColor="DarkGray"
CardTitle="Jane Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"/>
</StackLayout>
</ContentPage>
In this example, the implicit Style is automatically applied to each CardView object, and
sets the ControlTemplate property of each CardView to CardViewControlTemplate .
Redefine a control’s UI
When a ControlTemplate is instantiated and assigned to the ControlTemplate property
of a ContentView derived custom control, or a ContentPage derived page, the visual
structure defined for the custom control or page is replaced with the visual structure
defined in the ControlTemplate.
For example, the CardViewUI custom control defines its user interface using the
following XAML:
XAML
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ControlTemplateDemos.Controls.CardViewUI"
x:Name="this">
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
CornerRadius="5"
HasShadow="True"
Padding="8"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Frame IsClippedToBounds="True"
BorderColor="{Binding BorderColor,
FallbackValue='Black'}"
BackgroundColor="{Binding IconBackgroundColor,
FallbackValue='Gray'}"
CornerRadius="38"
HeightRequest="60"
WidthRequest="60"
HorizontalOptions="Center"
VerticalOptions="Center">
Margin="-20"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill" />
</Frame>
<Label Grid.Column="1"
FontAttributes="Bold"
FontSize="18"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start" />
<BoxView Grid.Row="1"
Grid.ColumnSpan="2"
BackgroundColor="{Binding BorderColor,
FallbackValue='Black'}"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
VerticalTextAlignment="Start"
VerticalOptions="Fill"
HorizontalOptions="Fill" />
</Grid>
</Frame>
</ContentView>
However, the controls that comprise this UI can be replaced by defining a new visual
structure in a ControlTemplate, and assigning it to the ControlTemplate property of a
CardViewUI object:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<ContentPage.Resources>
<ControlTemplate x:Key="CardViewCompressed">
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
BackgroundColor="{TemplateBinding
IconBackgroundColor}"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill"
HorizontalOptions="Center"
VerticalOptions="Center" />
<StackLayout Grid.Column="1">
FontAttributes="Bold" />
</StackLayout>
</Grid>
</ControlTemplate>
</ContentPage.Resources>
<StackLayout Margin="30">
<controls:CardViewUI BorderColor="DarkGray"
CardTitle="John Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource
CardViewCompressed}" />
<controls:CardViewUI BorderColor="DarkGray"
CardTitle="Jane Doe"
IconBackgroundColor="SlateGray"
IconImageSource="user.png"
ControlTemplate="{StaticResource
CardViewCompressed}" />
</StackLayout>
</ContentPage>
The following XAML shows a control template named TealTemplate that contains a
ContentPresenter in its visual structure:
XAML
<ControlTemplate x:Key="TealTemplate">
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Label Margin="20,0,0,0"
Text="{TemplateBinding HeaderText}"
TextColor="White"
FontSize="24"
VerticalOptions="Center" />
<BoxView Grid.Row="2"
Color="Teal" />
<Label x:Name="changeThemeLabel"
Grid.Row="2"
Margin="20,0,0,0"
Text="Change Theme"
TextColor="White"
HorizontalOptions="Start"
VerticalOptions="Center">
<Label.GestureRecognizers>
</Label.GestureRecognizers>
</Label>
<controls:HyperlinkLabel Grid.Row="2"
Margin="0,0,20,0"
Text="Help"
TextColor="White"
Url="https://learn.microsoft.com/dotnet/maui/"
HorizontalOptions="End"
VerticalOptions="Center" />
</Grid>
</ControlTemplate>
XAML
<controls:HeaderFooterPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-
namespace:ControlTemplateDemos.Controls"
ControlTemplate="{StaticResource TealTemplate}"
HeaderText="MyApp"
...>
<StackLayout Margin="10">
IsPassword="True" />
</StackLayout>
</controls:HeaderFooterPage>
At runtime, when TealTemplate is applied to the page, the page content is substituted
into the ContentPresenter defined in the control template:
After a control template has been instantiated, the template's OnApplyTemplate method
is called. The GetTemplateChild method should therefore be called from a
OnApplyTemplate override in the templated control or templated page.
) Important
The following XAML shows a control template named TealTemplate that can be applied
to ContentPage derived pages:
XAML
<ControlTemplate x:Key="TealTemplate">
<Grid>
...
<Label x:Name="changeThemeLabel"
Grid.Row="2"
Margin="20,0,0,0"
Text="Change Theme"
TextColor="White"
HorizontalOptions="Start"
VerticalOptions="Center">
<Label.GestureRecognizers>
</Label.GestureRecognizers>
</Label>
...
</Grid>
</ControlTemplate>
In this example, the Label element is named, and can be retrieved in the code for the
templated page. This is achieved by calling the GetTemplateChild method from the
OnApplyTemplate override for the templated page:
C#
Label themeLabel;
public AccessTemplateElementPage()
InitializeComponent();
base.OnApplyTemplate();
themeLabel = (Label)GetTemplateChild("changeThemeLabel");
In this example, the Label object named changeThemeLabel is retrieved once the
ControlTemplate has been instantiated. changeThemeLabel can then be accessed and
manipulated by the AccessTemplateElementPage class. The following screenshot shows
that the text displayed by the Label has been changed:
Bind to a viewmodel
A ControlTemplate can data bind to a viewmodel, even when the ControlTemplate binds
to the templated parent (the runtime object instance to which the template is applied).
The following XAML example shows a page that consumes a viewmodel named
PeopleViewModel :
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ControlTemplateDemos"
xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
...>
<ContentPage.BindingContext>
<local:PeopleViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<DataTemplate x:Key="PersonTemplate">
<controls:CardView BorderColor="DarkGray"
CardTitle="{Binding Name}"
CardDescription="{Binding Description}"
ControlTemplate="{StaticResource
CardViewControlTemplate}" />
</DataTemplate>
</ContentPage.Resources>
<StackLayout Margin="10"
BindableLayout.ItemsSource="{Binding People}"
BindableLayout.ItemTemplate="{StaticResource
PersonTemplate}" />
</ContentPage>
collection will be displayed using a CardView object. The visual structure of the CardView
object is defined using a ControlTemplate named CardViewControlTemplate :
XAML
<ControlTemplate x:Key="CardViewControlTemplate">
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
CornerRadius="5"
HasShadow="True"
Padding="8"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
FontAttributes="Bold"
FontSize="18"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start" />
<BoxView Grid.Row="1"
BackgroundColor="{Binding BorderColor}"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Grid.Row="2"
Text="{Binding CardDescription}"
VerticalTextAlignment="Start"
VerticalOptions="Fill"
HorizontalOptions="Fill" />
<Button Text="Delete"
CommandParameter="{Binding CardTitle}"
HorizontalOptions="End" />
</Grid>
</Frame>
</ControlTemplate>
In this example, the root element of the ControlTemplate is a Frame object. The Frame
object uses the RelativeSource markup extension to set its BindingContext to the
templated parent. The binding expressions of the Frame object and its children resolve
against CardView properties, due to inheriting the BindingContext from the root Frame
element. The following screenshot shows the page displaying the People collection:
While the objects in the ControlTemplate bind to properties on its templated parent, the
Button within the control template binds to both its templated parent, and to the
DeletePersonCommand in the viewmodel. This is because the Button.Command property
redefines its binding source to be the binding context of the ancestor whose binding
context type is PeopleViewModel , which is the StackLayout. The Path part of the binding
expressions can then resolve the DeletePersonCommand property. However, the
Button.CommandParameter property doesn't alter its binding source, instead inheriting it
from its parent in the ControlTemplate. Therefore, the CommandParameter property binds
to the CardTitle property of the CardView .
The overall effect of the Button bindings is that when the Button is tapped, the
DeletePersonCommand in the PeopleViewModel class is executed, with the value of the
.NET Multi-platform App UI (.NET MAUI) data templates provide the ability to define the
presentation of data on supported controls.
C#
The Person class defines Name , Age , and Location properties, which can be set when a
Person object is created. A control that displays collections, such as CollectionView, can
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataTemplates"
x:Class="DataTemplates.WithoutDataTemplatePage">
<StackLayout>
<CollectionView>
<CollectionView.ItemsSource>
</x:Array>
</CollectionView.ItemsSource>
</CollectionView>
</StackLayout>
</ContentPage>
In this example, items are added to the CollectionView by initializing its ItemsSource
property from an array of Person objects. CollectionView calls ToString when displaying
the objects in the collection. However, because there is no Person.ToString override,
ToString returns the type name of each object:
The Person object can override the ToString method to display meaningful data:
C#
...
return Name;
This results in the CollectionView displaying the Person.Name property value for each
object in the collection:
The Person.ToString override could return a formatted string consisting of the Name ,
Age , and Location properties. However, this approach only offers limited control over
the appearance of each item of data. For more flexibility, a DataTemplate can be created
that defines the appearance of the data.
Create a DataTemplate
A DataTemplate is used to specify the appearance of data, and typically uses data
binding to display data. A common usage scenario for data templates is when
displaying data from a collection of objects in a control such as a CollectionView or
CarouselView. For example, when a CollectionView is bound to a collection of Person
objects, the ItemTemplate property can be set to a DataTemplate that defines the
appearance of each Person object in the CollectionView. The DataTemplate will contain
objects that bind to property values of each Person object. For more information about
data binding, see Data binding.
7 Note
Data templates lower in the view hierarchy take precedence over those defined
higher up when they share x:Key attributes. For example, an app-level data
template will be overridden by a page-level data template, and a page-level data
template will be overridden by a control-level data template, or an inline data
template.
XAML
<CollectionView>
<CollectionView.ItemsSource>
</x:Array>
</CollectionView.ItemsSource>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
...
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataTemplates"
x:Class="DataTemplates.WithDataTemplatePageFromType">
<StackLayout>
<CollectionView>
<CollectionView.ItemsSource>
...
</x:Array>
</CollectionView.ItemsSource>
<CollectionView.ItemTemplate>
<DataTemplate>
<local:PersonView />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
In this example, the ItemTemplate property is set to a DataTemplate that's created from
a custom type that defines the view appearance. The custom type must derive from
ContentView:
XAML
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataTemplates.PersonView">
<Grid>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
</Grid>
</ContentView>
In this example, layout within the ContentView is managed by a Grid. The Grid contains
three Label objects that bind their Text properties to properties of each Person object
in the collection.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataTemplates"
x:Class="DataTemplates.WithDataTemplateResource">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="personTemplate">
<Grid>
...
</Grid>
</DataTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<CollectionView.ItemsSource>
...
</x:Array>
</CollectionView.ItemsSource>
</CollectionView>
</StackLayout>
</ContentPage>
In this example, the DataTemplate is assigned to the ItemTemplate property by using the
StaticResource markup extension. While the DataTemplate is defined in the page's
ResourceDictionary, it could also be defined at the control-level or app-level.
Create a DataTemplateSelector
A DataTemplateSelector can be used to choose a DataTemplate at runtime based on the
value of a data-bound property. This enables multiple data templates to be applied to
the same type of object, to choose their appearance at runtime. A data template
selector enables scenarios such as a CollectionView or CarouselView binding to a
collection of objects where the appearance of each object can be chosen at runtime by
the data template selector returning a specific DataTemplate.
C#
In this example, the OnSelectTemplate method returns a specific data template based on
the value of the DateOfBirth property. The returned data template is defined by the
ValidTemplate or InvalidTemplate property, which are set when consuming the data
template selector.
Limitations
DataTemplateSelector objects have the following limitations:
The DataTemplateSelector subclass must always return the same template for the
same data if queried multiple times.
The DataTemplateSelector subclass must not return another DataTemplateSelector
subclass.
The DataTemplateSelector subclass must not return new instances of a
DataTemplate on each call. Instead, the same instance must be returned. Failure to
do so will create a memory leak and will disable control virtualization.
Consume a DataTemplateSelector
A data template selector can be consumed by creating it as a resource and assigning its
instance to .NET MAUI control properties of type DataTemplate, such as ItemTemplate.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:local="clr-namespace:Selector"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Selector.MainPage">
<ContentPage.Resources>
<DataTemplate x:Key="validPersonTemplate">
<Grid>
...
</Grid>
</DataTemplate>
<DataTemplate x:Key="invalidPersonTemplate">
<Grid>
...
</Grid>
</DataTemplate>
<local:PersonDataTemplateSelector x:Key="personDataTemplateSelector"
ValidTemplate="{StaticResource
validPersonTemplate}"
InvalidTemplate="{StaticResource
invalidPersonTemplate}" />
</ContentPage.Resources>
...
</ContentPage>
XAML
<CollectionView x:Name="collectionView"
ItemTemplate="{StaticResource personDataTemplateSelector}"
/>
The following screenshot shows the result of the CollectionView applying the
PersonDataTemplateSelector to each object in the underlying collection:
In this example, any Person object that has a DateOfBirth property value greater than
or equal to 1980 is displayed in green, with the remaining objects being displayed in
red.
Triggers
Article • 03/03/2023 • 11 minutes to read
.NET Multi-platform App UI (.NET MAUI) triggers allow you to express actions
declaratively in XAML that change the appearance of controls based on events or data
changes. In addition, state triggers, which are a specialized group of triggers, define
when a VisualState should be applied.
You can assign a trigger directly to a control's Triggers collection, or add it to a page-
level or app-level resource dictionary to be applied to multiple controls.
Property triggers
A Trigger represents a trigger that applies property values, or performs actions, when
the specified property meets a specified condition.
The following example shows a Trigger that changes an Entry background color when it
receives focus:
XAML
<Entry.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused"
Value="True">
<Setter Property="BackgroundColor"
Value="Yellow" />
</Trigger>
</Entry.Triggers>
</Entry>
XAML
<ContentPage.Resources>
<Style TargetType="Entry">
<Style.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused"
Value="True">
<Setter Property="BackgroundColor"
Value="Yellow" />
</Trigger>
</Style.Triggers>
</Style>
</ContentPage.Resources>
Data triggers
A DataTrigger represents a trigger that applies property values, or performs actions,
when the bound data meets a specified condition. The Binding markup extension is
used to monitor for the specified condition.
The following example shows a DataTrigger that disables a Button when the Entry is
empty:
XAML
<Entry x:Name="entry"
Text=""
<Button Text="Save">
<Button.Triggers>
<DataTrigger TargetType="Button"
Value="0">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
In this example, when the length of the Entry is zero, the trigger is activated.
Tip
When evaluating Path=Text.Length always provide a default value for the target
property (eg. Text="" ) because otherwise it will be null and the trigger won't work
like you expect.
In addition, optional EnterActions and ExitActions collections can be specified. For more
information, see EnterActions and ExitActions.
Event triggers
An EventTrigger represents a trigger that applies a set of actions in response to an
event. Unlike Trigger, EventTrigger has no concept of termination of state, so the actions
will not be undone once the condition that raised the event is no longer true.
XAML
<EventTrigger Event="TextChanged">
<local:NumericValidationTriggerAction />
</EventTrigger>
7 Note
C#
double result;
2 Warning
Multi-triggers
A MultiTrigger represents a trigger that applies property values, or performs actions,
when a set of conditions are satisfied. All the conditions must be true before the Setter
objects are applied.
The following example shows a MultiTrigger that binds to two Entry objects:
XAML
<Entry x:Name="email"
Text="" />
<Entry x:Name="phone"
Text="" />
<Button Text="Save">
<Button.Triggers>
<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
Path=Text.Length}"
Value="0" />
Path=Text.Length}"
Value="0" />
</MultiTrigger.Conditions>
</MultiTrigger>
</Button.Triggers>
</Button>
XAML
<PropertyCondition Property="Text"
Value="OK" />
7 Note
The following example shows a property trigger that specifies an EnterAction and an
ExitAction :
XAML
<Entry.Triggers>
<Trigger TargetType="Entry"
Property="Entry.IsFocused"
Value="True">
<Trigger.EnterActions>
</Trigger.EnterActions>
<Trigger.ExitActions>
</Trigger.ExitActions>
</Trigger>
</Entry.Triggers>
</Entry>
C#
}),
easing: Easing.Linear);
7 Note
You can provide EnterActions and ExitActions as well as Setter objects in a trigger,
but be aware that the Setter objects are called immediately (they do not wait for
the EnterAction or ExitAction to complete).
State triggers
State triggers are a specialized group of triggers that define the conditions under which
a VisualState should be applied.
State triggers are added to the StateTriggers collection of a VisualState. This collection
can contain a single state trigger, or multiple state triggers. A VisualState will be applied
when any state triggers in the collection are active.
When using state triggers to control visual states, .NET MAUI uses the following
precedence rules to determine which trigger (and corresponding VisualState) will be
active:
If multiple triggers are simultaneously active (for example, two custom triggers) then the
first trigger declared in the markup takes precedence.
7 Note
State trigger
The StateTrigger class, which derives from the StateTriggerBase class, has an IsActive
bindable property. A StateTrigger triggers a VisualState change when the IsActive
property changes value.
The StateTriggerBase class, which is the base class for all state triggers, has an IsActive
property and an IsActiveChanged event. This event fires whenever a VisualState change
occurs. In addition, the StateTriggerBase class has overridable OnAttached and
OnDetached methods.
) Important
The StateTrigger.IsActive bindable property hides the inherited
StateTriggerBase.IsActive property.
The following XAML example shows a Style that includes StateTrigger objects:
XAML
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Checked">
<VisualState.StateTriggers>
IsActiveChanged="OnCheckedStateIsActiveChanged" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Black" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.StateTriggers>
IsActiveChanged="OnUncheckedStateIsActiveChanged" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="White" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
In this example, the implicit Style targets Grid objects. When the IsToggled property of
the bound object is true , the background color of the Grid is set to black. When the
IsToggled property of the bound object becomes false , a VisualState change is
In addition, every time a VisualState change occurs, the IsActiveChanged event for the
VisualState is raised. Each VisualState registers an event handler for this event:
C#
void OnCheckedStateIsActiveChanged(object sender, EventArgs e)
In this example, when a handler for the IsActiveChanged event is raised, the handler
outputs whether the VisualState is active or not. For example, the following messages
are output to the console window when changing from the Checked visual state to the
Unchecked visual state:
7 Note
Custom state triggers can be created by deriving from the StateTriggerBase class,
and overriding the OnAttached and OnDetached methods to perform any required
registrations and cleanup.
Adaptive trigger
An AdaptiveTrigger triggers a VisualState change when the window is a specified height
or width. This trigger has two bindable properties:
7 Note
The AdaptiveTrigger derives from the StateTriggerBase class and can therefore
attach an event handler to the IsActiveChanged event.
The following XAML example shows a Style that includes AdaptiveTrigger objects:
XAML
<Style TargetType="StackLayout">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Vertical">
<VisualState.StateTriggers>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Orientation"
Value="Vertical" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Horizontal">
<VisualState.StateTriggers>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Orientation"
Value="Horizontal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
In this example, the implicit Style targets StackLayout objects. When the window width is
between 0 and 800 device-independent units, StackLayout objects to which the Style is
applied will have a vertical orientation. When the window width is >= 800 device-
independent units, the VisualState change is triggered, and the StackLayout orientation
changes to horizontal.
XAML
<AdaptiveTrigger MinWindowWidth="800"
MinWindowHeight="1200"/>
In this example, the AdaptiveTrigger indicates that the corresponding VisualState will be
applied when the current window width is >= 800 device-independent units and the
current window height is >= 1200 device-independent units.
Property, of type object , which indicates the property being compared by the
trigger.
Value, of type object , which indicates the value at which the VisualState should be
applied.
7 Note
The following XAML example shows a Style that includes CompareStateTrigger objects:
XAML
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Checked">
<VisualState.StateTriggers>
Value="True" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Black" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.StateTriggers>
Value="False" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="White" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
...
<Grid>
<Frame BackgroundColor="White"
CornerRadius="12"
Margin="24"
HorizontalOptions="Center"
VerticalOptions="Center">
<StackLayout Orientation="Horizontal">
<CheckBox x:Name="checkBox"
VerticalOptions="Center" />
VerticalOptions="Center" />
</StackLayout>
</Frame>
</Grid>
In this example, the implicit Style targets Grid objects. When the IsChecked property of
the CheckBox is false , the background color of the Grid is set to white. When the
CheckBox.IsChecked property becomes true , a VisualState change is triggered, and the
background color of the Grid becomes black.
Device, of type string , which indicates the device platform on which the
VisualState should be applied.
7 Note
The DeviceStateTrigger derives from the StateTriggerBase class and can therefore
attach an event handler to the IsActiveChanged event.
The following XAML example shows a Style that includes DeviceStateTrigger objects:
XAML
<Style x:Key="DeviceStateTriggerPageStyle"
TargetType="ContentPage">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="iOS">
<VisualState.StateTriggers>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Silver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Android">
<VisualState.StateTriggers>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="#2196F3" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
In this example, the explicit Style targets ContentPage objects. ContentPage objects that
consume the style set their background color to silver on iOS, and to pale blue on
Android.
7 Note
XAML
<Style x:Key="OrientationStateTriggerPageStyle"
TargetType="ContentPage">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Portrait">
<VisualState.StateTriggers>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Silver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Landscape">
<VisualState.StateTriggers>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="White" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
In this example, the explicit Style targets ContentPage objects. ContentPage objects that
consume the style set their background color to silver when the orientation is portrait,
and set their background color to white when the orientation is landscape.
.NET MAUI windows
Article • 12/23/2022 • 7 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Window class provides the ability to create,
configure, show, and manage multiple windows.
of window overlays.
Page, of type Page, indicates the page being displayed by the window. This
property is the content property of the Window class, and therefore does not need
to be explicitly set.
Title , of type string , represents the title of the window.
These properties, with the exception of the Overlays property, are backed by
BindableProperty objects, which means that they can be targets of data bindings, and
styled.
object, which is raised on Android and Windows when the effective dots per inch
(DPI) for the window has changed.
For more information about the lifecycle events, and their associated overrides, see App
lifecycle.
The Window class also defines the following modal navigation events:
popped modally.
ModalPopping , with ModalPoppingEventArgs , which is raised when a view is modally
popped.
ModalPushed , with ModalPushedEventArgs , which is raised after a view has been
pushed modally.
ModalPushing , with ModalPushingEventArgs , which is raised when a view is modally
pushed.
PopCanceled , which is raised when a modal pop is cancelled.
The VisualElement class has a Window property that exposes the parent Window object.
This property can be accessed from any page, layout, or view, to manipulate Window
objects.
Create a Window
By default, .NET MAUI creates a Window object when you set the MainPage property to a
Page object in your App class. However, you can also override the CreateWindow method
in your App class to create a Window object:
C#
namespace MyMauiApp
public App()
InitializeComponent();
return window;
While the Window class has a default constructor and a constructor that accepts a Page
argument, which represents the root page of the app, you can also call the base
CreateWindow method to return the .NET MAUI created Window object.
In addition, you can also create your own Window -derived object:
C#
namespace MyMauiApp
The Window -derived class can then be consumed by creating a MyWindow object in the
CreateWindow override in your App class.
Regardless of how your Window object is created, it will be the parent of the root page in
your app.
Multi-window support
Multiple windows can be simultaneously opened on Android, iOS on iPad (iPadOS), Mac
Catalyst, and Windows. This can be achieved by creating a Window object and opening it
using the OpenWindow method on the Application object:
C#
Application.Current.OpenWindow(secondWindow);
C#
Application.Current.CloseWindow(secondWindow);
Application.Current.CloseWindow(GetParentWindow());
) Important
C#
using Foundation;
using Microsoft.Maui;
using UIKit;
namespace MyMauiApp;
[Register("SceneDelegate")]
Then, in the XML editor, open the Platforms > iOS > Info.plist file and the Platforms >
MacCatalyst > Info.plist file and add the following XML to the end of each file:
XML
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>__MAUI_DEFAULT_SCENE_CONFIGURATION__</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
) Important
2 Warning
C#
public App()
InitializeComponent();
Width = 700,
Height = 500,
X = 100,
Y = 100
};
Alternatively, a window can be positioned and sized by accessing the Window property
from any page, layout, or view. For example, the following code shows how to position a
window in the center of the screen:
C#
For information about obtaining the device's screen metrics, see Device display
information.
Mac Catalyst
Mac Catalyst doesn't support resizing or repositioning windows programmatically.
However, a workaround to enable resizing is to set the MinimumWidth and MaximumWidth
properties to the desired width of the window, and the MinimumHeight and
MaximumHeight properties to the desired height of the window. This will trigger a resize,
and you can then revert the properties back to their original values:
C#
Window.MinimumWidth = 700;
Window.MaximumWidth = 700;
Window.MinimumHeight = 500;
Window.MaximumHeight = 500;
Window.MinimumWidth = 0;
Window.MinimumHeight = 0;
Window.MaximumWidth = double.PositiveInfinity;
Window.MaximumHeight = double.PositiveInfinity;
});
Basic animation
Article • 02/09/2023 • 8 minutes to read
The .NET Multi-platform App UI (.NET MAUI) animation classes target different
properties of visual elements, with a typical basic animation progressively changing a
property from one value to another over a period of time.
By default, each animation will take 250 milliseconds. However, a duration for each
animation can be specified when creating the animation.
7 Note
The animation extension methods in the ViewExtensions class are all asynchronous and
return a Task<bool> object. The return value is false if the animation completes, and
true if the animation is cancelled. Therefore, when animation operations are combined
with the await operator it becomes possible to create sequential animations with
subsequent animation methods executing after the previous method has completed. For
more information, see Compound animations.
If there's a requirement to let an animation complete in the background, then the await
operator can be omitted. In this scenario, the animation extension methods will quickly
return after initiating the animation, with the animation occurring in the background.
This operation can be taken advantage of when creating composite animations. For
more information, see Composite animations.
Single animations
Each extension method in the ViewExtensions class implements a single animation
operation that progressively changes a property from one value to another value over a
period of time.
Rotation
Rotation is performed with the RotateTo method, which progressively changes the
Rotation property of an element:
C#
image.Rotation = 0;
In this example, an Image instance is rotated up to 360 degrees over 2 seconds (2000
milliseconds). The RotateTo method obtains the current Rotation property value of the
element for the start of the animation, and then rotates from that value to its first
argument (360). Once the animation is complete, the image's Rotation property is reset
to 0. This ensures that the Rotation property doesn't remain at 360 after the animation
concludes, which would prevent additional rotations.
7 Note
In addition to the RotateTo method, there are also RotateXTo and RotateYTo
methods that animate the RotationX and RotationY properties, respectively.
Relative rotation
Relative rotation is performed with the RelRotateTo method, which progressively
changes the Rotation property of an element:
C#
await image.RelRotateTo(360, 2000);
In this example, an Image instance is rotated 360 degrees from its starting position over
2 seconds (2000 milliseconds). The RelRotateTo method obtains the current Rotation
property value of the element for the start of the animation, and then rotates from that
value to the value plus its first argument (360). This ensures that each animation will
always be a 360 degrees rotation from the starting position. Therefore, if a new
animation is invoked while an animation is already in progress, it will start from the
current position and may end at a position that is not an increment of 360 degrees.
Scaling
Scaling is performed with the ScaleTo method, which progressively changes the Scale
property of an element:
C#
In this example, an Image instance is scaled up to twice its size over 2 seconds (2000
milliseconds). The ScaleTo method obtains the current Scale property value of the
element for the start of the animation, and then scales from that value to its first
argument. This has the effect of expanding the size of the image to twice its size.
7 Note
In addition to the ScaleTo method, there are also ScaleXTo and ScaleYTo methods
that animate the ScaleX and ScaleY properties, respectively.
Relative scaling
Relative scaling is performed with the RelScaleTo method, which progressively changes
the Scale property of an element:
C#
In this example, an Image instance is scaled up to twice its size over 2 seconds (2000
milliseconds). The RelScaleTo method obtains the current Scale property value of the
element for the start of the animation, and then scales from that value to the value plus
its first argument. This ensures that each animation will always be a scaling of 2 from the
starting position.
Given an Image that has been placed at the center of a layout, the following code
example demonstrates rotating the image around the center of the layout by setting its
AnchorY property:
C#
To rotate the Image instance around the center of the layout, the AnchorX and AnchorY
properties must be set to values that are relative to the width and height of the Image.
In this example, the center of the Image is defined to be at the center of the layout, and
so the default AnchorX value of 0.5 does not require changing. However, the AnchorY
property is redefined to be a value from the top of the Image to the center point of the
layout. This ensures that the Image makes a full rotation of 360 degrees around the
center point of the layout.
Translation
Translation is performed with the TranslateTo method, which progressively changes the
TranslationX and TranslationY properties of an element:
C#
In this example, the Image instance is translated horizontally and vertically over 1
second (1000 milliseconds). The TranslateTo method simultaneously translates the image
100 device-independent units to the left, and 100 device-independent units upwards.
This is because the first and second arguments are both negative numbers. Providing
positive numbers would translate the image to the right, and down.
) Important
If an element is initially laid out off screen and then translated onto the screen,
after translation the element's input layout remains off screen and the user can't
interact with it. Therefore, it's recommended that a view should be laid out in its
final position, and then any required translations performed.
Fading
Fading is performed with the FadeTo method, which progressively changes the Opacity
property of an element:
C#
image.Opacity = 0;
In this example, the Image instance fades in over 4 seconds (4000 milliseconds). The
FadeTo method obtains the current Opacity property value of the element for the start
of the animation, and then fades in from that value to its first argument.
Compound animations
A compound animation is a sequential combination of animations, and can be created
with the await operator:
C#
In this example, the Image instance is translated over 6 seconds (6000 milliseconds). The
translation of the Image uses five animations, with the await operator indicating that
each animation executes sequentially. Therefore, subsequent animation methods
execute after the previous method has completed.
Composite animations
A composite animation is a combination of animations where two or more animations
run simultaneously. Composite animations can be created by combining awaited and
non-awaited animations:
C#
image.RotateTo(360, 4000);
In this example, the Image instance is scaled and simultaneously rotated over 4 seconds
(4000 milliseconds). The scaling of the Image uses two sequential animations that occur
at the same time as the rotation. The RotateTo method executes without an await
operator and returns immediately, with the first ScaleTo animation then beginning. The
await operator on the first ScaleTo method delays the second ScaleTo method until the
first ScaleTo method has completed. At this point the RotateTo animation is half
completed and the Image will be rotated 180 degrees. During the final 2 seconds (2000
milliseconds), the second ScaleTo animation and the RotateTo animation both complete.
C#
await Task.WhenAny<bool>
image.RotateTo(360, 4000),
image.ScaleTo(2, 2000)
);
In this example, the Task.WhenAny method contains two tasks. The first task rotates an
Image instance over 4 seconds (4000 milliseconds), and the second task scales the
image over 2 seconds (2000 milliseconds). When the second task completes, the
Task.WhenAny method call completes. However, even though the RotateTo method is still
running, the second ScaleTo method can begin.
The Task.WhenAll method completes when all the methods in its collection have
completed, as demonstrated in the following code example:
C#
// 10 minute animation
await Task.WhenAll
);
In this example, the Task.WhenAll method contains three tasks, each of which executes
over 10 minutes. Each Task makes a different number of 360 degree rotations – 307
rotations for RotateTo, 251 rotations for RotateXTo, and 199 rotations for RotateYTo.
These values are prime numbers, therefore ensuring that the rotations aren't
synchronized and hence won't result in repetitive patterns.
Canceling animations
An app can cancel one or more animations with a call to the CancelAnimations
extension method:
C#
image.CancelAnimations();
In this example, all animations that are running on the Image instance are immediately
canceled.
Easing functions
Article • 02/09/2023 • 3 minutes to read
.NET Multi-platform App UI (.NET MAUI) includes an Easing class that enables you to
specify a transfer function that controls how animations speed up or slow down as
they're running.
The Easing class defines a number of easing functions that can be consumed by
animations:
The In and Out suffixes indicate if the effect provided by the easing function is
noticeable at the beginning of the animation, at the end, or both.
In addition, custom easing functions can be created. For more information, see Custom
easing functions.
C#
By specifying an easing function for an animation, the animation velocity becomes non-
linear and produces the effect provided by the easing function. Omitting an easing
function when creating an animation causes the animation to use the default Linear
easing function, which produces a linear velocity.
For more information about using the animation extension methods in the
ViewExtensions class, see Basic animation. Easing functions can also be consumed by
the Animation class. For more information, see Custom animation.
1. Create a method that takes a double argument, and returns a double result.
2. Create a Func<double, double> .
3. Specify the easing function as the argument to the Easing constructor.
In all three cases, the custom easing function should return a value between 0 and 1.
C#
In this example, the CustomEase method truncates the incoming value to the values 0,
0.2, 0.4, 0.6, 0.8, and 1. Therefore, the Image instance is translated in discrete jumps,
rather than smoothly.
C#
In this example, the CustomEaseFunc represents an easing function that starts off fast,
slows down and reverses course, and then reverses course again to accelerate quickly
towards the end. Therefore, while the overall movement of the Image instance is
downwards, it also temporarily reverses course halfway through the animation.
C#
In this example, the custom easing function is specified as a lambda function argument
to the Easing constructor, and uses the Math.Cos method to create a slow drop effect
that's dampened by the Math.Exp method. Therefore, the Image instance is translated
so that it appears to drop to its final position.
Custom animation
Article • 04/03/2023 • 9 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Animation class is the building block of all
.NET MAUI animations, with the extension methods in the ViewExtensions class creating
one or more Animation objects.
Running an animation created with the Animation class, which may or may not include
child animations, is achieved by calling the Commit method. This method specifies the
duration of the animation, and amongst other items, a callback that controls whether to
repeat the animation.
7 Note
The Animation class has an IsEnabled property that can be examined to determine
if animations have been disabled by the operating system, such as when power
saving mode is activated.
Create an animation
When creating an Animation object, typically, a minimum of three parameters are
required, as demonstrated in the following code example:
C#
In this example, an animation of the Scale property of an Image instance is defined from
a value of 1 to a value of 2. The animated value is passed to the callback specified as the
first argument, where it's used to change the value of the Scale property.
C#
animation.Commit(this, "SimpleAnimation", 16, 2000, Easing.Linear, (v, c) =>
image.Scale = 1, () => true);
7 Note
The Commit method doesn't return a Task object. Instead, notifications are
provided through callback methods.
The first argument ( owner ) identifies the owner of the animation. This can be the
visual element on which the animation is applied, or another visual element, such
as the page.
The second argument ( name ) identifies the animation with a name. The name is
combined with the owner to uniquely identify the animation. This unique
identification can then be used to determine whether the animation is running
(AnimationIsRunning), or to cancel it (AbortAnimation).
The third argument ( rate ) indicates the number of milliseconds between each call
to the callback method defined in the Animation constructor.
The fourth argument ( length ) indicates the duration of the animation, in
milliseconds.
The fifth argument (Easing) defines the easing function to be used in the
animation. Alternatively, the easing function can be specified as an argument to
the Animation constructor. For more information about easing functions, see
Easing functions.
The sixth argument ( finished ) is a callback that will be executed when the
animation has completed. This callback takes two arguments, with the first
argument indicating a final value, and the second argument being a bool that's set
to true if the animation was canceled. Alternatively, the finished callback can be
specified as an argument to the Animation constructor. However, with a single
animation, if finished callbacks are specified in both the Animation constructor
and the Commit method, only the callback specified in the Commit method will be
executed.
The seventh argument ( repeat ) is a callback that allows the animation to be
repeated. It's called at the end of the animation, and returning true indicates that
the animation should be repeated.
In the above example, the overall effect is to create an animation that increases the
Scale property of an Image instance from 1 to 2, over 2 seconds (2000 milliseconds),
using the Linear easing function. Each time the animation completes, its Scale property
is reset to 1 and the animation repeats.
7 Note
Child animations
The Animation class also supports child animations, which are Animation objects to
which other Animation objects are added as children. This enables a series of animations
to be run and synchronized. The following code example demonstrates creating and
running child animations:
C#
parentAnimation.Add(0, 1, rotateAnimation);
parentAnimation.Add(0.5, 1, scaleDownAnimation);
C#
new Animation
The overall effect of this example is that the animation occurs over 4 seconds (4000
milliseconds). The scaleUpAnimation animates the Scale property from 1 to 2, over 2
seconds. The scaleDownAnimation then animates the Scale property from 2 to 1, over 2
seconds. While both scale animations are occurring, the rotateAnimation animates the
Rotation property from 0 to 360, over 4 seconds. Both scaling animations also use
easing functions. The SpringIn easing function causes the Image instance to initially
shrink before getting larger, and the SpringOut easing function causes the Image to
become smaller than its actual size towards the end of the complete animation.
There are a number of differences between an Animation object that uses child
animations, and one that doesn't:
When using child animations, the finished callback on a child animation indicates
when the child has completed, and the finished callback passed to the Commit
method indicates when the entire animation has completed.
When using child animations, returning true from the repeat callback on the
Commit method will not cause the animation to repeat, but the animation will
continue to run without new values.
When including an easing function in the Commit method, and the easing function
returns a value greater than 1, the animation will be terminated. If the easing
function returns a value less than 0, the value is clamped to 0. To use an easing
function that returns a value less than 0 or greater than 1, it must specified in one
of the child animations, rather than in the Commit method.
The Animation class also includes WithConcurrent methods that can be used to add
child animations to a parent Animation object. However, their begin and finish
argument values aren't restricted to 0 to 1, but only that part of the child animation that
corresponds to a range of 0 to 1 will be active. For example, if a WithConcurrent method
call defines a child animation that targets a Scale property from 1 to 6, but with begin
and finish values of -2 and 3, the begin value of -2 corresponds to a Scale value of 1,
and the finish value of 3 corresponds to a Scale value of 6. Because values outside the
range of 0 and 1 play no part in an animation, the Scale property will only be animated
from 3 to 6.
Cancel an animation
An app can cancel a custom animation with a call to the AbortAnimation extension
method:
C#
this.AbortAnimation ("SimpleAnimation");
C#
start: 0,
The resulting animation provides the appearance of advancing the page background
through the colors of the rainbow.
The solution to this problem is to not have the ColorTo method target a particular
Color property. Instead, it can be written with a callback method that passes the
interpolated Color value back to the caller. In addition, the method will take start and
end Color arguments.
The ColorTo method can be implemented as an extension method that uses the
Animate method in the AnimationExtensions class to provide its functionality. This is
because the Animate method can be used to target properties that aren't of type
double , as demonstrated in the following code example:
C#
Color.FromRgba(fromColor.Red + t * (toColor.Red -
fromColor.Red),
fromColor.Green + t * (toColor.Green -
fromColor.Green),
fromColor.Blue + t * (toColor.Blue -
fromColor.Blue),
fromColor.Alpha + t * (toColor.Alpha -
fromColor.Alpha));
self.AbortAnimation("ColorTo");
return taskCompletionSource.Task;
The Animate method requires a transform argument, which is a callback method. The
input to this callback is always a double ranging from 0 to 1. Therefore, in this example
the ColorTo method defines its own transform Func that accepts a double ranging from
0 to 1, and that returns a Color value corresponding to that value. The Color value is
calculated by interpolating the Red , Green , Blue , and Alpha values of the two supplied
Color arguments. The Color value is then passed to the callback method to be applied
to a property. This approach allows the ColorTo method to animate any specified Color
property:
C#
await Task.WhenAll(
In this code example, the ColorTo method animates the TextColor and
BackgroundColor properties of a Label, the BackgroundColor property of a page, and
the Color property of a BoxView.
Brushes
Article • 02/09/2023 • 2 minutes to read
A .NET Multi-platform App UI (.NET MAUI) brush enables you to paint an area, such as
the background of a control, using different approaches.
The Brush class is an abstract class that paints an area with its output. Classes that derive
from Brush describe different ways of painting an area. The following list describes the
different brush types available in .NET MAUI:
SolidColorBrush, which paints an area with a solid color. For more information, see
Solid color brushes.
LinearGradientBrush, which paints an area with a linear gradient. For more
information, see Linear gradient brushes.
RadialGradientBrush, which paints an area with a radial gradient. For more
information, see Radial gradient brushes.
Instances of these brush types can be assigned to the Stroke and Fill properties of a
Shape, the Stroke property of a Border, the Brush property of a Shadow , and the
Background property of a VisualElement.
7 Note
The Brush class also has an IsNullOrEmpty method that returns a bool that represents
whether the brush is defined or not.
Solid color brushes
Article • 04/03/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) SolidColorBrush class derives from the
Brush class, and is used to paint an area with a solid color. There are a variety of
approaches to specifying the color of a SolidColorBrush. For example, you can specify its
color with a Color value or by using one of the predefined SolidColorBrush objects
provided by the Brush class.
The SolidColorBrush class defines the Color property, of type Color, which represents
the color of the brush. This property is backed by a BindableProperty object, which
means that it can be the target of data bindings, and styled.
The SolidColorBrush class also has an IsEmpty method that returns a bool that
represents whether the brush has been assigned a color.
Create a SolidColorBrush
There are three main techniques for creating a SolidColorBrush. You can create a
SolidColorBrush from a Color, use a predefined brush, or create a SolidColorBrush using
hexadecimal notation.
XAML
<Frame Background="DarkBlue"
BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120" />
In this example, the background of the Frame is painted with a dark blue
SolidColorBrush:
Alternatively, the Color value can be specified using property tag syntax:
XAML
<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
</Frame.Background>
</Frame>
In this example, the background of the Frame is painted with a SolidColorBrush whose
color is specified by setting the SolidColorBrush.Color property.
XAML
BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120" />
C#
Background = Brush.Indigo,
BorderColor = Colors.LightGray,
// ...
};
In this example, the background of the Frame is painted with an indigo SolidColorBrush:
For a list of predefined SolidColorBrush objects provided by the Brush class, see Solid
color brushes.
In addition, a color can be specified as #aarrggbb where aa specifies the alpha value, or
transparency, of the color. This approach enables you to create colors that are partially
transparent.
The following example sets the color value of a SolidColorBrush using hexadecimal
notation:
XAML
<Frame Background="#FF9988"
BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120" />
Gradients
Article • 04/03/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) GradientBrush class derives from the Brush
class, and is an abstract class that describes a gradient, which is composed of gradient
stops. A gradient brush paints an area with multiple colors that blend into each other
along an axis.
Classes that derive from GradientBrush describe different ways of interpreting gradient
stops, and .NET MAUI provides the following gradient brushes:
7 Note
Gradient stops
Gradient stops are the building blocks of a gradient brush, and specify the colors in the
gradient and their location along the gradient axis. Gradient stops are specified using
GradientStop objects.
Color , of type Color, which represents the color of the gradient stop.
Offset , of type float , which represents the location of the gradient stop within
the gradient vector. The default value of this property is 0, and valid values are in
the range 0.0-1.0. The closer this value is to 0, the closer the color is to the start of
the gradient. Similarly, the closer this value is to 1, the closer the color is to the end
of the gradient.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
) Important
The coordinate system used by gradients is relative to a bounding box for the
output area. 0 indicates 0 percent of the bounding box, and 1 indicates 100 percent
of the bounding box. Therefore, (0.5,0.5) describes a point in the middle of the
bounding box, and (1,1) describes a point at the bottom right of the bounding box.
The following XAML example creates a diagonal LinearGradientBrush with four colors:
XAML
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,1">
<GradientStop Color="Yellow"
Offset="0.0" />
<GradientStop Color="Red"
Offset="0.25" />
<GradientStop Color="Blue"
Offset="0.75" />
<GradientStop Color="LimeGreen"
Offset="1.0" />
</LinearGradientBrush>
The color of each point between gradient stops is interpolated as a combination of the
color specified by the two bounding gradient stops. The following diagram shows the
gradient stops from the previous example:
In this diagram, the circles mark the position of gradient stops, and the dashed line
shows the gradient axis. The first gradient stop specifies the color yellow at an offset of
0.0. The second gradient stop specifies the color red at an offset of 0.25. The points
between these two gradient stops gradually change from yellow to red as you move
from left to right along the gradient axis. The third gradient stop specifies the color blue
at an offset of 0.75. The points between the second and third gradient stops gradually
change from red to blue. The fourth gradient stop specifies the color lime green at an
offset of 1.0. The points between the third and fourth gradient stops gradually change
from blue to lime green.
Linear gradient brushes
Article • 12/23/2022 • 3 minutes to read
The .NET Multi-platform App UI (.NET MAUI) LinearGradientBrush class derives from the
GradientBrush class, and paints an area with a linear gradient, which blends two or more
colors along a line known as the gradient axis. GradientStop objects are used to specify
the colors in the gradient and their positions. For more information about GradientStop
objects, see Gradients.
coordinates of the linear gradient. The default value of this property is (1,1).
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The LinearGradientBrush class also has an IsEmpty method that returns a bool that
represents whether the brush has been assigned any GradientStop objects.
7 Note
Linear gradients can also be created with the linear-gradient() CSS function.
Create a LinearGradientBrush
A linear gradient brush's gradient stops are positioned along the gradient axis. The
orientation and size of the gradient axis can be changed using the brush's StartPoint
and EndPoint properties. By manipulating these properties, you can create horizontal,
vertical, and diagonal gradients, reverse the gradient direction, condense the gradient
spread, and more.
The StartPoint and EndPoint properties are relative to the area being painted. (0,0)
represents the top-left corner of the area being painted, and (1,1) represents the
bottom-right corner of the area being painted. The following diagram shows the
gradient axis for a diagonal linear gradient brush:
In this diagram, the dashed line shows the gradient axis, which highlights the
interpolation path of the gradient from the start point to the end point.
The following XAML example shows a horizontal LinearGradientBrush that's set as the
Background of a Frame:
XAML
<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<LinearGradientBrush EndPoint="1,0">
<GradientStop Color="Yellow"
Offset="0.1" />
<GradientStop Color="Green"
Offset="1.0" />
</LinearGradientBrush>
</Frame.Background>
</Frame>
In this example, the background of the Frame is painted with a LinearGradientBrush that
interpolates from yellow to green horizontally:
Create a vertical linear gradient
To create a vertical linear gradient, create a LinearGradientBrush object and set its
StartPoint to (0,0) and its EndPoint to (0,1). Then, add two or more GradientStop
The following XAML example shows a vertical LinearGradientBrush that's set as the
Background of a Frame:
XAML
<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="Yellow"
Offset="0.1" />
<GradientStop Color="Green"
Offset="1.0" />
</LinearGradientBrush>
</Frame.Background>
</Frame>
In this example, the background of the Frame is painted with a LinearGradientBrush that
interpolates from yellow to green vertically:
Create a diagonal linear gradient
To create a diagonal linear gradient, create a LinearGradientBrush object and set its
StartPoint to (0,0) and its EndPoint to (1,1). Then, add two or more GradientStop
The following XAML example shows a diagonal LinearGradientBrush that's set as the
Background of a Frame:
XAML
<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<LinearGradientBrush>
<GradientStop Color="Yellow"
Offset="0.1" />
<GradientStop Color="Green"
Offset="1.0" />
</LinearGradientBrush>
</Frame.Background>
</Frame>
In this example, the background of the Frame is painted with a LinearGradientBrush that
interpolates from yellow to green diagonally:
Radial gradient brushes
Article • 12/23/2022 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) RadialGradientBrush class derives from the
GradientBrush class, and paints an area with a radial gradient, which blends two or more
colors across a circle. GradientStop objects are used to specify the colors in the gradient
and their positions. For more information about GradientStop objects, see Gradients.
Center , of type Point , which represents the center point of the circle for the radial
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The RadialGradientBrush class also has an IsEmpty method that returns a bool that
represents whether the brush has been assigned any GradientStop objects.
7 Note
Radial gradients can also be created with the radial-gradient() CSS function.
Create a RadialGradientBrush
A radial gradient brush's gradient stops are positioned along a gradient axis defined by
a circle. The gradient axis radiates from the center of the circle to its circumference. The
position and size of the circle can be changed using the brush's Center and Radius
properties. The circle defines the end point of the gradient. Therefore, a gradient stop at
1.0 defines the color at the circle's circumference. A gradient stop at 0.0 defines the
color at the center of the circle.
To create a radial gradient, create a RadialGradientBrush object and set its Center and
Radius properties. Then, add two or more GradientStop objects to the
RadialGradientBrush.GradientStops collection, that specify the colors in the gradient
XAML
<Frame BorderColor="LightGray"
HasShadow="True"
CornerRadius="12"
HeightRequest="120"
WidthRequest="120">
<Frame.Background>
<RadialGradientBrush>
<GradientStop Color="Red"
Offset="0.1" />
<GradientStop Color="DarkBlue"
Offset="1.0" />
</RadialGradientBrush>
</Frame.Background>
</Frame>
In this example, the background of the Frame is painted with a RadialGradientBrush that
interpolates from red to dark blue. The center of the radial gradient is positioned in the
center of the Frame:
The following XAML example moves the center of the radial gradient to the top-left
corner of the Frame:
XAML
<GradientStop Color="Red"
Offset="0.1" />
<GradientStop Color="DarkBlue"
Offset="1.0" />
</RadialGradientBrush>
In this example, the background of the Frame is painted with a RadialGradientBrush that
interpolates from red to dark blue. The center of the radial gradient is positioned in the
top-left of the Frame:
The following XAML example moves the center of the radial gradient to the bottom-
right corner of the Frame:
XAML
<GradientStop Color="Red"
Offset="0.1" />
<GradientStop Color="DarkBlue"
Offset="1.0" />
</RadialGradientBrush>
In this example, the background of the Frame is painted with a RadialGradientBrush that
interpolates from red to dark blue. The center of the radial gradient is positioned in the
bottom-right of the Frame:
Controls
Article • 03/03/2023 • 6 minutes to read
The user interface of a .NET Multi-platform App UI (.NET MAUI) app is constructed of
objects that map to the native controls of each target platform.
The main control groups used to create the user interface of a .NET MAUI app are
pages, layouts, and views. A .NET MAUI page generally occupies the full screen or
window. The page usually contains a layout, which contains views and possibly other
layouts. Pages, layouts, and views derive from the VisualElement class. This class
provides a variety of properties, methods, and events that are useful in derived classes.
7 Note
ListView and TableView also support the use of cells. Cells are specialized elements
used for items in a table, that describe how each item should be rendered.
Pages
.NET MAUI apps consist of one or more pages. A page usually occupies all of the screen,
or window, and each page typically contains at least one layout.
Page Description
ContentPage ContentPage displays a single view, and is the most common page type. For
more information, see ContentPage.
FlyoutPage FlyoutPage is a page that manages two related pages of information – a flyout
page that presents items, and a detail page that presents details about items
on the flyout page. For more information, see FlyoutPage.
TabbedPage TabbedPage consists of a series of pages that are navigable by tabs across the
top or bottom of the page, with each tab loading the page content. For more
information, see TabbedPage.
Layouts
.NET MAUI layouts are used to compose user-interface controls into visual structures,
and each layout typically contains multiple views. Layout classes typically contain logic
to set the position and size of child elements.
Layout Description
Grid Grid positions its child elements in a grid of rows and columns. For more
information, see Grid.
Views
.NET MAUI views are the UI objects such as labels, buttons, and sliders that are
commonly known as controls or widgets in other environments.
View Description
BlazorWebView BlazorWebView enables you to host a Blazor web app in your .NET MAUI app.
For more information, see BlazorWebView.
Border Border is a container control that draws a border, background, or both, around
another control. For more information, see Border.
BoxView BoxView draws a rectangle or square, of a specified width, height, and color.
For more information, see BoxView.
Button Button displays text and responds to a tap or click that directs an app to carry
out a task. For more information, see Button.
CarouselView CarouselView displays a scrollable list of data items, where users swipe to move
through the collection. For more information, see CarouselView.
CheckBox CheckBox enables you to select a boolean value using a type of button that
can either be checked or empty. For more information, see CheckBox.
CollectionView CollectionView displays a scrollable list of selectable data items, using different
layout specifications. For more information, see CollectionView.
DatePicker DatePicker enables you to select a date with the platform date picker. For more
information, see DatePicker.
Editor Editor enables you to enter and edit multiple lines of text. For more
information, see Editor.
Ellipse Ellipse displays an ellipse or circle. For more information, see Ellipse.
Entry Entry enables you to enter and edit a single line of text. For more information,
see Entry.
Frame Frame is used to wrap a view or layout with a border that can be configured
with color, shadow, and other options. For more information, see Frame.
Image Image displays an image that can be loaded from a local file, a URI, an
embedded resource, or a stream. For more information, see Image.
ImageButton ImageButton displays an image and responds to a tap or click that direct an
app to carry out a task. For more information, see ImageButton.
Label Label displays single-line and multi-line text. For more information, see Label.
Line Line displays a line from a start point to an end point. For more information,
see Line.
ListView ListView displays a scrollable list of selectable data items. For more
information, see ListView.
Path Path display curves and complex shapes. For more information, see Path.
Picker Picker displays a short list of items, from which an item can be selected. For
more information, see Picker.
Polyline Polyline displays a series of connected straight lines. For more information, see
Polyline.
ProgressBar ProgressBar uses an animation to show that the app is progressing through a
lengthy activity. For more information, see ProgressBar.
RadioButton RadioButton is a type of button that allows the selection of one option from a
set. For more information, see RadioButton.
Rectangle Rectangle displays a rectangle or square. For more information, see Rectangle.
RoundRectangle RoundRectangle displays a rectangle or square with rounded corners. For more
information, see Rectangle.
ScrollView ScrollView provides scrolling of its content, which is typically a layout. For more
information, see ScrollView.
SearchBar SearchBar is a user input control used to initiate a search. For more
information, see SearchBar.
Slider Slider enables you to select a double value from a continuous range. For more
information, see Slider.
Stepper Stepper enables you to select a double value from a range of incremental
values. For more information, see Stepper.
SwipeView SwipeView is a container control that wraps around an item of content, and
provides context menu items that are revealed by a swipe gesture. For more
information, see SwipeView.
View Description
Switch Switch enables you to select a boolean value using a type of button that can
either be on or off. For more information, see Switch.
TableView TableView displays a table of scrollable items that can be grouped into
sections. For more information, see TableView.
TimePicker TimePicker enables you to select a time with the platform time picker. For
more information, see TimePicker.
TwoPaneView TwoPaneView represents a container with two views that size and position their
content in the available space, either side-by-side or top-to-bottom. For more
information, see TwoPaneView.
WebView WebView displays web pages or local HTML content. For more information, see
WebView.
Align and position .NET MAUI controls
Article • 02/09/2023 • 4 minutes to read
Every .NET Multi-platform App UI (.NET MAUI) control that derives from View, which
includes views and layouts, has HorizontalOptions and VerticalOptions properties, of
type LayoutOptions . The LayoutOptions structure encapsulates a view's preferred
alignment, which determines its position and size within its parent layout when the
parent layout contains unused space (that is, the parent layout is larger than the
combined size of all its children).
In addition, the Margin and Padding properties position controls relative to adjacent, or
child controls. For more information, see Position controls.
The Start , Center , End , and Fill fields are used to define the view's alignment within
the parent layout:
For horizontal alignment, Start positions the View on the left hand side of the
parent layout, and for vertical alignment, it positions the View at the top of the
parent layout.
For horizontal and vertical alignment, Center horizontally or vertically centers the
View.
For horizontal alignment, End positions the View on the right hand side of the
parent layout, and for vertical alignment, it positions the View at the bottom of the
parent layout.
For horizontal alignment, Fill ensures that the View fills the width of the parent
layout, and for vertical alignment, it ensures that the View fills the height of the
parent layout.
7 Note
A StackLayout does not respect the Start , Center , End , and Fill LayoutOptions fields
on child views that are in the same direction as the StackLayout orientation. Therefore, a
vertically oriented StackLayout ignores the Start , Center , End , or Fill fields if they are
set on the VerticalOptions properties of child views. Similarly, a horizontally oriented
StackLayout ignores the Start , Center , End , or Fill fields if they are set on the
HorizontalOptions properties of child views.
) Important
The following XAML example demonstrates a vertically oriented StackLayout where each
child Label sets its HorizontalOptions property to one of the four alignment fields from
the LayoutOptions structure:
XAML
<StackLayout>
...
</StackLayout>
The Margin property represents the distance between an element and its adjacent
elements, and is used to control the element's rendering position, and the
rendering position of its neighbors. Margin values can be specified on layouts and
views.
The Padding property represents the distance between an element and its child
elements, and is used to separate the control from its own content. Padding values
can be specified on pages, layouts, and views.
7 Note
Margin values are additive. Therefore, if two adjacent elements specify a margin of
The Margin and Padding properties are both of type Thickness . There are three
possibilities when creating a Thickness structure:
Create a Thickness structure defined by a single uniform value. The single value is
applied to the left, top, right, and bottom sides of the element.
Create a Thickness structure defined by horizontal and vertical values. The
horizontal value is symmetrically applied to the left and right sides of the element,
with the vertical value being symmetrically applied to the top and bottom sides of
the element.
Create a Thickness structure defined by four distinct values that are applied to the
left, top, right, and bottom sides of the element.
XAML
<StackLayout Padding="0,20,0,0">
</StackLayout>
C#
};
7 Note
Thickness values can be negative, which typically clips or overdraws the content.
Handlers
Article • 01/09/2023 • 5 minutes to read
.NET MAUI handlers are accessed through their control-specific interface, such as IButton
for a Button. This avoids the cross-platform control having to reference its handler, and the
handler having to reference the cross-platform control.
Each handler class exposes the native view for the cross-platform control via its
PlatformView property. This property can be accessed to set native view properties, invoke
native view methods, and subscribe to native view events. In addition, the cross-platform
control implemented by the handler is exposed via its VirtualView property.
Mappers
A key concept of .NET MAUI handlers is mappers. Each handler typically provides a property
mapper, and sometimes a command mapper, that maps the cross-platform control's API to
the native view's API.
A property mapper defines what Actions to take when a property change occurs in the
cross-platform control. It's a Dictionary that maps the cross-platform control's properties
to their associated Actions. Each platform handler then provides implementations of the
Actions, which manipulate the native view API. This ensures that when a property is set on a
cross-platform control, the underlying native view is updated as required.
A command mapper defines what Actions to take when the cross-platform control sends
commands to native views. They're similar to property mappers, but allow for additional
data to be passed. A command in this context doesn't mean an ICommand implementation.
Instead, a command is just an instruction, and optionally its data, that's sent to a native
view. The command mapper is a Dictionary that maps the cross-platform control's
command to their associated Actions. Each handler then provides implementations of the
Actions, which manipulate the native view API. This ensures that when a cross-platform
control sends a command to its native view, the native view is updated as required. For
example, when a ScrollView is scrolled, the ScrollViewHandler uses a command mapper to
invoke an Action that accepts a scroll position argument. The Action then instructs the
underlying native view to scroll to that position.
The advantage of using mappers to update native views is that native views can be
decoupled from cross-platform controls. This removes the need for native views to
subscribe to and unsubscribe from cross-platform control events. It also allows for easy
customization because mappers can be modified without subclassing.
Handler lifecycle
All handler-based .NET MAUI controls support two handler lifecycle events:
created for a cross-platform control. When the OldHandler property isn't null , the
event indicates that the existing native control is about be removed from the cross-
platform control, and therefore any native events should be unwired and other
cleanup performed.
HandlerChanged is raised after the handler for a cross-platform control has been
created. This event indicates that the native control that implements the cross-
platform control is available, and all the property values set on the cross-platform
control have been applied to the native control.
7 Note
Handler-based views
The following table lists the types that implement handler-based views in .NET MAUI:
A standard requirement for apps is the ability to play videos. This article examines how
to create a .NET Multi-platform App UI (.NET MAUI) cross-platform Video control that
uses a handler to map the cross-platform control API to the native views on Android,
iOS, and Mac Catalyst that play videos. This control can play video from three sources:
Video controls require transport controls, which are buttons for playing and pausing the
video, and a positioning bar that shows the progress through the video and allows the
user to move quickly to a different location. The Video control can either use the
transport controls and positioning bar provided by the platform, or you can supply
custom transport controls and a positioning bar. The following screenshots show the
control on iOS, with and without custom transport controls:
A more sophisticated video control would have additional features, such as a volume
control, a mechanism to interrupt video playback when a call is received, and a way of
keeping the screen active during playback.
) Important
.NET MAUI decouples its handlers from its cross-platform controls through
interfaces. This enables experimental frameworks such as Comet and Fabulous to
provide their own cross-platform controls, that implement the interfaces, while still
using .NET MAUI's handlers. Creating an interface for your cross-platform control is
only necessary if you need to decouple your handler from its cross-platform control
for a similar purpose, or for testing purposes.
The process for creating a cross-platform .NET MAUI custom control, whose platform
implementations are provided by handlers, is as follows:
1. Create a class for the cross-platform control, which provides the control's public
API. For more information, see Create the cross-platform control.
2. Create any required additional cross-platform types.
3. Create a partial handler class. For more information, see Create the handler.
4. In the handler class, create a PropertyMapper dictionary, which defines the Actions
to take when cross-platform property changes occur. For more information, see
Create the property mapper.
5. Optionally, in your handler class, create a CommandMapper dictionary, which defines
the Actions to take when the cross-platform control sends instructions to the
native views that implement the cross-platform control. For more information, see
Create the command mapper.
6. Create partial handler classes for each platform that create the native views that
implement the cross-platform control. For more information, see Create the
platform controls.
7. Register the handler using the ConfigureMauiHandlers and AddHandler methods in
your app's MauiProgram class. For more information, see Register the handler.
Then, the cross-platform control can be consumed. For more information, see Consume
the cross-platform control.
Create the cross-platform control
To create a cross-platform control, you should create a class that derives from View:
C#
using System.ComponentModel;
namespace VideoDemos.Controls
BindableProperty.Create(nameof(AreTransportControlsEnabled),
typeof(bool), typeof(Video), true);
BindableProperty.Create(nameof(Source), typeof(VideoSource),
typeof(Video), null);
BindableProperty.Create(nameof(AutoPlay), typeof(bool),
typeof(Video), true);
BindableProperty.Create(nameof(IsLooping), typeof(bool),
typeof(Video), false);
get { return
(bool)GetValue(AreTransportControlsEnabledProperty); }
[TypeConverter(typeof(VideoSourceConverter))]
...
The control should provide a public API that will be accessed by its handler, and control
consumers. Cross-platform controls should derive from View, which represents a visual
element that's used to place layouts and views on the screen.
C#
#elif ANDROID
#elif WINDOWS
#endif
using VideoDemos.Controls;
using Microsoft.Maui.Handlers;
namespace VideoDemos.Handlers
The handler class is a partial class whose implementation will be completed on each
platform with an additional partial class.
The conditional using statements define the PlatformView type on each platform. On
Android, iOS, Mac Catalyst, and Windows, the native views are provided by the custom
MauiVideoPlayer class. The final conditional using statement defines PlatformView to
be equal to System.Object . This is necessary so that the PlatformView type can be used
within the handler for usage across all platforms. The alternative would be to have to
define the PlatformView property once per platform, using conditional compilation.
Create the property mapper
Each handler typically provides a property mapper, which defines what Actions to take
when a property change occurs in the cross-platform control. The PropertyMapper type
is a Dictionary that maps the cross-platform control's properties to their associated
Actions.
PropertyMapper is defined in .NET MAUI's generic ViewHandler class, and requires two
generic arguments to be supplied:
The class for the cross-platform control, which derives from View.
The class for the handler.
The following code example shows the VideoHandler class extended with the
PropertyMapper definition:
C#
[nameof(Video.AreTransportControlsEnabled)] =
MapAreTransportControlsEnabled,
[nameof(Video.Source)] = MapSource,
[nameof(Video.IsLooping)] = MapIsLooping,
[nameof(Video.Position)] = MapPosition
};
The PropertyMapper is a Dictionary whose key is a string and whose value is a generic
Action . The string represents the cross-platform control's property name, and the
Action represents a static method that requires the handler and cross-platform
control as arguments. For example, the signature of the MapSource method is public
static void MapSource(VideoHandler handler, Video video) .
Each platform handler must provide implementations of the Actions, which manipulate
the native view APIs. This ensures that when a property is set on a cross-platform
control, the underlying native view will be updated as required. The advantage of this
approach is that it allows for easy cross-platform control customization, because the
property mapper can be modified by cross-platform control consumers without
subclassing.
CommandMapper is defined in .NET MAUI's generic ViewHandler class, and requires two
The class for the cross-platform control, which derives from View.
The class for the handler.
The following code example shows the VideoHandler class extended with the
CommandMapper definition:
C#
[nameof(Video.AreTransportControlsEnabled)] =
MapAreTransportControlsEnabled,
[nameof(Video.Source)] = MapSource,
[nameof(Video.IsLooping)] = MapIsLooping,
[nameof(Video.Position)] = MapPosition
};
[nameof(Video.UpdateStatus)] = MapUpdateStatus,
[nameof(Video.PlayRequested)] = MapPlayRequested,
[nameof(Video.PauseRequested)] = MapPauseRequested,
[nameof(Video.StopRequested)] = MapStopRequested
};
The CommandMapper is a Dictionary whose key is a string and whose value is a generic
Action . The string represents the cross-platform control's command name, and the
Action represents a static method that requires the handler, cross-platform control,
and optional data as arguments. For example, the signature of the MapPlayRequested
method is public static void MapPlayRequested(VideoHandler handler, Video video,
object? args) .
Each platform handler must provide implementations of the Actions, which manipulate
the native view APIs. This ensures that when a command is sent from the cross-platform
control, the underlying native view will be manipulated as required. The advantage of
this approach is that it removes the need for native views to subscribe to and
unsubscribe from cross-platform control events. In addition, it allows for easy
customization because the command mapper can be modified by cross-platform
control consumers without subclassing.
The VideoHandler class containing the mappers is named VideoHandler.cs. Its platform
implementations are in the VideoHandler.Android.cs, VideoHandler.MaciOS.cs, and
VideoHandler.Windows.cs files. This filename-based multi-targeting is configured by
adding the following XML to the project file, as children of the <Project> node:
XML
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-android')) !=
true">
<None Include="**\**\*.Android.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\**\*.MaciOS.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\*.Windows.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
Each platform handler class should be a partial class and derive from the generic
ViewHandler class, which requires two type arguments:
The class for the cross-platform control, which derives from View.
The type of the native view that implements the cross-platform control on the
platform. This should be identical to the type of the PlatformView property in the
handler.
) Important
The PlatformView property, is used to access the native view on each platform that
implements the cross-platform control.
Each of the platform handler implementations should override the following methods:
CreatePlatformView , which should create and return the native view that
Each platform handler should also implement the Actions that are defined in the
mapper dictionaries.
In addition, each platform handler should also provide code, as required, to implement
the functionality of the cross-platform control on the platform. Alternatively, this can be
provided by an additional type, which is the approach adopted here.
Android
Video is played on Android with a VideoView . However, here, the VideoView has been
encapsulated in a MauiVideoPlayer type to keep the native view separated from its
handler. The following example shows the VideoHandler partial class for Android, with
its three overrides:
C#
#nullable enable
using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.Android;
namespace VideoDemos.Handlers
base.ConnectHandler(platformView);
platformView.Dispose();
base.DisconnectHandler(platformView);
...
VideoHandler derives from the ViewHandler class, with the generic Video argument
DisconnectHandler override is the location to perform any native view cleanup, and so
The platform handler also has to implement the Actions defined in the property mapper
dictionary:
C#
...
handler.PlatformView?.UpdateTransportControlsEnabled();
handler.PlatformView?.UpdateSource();
handler.PlatformView?.UpdateIsLooping();
handler.PlatformView?.UpdatePosition();
...
C#
...
handler.PlatformView?.UpdateStatus();
return;
handler.PlatformView?.PlayRequested(position);
return;
handler.PlatformView?.PauseRequested(position);
return;
handler.PlatformView?.StopRequested(position);
...
Each Action is executed in response to a command being sent from the cross-platform
control, and is a static method that requires handler and cross-platform control
instances, and optional data as arguments. In each case, the Action calls a method
defined in the MauiVideoPlayer class, after extracting the optional data.
On Android, the MauiVideoPlayer class encapsulates the VideoView to keep the native
view separated from its handler:
C#
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.Android
VideoView _videoView;
MediaController _mediaController;
bool _isPrepared;
Context _context;
Video _video;
_context = context;
_video = video;
SetBackgroundColor(Color.Black);
LayoutParameters = new
CoordinatorLayout.LayoutParams(LayoutParams.MatchParent,
LayoutParams.MatchParent)
Gravity = (int)GravityFlags.Center
};
LayoutParameters = new
RelativeLayout.LayoutParams(LayoutParams.MatchParent,
LayoutParams.MatchParent)
};
AddView(relativeLayout);
// Handle events
_videoView.Prepared += OnVideoViewPrepared;
...
MauiVideoPlayer derives from CoordinatorLayout , because the root native view in a .NET
The VideoView could be added directly to the CoordinatorLayout , and positioned in the
layout as required. However, here, an Android RelativeLayout is added to the
CoordinatorLayout , and the VideoView is added to the RelativeLayout . Layout
parameters are set on both the RelativeLayout and VideoView so that the VideoView is
centered in the page, and expands to fill the available space while maintaining its aspect
ratio.
The constructor also subscribes to the VideoView.Prepared event. This event is raised
when the video is ready for playback, and is unsubscribed from in the Dispose override:
C#
VideoView _videoView;
Video _video;
...
if (disposing)
_videoView.Prepared -= OnVideoViewPrepared;
_videoView.Dispose();
_videoView = null;
_video = null;
base.Dispose(disposing);
...
In addition to unsubscribing from the Prepared event, the Dispose override also
performs native view cleanup.
7 Note
The platform transport controls include buttons that play, pause, and stop the video,
and are provided by Android's MediaController type. If the
Video.AreTransportControlsEnabled property is set to true , a MediaController is set as
the media player of the VideoView . This occurs because when the
AreTransportControlsEnabled property is set, the handler's property mapper ensures
that the MapAreTransportControlsEnabled method is invoked, which in turn calls the
UpdateTransportControlsEnabled method in MauiVideoPlayer :
C#
VideoView _videoView;
MediaController _mediaController;
Video _video;
...
if (_video.AreTransportControlsEnabled)
_mediaController.SetMediaPlayer(_videoView);
_videoView.SetMediaController(_mediaController);
else
_videoView.SetMediaController(null);
if (_mediaController != null)
_mediaController.SetMediaPlayer(null);
_mediaController = null;
...
The transport controls fade out if they're not used but can be restored by tapping on
the video.
following example shows the VideoHandler partial class for iOS, with its three overrides:
C#
using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.MaciOS;
namespace VideoDemos.Handlers
base.ConnectHandler(platformView);
platformView.Dispose();
base.DisconnectHandler(platformView);
...
VideoHandler derives from the ViewHandler class, with the generic Video argument
The platform handler also has to implement the Actions defined in the property mapper
dictionary:
C#
...
handler?.PlatformView.UpdateTransportControlsEnabled();
handler?.PlatformView.UpdateSource();
handler.PlatformView?.UpdateIsLooping();
handler?.PlatformView.UpdatePosition();
...
The platform handler also has to implement the Actions defined in the command
mapper dictionary:
C#
...
handler.PlatformView?.UpdateStatus();
return;
handler.PlatformView?.PlayRequested(position);
return;
handler.PlatformView?.PauseRequested(position);
return;
handler.PlatformView?.StopRequested(position);
...
Each Action is executed in response to a command being sent from the cross-platform
control, and is a static method that requires handler and cross-platform control
instances, and optional data as arguments. In each case, the Action calls a method
defined in the MauiVideoPlayer class, after extracting the optional data.
On iOS and Mac Catalyst, the MauiVideoPlayer class encapsulates the AVPlayer and
AVPlayerViewController types to keep the native views separated from their handler:
C#
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
AVPlayer _player;
AVPlayerViewController _playerViewController;
Video _video;
...
_video = video;
_playerViewController.Player = _player;
_playerViewController.View.Frame = this.Bounds;
var viewController =
WindowStateManager.Default.GetCurrentUIViewController();
_playerViewController.AdditionalSafeAreaInsets = new
UIEdgeInsets(insets.Top * -1, insets.Left, insets.Bottom * -1,
insets.Right);
viewController.View.AddSubview(_playerViewController.View);
#endif
AddSubview(_playerViewController.View);
...
MauiVideoPlayer derives from UIView , which is the base class on iOS and Mac Catalyst
for objects that display content and handle user interaction with that content. The
constructor creates an AVPlayer object, which manages the playback and timing of a
media file, and sets it as the Player property value of an AVPlayerViewController . The
AVPlayerViewController displays content from the AVPlayer and presents transport
controls and other features. The size and location of the control is then set, which
ensures that the video is centered in the page and expands to fill the available space
while maintaining its aspect ratio. On iOS 16 and Mac Catalyst 16, the
AVPlayerViewController has to be added to the parent ViewController for Shell-based
apps, otherwise the transport controls aren't displayed. The native view, which is the
view from the AVPlayerViewController , is then added to the page.
C#
AVPlayer _player;
AVPlayerViewController _playerViewController;
Video _video;
...
if (disposing)
if (_player != null)
DestroyPlayedToEndObserver();
_player.ReplaceCurrentItemWithPlayerItem(null);
_player.Dispose();
if (_playerViewController != null)
_playerViewController.Dispose();
_video = null;
base.Dispose(disposing);
...
In some scenarios, videos continue playing after a video playback page has been
navigated away from. To stop the video, the ReplaceCurrentItemWithPlayerItem is set to
null in the Dispose override, and other native view cleanup is performed.
7 Note
The Dispose override is called by the handler's DisconnectHandler override.
The platform transport controls include buttons that play, pause, and stop the video,
and are provided by the AVPlayerViewController type. If the
Video.AreTransportControlsEnabled property is set to true , the AVPlayerViewController
will display its playback controls. This occurs because when the
AreTransportControlsEnabled property is set, the handler's property mapper ensures
C#
AVPlayerViewController _playerViewController;
Video _video;
...
_playerViewController.ShowsPlaybackControls =
_video.AreTransportControlsEnabled;
...
The transport controls fade out if they're not used but can be restored by tapping on
the video.
then control video playback programmatically or supply your own transport controls.
For more information, see Create custom transport controls.
Windows
Video is played on Windows with the MediaPlayerElement . However, here, the
MediaPlayerElement has been encapsulated in a MauiVideoPlayer type to keep the
native view separated from its handler. The following example shows the VideoHandler
partial class fo Windows, with its three overrides:
C#
#nullable enable
using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.Windows;
namespace VideoDemos.Handlers
base.ConnectHandler(platformView);
platformView.Dispose();
base.DisconnectHandler(platformView);
...
VideoHandler derives from the ViewHandler class, with the generic Video argument
specifying the cross-platform control type, and the MauiVideoPlayer argument
specifying the type that encapsulates the MediaPlayerElement native view.
DisconnectHandler override is the location to perform any native view cleanup, and so
calls the Dispose method on the MauiVideoPlayer instance.
The platform handler also has to implement the Actions defined in the property mapper
dictionary:
C#
...
handler.PlatformView?.UpdateTransportControlsEnabled();
handler.PlatformView?.UpdateSource();
handler.PlatformView?.UpdateIsLooping();
handler.PlatformView?.UpdatePosition();
...
The platform handler also has to implement the Actions defined in the command
mapper dictionary:
C#
...
handler.PlatformView?.UpdateStatus();
return;
handler.PlatformView?.PlayRequested(position);
return;
handler.PlatformView?.PauseRequested(position);
return;
handler.PlatformView?.StopRequested(position);
...
Each Action is executed in response to a command being sent from the cross-platform
control, and is a static method that requires handler and cross-platform control
instances, and optional data as arguments. In each case, the Action calls a method
defined in the MauiVideoPlayer class, after extracting the optional data.
C#
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
namespace VideoDemos.Platforms.Windows
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
_video = video;
this.Children.Add(_mediaPlayerElement);
...
the Grid. This enables the MediaPlayerElement to automatically size to fill all available
space.
C#
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.MediaOpened -=
OnMediaPlayerMediaOpened;
_mediaPlayerElement.MediaPlayer.Dispose();
_mediaPlayerElement = null;
...
In addition to unsubscribing from the MediaOpened event, the Dispose override also
performs native view cleanup.
7 Note
The platform transport controls include buttons that play, pause, and stop the video,
and are provided by the MediaPlayerElement type. If the
Video.AreTransportControlsEnabled property is set to true , the MediaPlayerElement will
display its playback controls. This occurs because when the
AreTransportControlsEnabled property is set, the handler's property mapper ensures
that the MapAreTransportControlsEnabled method is invoked, which in turn calls the
UpdateTransportControlsEnabled method in MauiVideoPlayer :
C#
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
_mediaPlayerElement.AreTransportControlsEnabled =
_video.AreTransportControlsEnabled;
...
7 Note
C#
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using VideoDemos.Controls;
using VideoDemos.Platforms.Android;
namespace VideoDemos.Handlers
...
handler.PlatformView?.UpdateSource();
MauiVideoPlayer mvp =
(MauiVideoPlayer)video.ToPlatform(handler.MauiContext);
...
...
In this example, in the VideoHandler partial class for Android, the MapSource method
converts the Video instance to a MauiVideoPlayer object.
The ToPlatform method can also convert a cross-platform control to its underlying
platform control from cross-platform code:
C#
using Microsoft.Maui.Platform;
namespace VideoDemos.Views;
...
#if ANDROID
Android.Views.View nativeView =
video.ToPlatform(video.Handler.MauiContext);
UIKit.UIView nativeView =
video.ToPlatform(video.Handler.MauiContext);
#elif WINDOWS
Microsoft.UI.Xaml.FrameworkElement nativeView =
video.ToPlatform(video.Handler.MauiContext);
#endif
...
...
Play a video
The Video class defines a Source property, which is used to specify the source of the
video file, and an AutoPlay property. AutoPlay defaults to true , which means that the
video should begin playing automatically after Source has been set. For the definition of
these properties, see Create the cross-platform control.
The Source property is of type VideoSource , which is an abstract class that consists of
three static methods that instantiate the three classes that derive from VideoSource :
C#
using System.ComponentModel;
namespace VideoDemos.Controls
[TypeConverter(typeof(VideoSourceConverter))]
C#
using System.ComponentModel;
namespace VideoDemos.Controls
object IExtendedTypeConverter.ConvertFromInvariantString(string
value, IServiceProvider serviceProvider)
if (!string.IsNullOrWhiteSpace(value))
Uri uri;
VideoSource.FromUri(value) :
VideoSource.FromResource(value);
The type converter is invoked when the Source property is set to a string in XAML. The
ConvertFromInvariantString method attempts to convert the string to a Uri object. If it
succeeds, and the scheme isn't file , then the method returns a UriVideoSource .
Otherwise it returns a ResourceVideoSource .
C#
namespace VideoDemos.Controls
BindableProperty.Create(nameof(Uri), typeof(string),
typeof(UriVideoSource));
When the Source property is set to a UriVideoSource , the handler's property mapper
ensures that the MapSource method is invoked:
C#
handler?.PlatformView.UpdateSource();
The MapSource method in turns calls the UpdateSource method on the handler's
PlatformView property. The PlatformView property, which is of type MauiVideoPlayer ,
represents the native view that provides the video player implementation on each
platform.
Android
Video is played on Android with a VideoView . The following code example shows how
the UpdateSource method processes the Source property when it's of type
UriVideoSource :
C#
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.Android
VideoView _videoView;
bool _isPrepared;
Video _video;
...
_isPrepared = false;
if (_video.Source is UriVideoSource)
if (!string.IsNullOrWhiteSpace(uri))
_videoView.SetVideoURI(Uri.Parse(uri));
hasSetSource = true;
...
_videoView.Start();
...
The AutoPlay property has no equivalent on VideoView , so the Start method is called if
a new video has been set.
To play a video on iOS and Mac Catalyst, an object of type AVAsset is created to
encapsulate the video, and that is used to create an AVPlayerItem , which is then handed
off to the AVPlayer object. The following code example shows how the UpdateSource
method processes the Source property when it's of type UriVideoSource :
C#
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
AVPlayer _player;
AVPlayerItem _playerItem;
Video _video;
...
if (_video.Source is UriVideoSource)
if (!string.IsNullOrWhiteSpace(uri))
...
if (asset != null)
else
_playerItem = null;
_player.ReplaceCurrentItemWithPlayerItem(_playerItem);
_player.Play();
...
The AutoPlay property has no equivalent in the iOS video classes, so the property is
examined at the end of the UpdateSource method to call the Play method on the
AVPlayer object.
In some cases on iOS, videos continue playing after the video playback page has been
navigated away from. To stop the video, the ReplaceCurrentItemWithPlayerItem is set to
null in the Dispose override:
C#
if (disposing)
if (_player != null)
_player.ReplaceCurrentItemWithPlayerItem(null);
...
...
base.Dispose(disposing);
Windows
Video is played on Windows with a MediaPlayerElement . The following code example
shows how the UpdateSource method processes the Source property when it's of type
UriVideoSource :
C#
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
namespace VideoDemos.Platforms.Windows
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
if (_video.Source is UriVideoSource)
if (!string.IsNullOrWhiteSpace(uri))
_mediaPlayerElement.Source =
MediaSource.CreateFromUri(new Uri(uri));
hasSetSource = true;
...
_isMediaPlayerAttached = true;
_mediaPlayerElement.MediaPlayer.MediaOpened +=
OnMediaPlayerMediaOpened;
_mediaPlayerElement.AutoPlay = true;
...
At the end of the UpdateSource method, the Video.AutoPlay property is examined and if
it's true the MediaPlayerElement.AutoPlay property is set to true to start video playback.
C#
namespace VideoDemos.Controls
BindableProperty.Create(nameof(Path), typeof(string),
typeof(ResourceVideoSource));
C#
handler?.PlatformView.UpdateSource();
The MapSource method in turns calls the UpdateSource method on the handler's
PlatformView property. The PlatformView property, which is of type MauiVideoPlayer ,
represents the native view that provides the video player implementation on each
platform.
Android
The following code example shows how the UpdateSource method processes the Source
property when it's of type ResourceVideoSource :
C#
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.Android
VideoView _videoView;
bool _isPrepared;
Context _context;
Video _video;
...
_isPrepared = false;
...
if (!string.IsNullOrWhiteSpace(path))
_videoView.SetVideoPath(assetFilePath);
hasSetSource = true;
...
...
A resource video file is stored in the package's assets folder, and requires a content
provider to access it. The content provider is provided by the VideoProvider class, which
creates an AssetFileDescriptor object that provides access to the video file:
C#
using Android.Content;
using Android.Content.Res;
using Android.Database;
namespace VideoDemos.Platforms.Android
if (fileName == null)
try
afd = assets.OpenFd(fileName);
Debug.WriteLine(ex);
return afd;
return false;
...
C#
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
Video _video;
...
...
if (!string.IsNullOrWhiteSpace(path))
string filename =
Path.GetFileNameWithoutExtension(path);
NSUrl url =
NSBundle.MainBundle.GetUrlForResource(filename, extension, directory);
asset = AVAsset.FromUrl(url);
...
...
In some cases on iOS, videos continue playing after the video playback page has been
navigated away from. To stop the video, the ReplaceCurrentItemWithPlayerItem is set to
null in the Dispose override:
C#
if (disposing)
if (_player != null)
_player.ReplaceCurrentItemWithPlayerItem(null);
...
...
base.Dispose(disposing);
Windows
The following code example shows how the UpdateSource method processes the Source
property when it's of type ResourceVideoSource :
C#
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
namespace VideoDemos.Platforms.Windows
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
...
if (!string.IsNullOrWhiteSpace(path))
_mediaPlayerElement.Source =
MediaSource.CreateFromUri(new Uri(path));
hasSetSource = true;
...
...
C#
namespace VideoDemos.Controls
BindableProperty.Create(nameof(File), typeof(string),
typeof(FileVideoSource));
When the Source property is set to a FileVideoSource , the handler's property mapper
ensures that the MapSource method is invoked:
C#
handler?.PlatformView.UpdateSource();
The MapSource method in turns calls the UpdateSource method on the handler's
PlatformView property. The PlatformView property, which is of type MauiVideoPlayer ,
represents the native view that provides the video player implementation on each
platform.
Android
The following code example shows how the UpdateSource method processes the Source
property when it's of type FileVideoSource :
C#
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.Android
VideoView _videoView;
bool _isPrepared;
Video _video;
...
_isPrepared = false;
...
if (!string.IsNullOrWhiteSpace(filename))
_videoView.SetVideoPath(filename);
hasSetSource = true;
...
...
C#
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
Video _video;
...
...
if (!string.IsNullOrWhiteSpace(uri))
...
...
Windows
The following code example shows how the UpdateSource method processes the Source
property when it's of type FileVideoSource :
C#
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
namespace VideoDemos.Platforms.Windows
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
...
if (!string.IsNullOrWhiteSpace(filename))
_mediaPlayerElement.Source =
MediaSource.CreateFromStorageFile(storageFile);
hasSetSource = true;
...
...
Loop a video
The Video class defines an IsLooping property, which enables the control to
automatically set the video position to the start after reaching its end. It defaults to
false , which indicates that videos don't automatically loop.
When the IsLooping property is set, the handler's property mapper ensures that the
MapIsLooping method is invoked:
C#
handler.PlatformView?.UpdateIsLooping();
The MapIsLooping method in turn calls the UpdateIsLooping method on the handler's
PlatformView property. The PlatformView property, which is of type MauiVideoPlayer ,
represents the native view that provides the video player implementation on each
platform.
Android
The following code example shows how the UpdateIsLooping method on Android
enables video looping:
C#
using Android.Content;
using Android.Media;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.Android
VideoView _videoView;
Video _video;
...
if (_video.IsLooping)
_videoView.SetOnPreparedListener(this);
else
_videoView.SetOnPreparedListener(null);
mp.Looping = _video.IsLooping;
...
C#
using System.Diagnostics;
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
AVPlayer _player;
AVPlayerViewController _playerViewController;
Video _video;
NSObject? _playedToEndObserver;
...
DestroyPlayedToEndObserver();
if (_video.IsLooping)
_player.ActionAtItemEnd = AVPlayerActionAtItemEnd.None;
_playedToEndObserver =
NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.DidPlayToEndTime
Notification, PlayedToEnd);
else
_player.ActionAtItemEnd = AVPlayerActionAtItemEnd.Pause;
return;
_playerViewController.Player?.Seek(CMTime.Zero);
...
On iOS and Mac Catalyst, a notification is used to execute a callback when the video has
been played to the end. When the Video.IsLooping property is true , the
UpdateIsLooping method adds an observer for the
Because MauiVideoPlayer adds an observer for a notification it must also remove the
observer when performing native view cleanup. This is accomplished in the Dispose
override:
C#
AVPlayer _player;
AVPlayerViewController _playerViewController;
Video _video;
NSObject? _playedToEndObserver;
...
if (disposing)
if (_player != null)
DestroyPlayedToEndObserver();
...
...
base.Dispose(disposing);
void DestroyPlayedToEndObserver()
if (_playedToEndObserver != null)
NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
DisposeObserver(ref _playedToEndObserver);
disposable?.Dispose();
disposable = null;
...
The Dispose override calls the DestroyPlayedToEndObserver method that removes the
observer for the AVPlayerItem.DidPlayToEndTimeNotification notification, and which
also invokes the Dispose method on the NSObject .
Windows
The following code example shows how the UpdateIsLooping method on Windows
enables video looping:
C#
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
Video.IsLooping property.
By default, the Video control displays transport controls supported by each platform.
However, when you set the AreTransportControlsEnabled property to false , these
controls are suppressed. You can then control video playback programmatically or
supply your own transport controls.
Implementing your own transport controls requires the Video class to be able to notify
its native views to play, pause, or stop the video, and know the current status of video
playback. The Video class defines methods named Play , Pause , and Stop that raise a
corresponding event, and send a command to the VideoHandler :
C#
namespace VideoDemos.Controls
...
Handler?.Invoke(nameof(Video.PlayRequested), args);
Handler?.Invoke(nameof(Video.PauseRequested), args);
Handler?.Invoke(nameof(Video.StopRequested), args);
The VideoPositionEventArgs class defines a Position property that can be set through
its constructor. This property represents the position at which video playback was
started, paused, or stopped.
The final line in the Play , Pause , and Stop methods sends a command and associated
data to VideoHandler . The CommandMapper for VideoHandler maps command names to
Actions that are executed when a command is received. For example, when
VideoHandler receives the PlayRequested command, it executes its MapPlayRequested
method. The advantage of this approach is that it removes the need for native views to
subscribe to and unsubscribe from cross-platform control events. In addition, it allows
for easy customization because the command mapper can be modified by cross-
platform control consumers without subclassing. For more information about
CommandMapper , see Create the command mapper.
pause, or stop the video. For example, the following code shows the PlayRequested ,
PauseRequested , and StopRequested methods on iOS and Mac Catalyst:
C#
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
AVPlayer _player;
...
_player.Play();
_player.Pause();
_player.Pause();
Each of the three methods logs the position at which the video was played, paused, or
stopped, using the data that's sent with the command.
This mechanism ensures that when the Play , Pause , or Stop method is invoked on the
Video control, its native view is instructed to play, pause, or stop the video and log the
position at which the video was played, paused, or stopped. This all happens using a
decoupled approach, without native views having to subscribe to cross-platform events.
Video status
Implementing play, pause, and stop functionality isn't sufficient for supporting custom
transport controls. Often the play and pause functionality should be implemented with
the same button, which changes its appearance to indicate whether the video is
currently playing or paused. In addition, the button shouldn't even be enabled if the
video hasn't yet loaded.
These requirements imply that the video player needs to make available a current status
indicating if it's playing or paused, or if it's not yet ready to play a video. This status can
be represented by an enumeration:
C#
NotReady,
Playing,
Paused
The Video class defines a read-only bindable property named Status of type
VideoStatus . This property is defined as read-only because it should only be set from
the control's handler:
C#
namespace VideoDemos.Controls
...
BindableProperty.CreateReadOnly(nameof(Status),
typeof(VideoStatus), typeof(Video), VideoStatus.NotReady);
VideoStatus IVideoController.Status
...
Usually, a read-only bindable property would have a private set accessor on the Status
property to allow it to be set from within the class. However, for a View derivative
supported by handlers, the property must be set from outside the class but only by the
control's handler.
For this reason, another property is defined with the name IVideoController.Status .
This is an explicit interface implementation, and is made possible by the
IVideoController interface that the Video class implements:
C#
This interface makes it possible for a class external to Video to set the Status property
by referencing the IVideoController interface. The property can be set from other
classes and the handler, but it's unlikely to be set inadvertently. Most importantly, the
Status property can't be set through a data binding.
To assist the handler implementations in keeping the Status property updated, the
Video class defines an UpdateStatus event and command:
C#
using System.ComponentModel;
namespace VideoDemos.Controls
...
IDispatcherTimer _timer;
public Video()
_timer = Dispatcher.CreateTimer();
_timer.Interval = TimeSpan.FromMilliseconds(100);
_timer.Tick += OnTimerTick;
_timer.Start();
UpdateStatus?.Invoke(this, EventArgs.Empty);
Handler?.Invoke(nameof(Video.UpdateStatus));
...
The OnTimerTick event handler is executed every tenth of a second, which raises the
UpdateStatus event and invokes the UpdateStatus command.
When the UpdateStatus command is sent from the Video control to its handler, the
handler's command mapper ensures that the MapUpdateStatus method is invoked:
C#
handler.PlatformView?.UpdateStatus();
The MapUpdateStatus method in turns calls the UpdateStatus method on the handler's
PlatformView property. The PlatformView property, which is of type MauiVideoPlayer ,
encapsulates the native views that provide the video player implementation on each
platform.
Android
The following code example shows the UpdateStatus method on Android sets the
Status property:
C#
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.Android
VideoView _videoView;
bool _isPrepared;
Video _video;
...
_video = video;
...
_videoView.Prepared += OnVideoViewPrepared;
if (disposing)
_videoView.Prepared -= OnVideoViewPrepared;
...
base.Dispose(disposing);
_isPrepared = true;
((IVideoController)_video).Duration =
TimeSpan.FromMilliseconds(_videoView.Duration);
if (_isPrepared)
((IVideoController)_video).Status = status;
...
...
C#
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
AVPlayer _player;
Video _video;
...
switch (_player.Status)
case AVPlayerStatus.ReadyToPlay:
switch (_player.TimeControlStatus)
case AVPlayerTimeControlStatus.Playing:
videoStatus = VideoStatus.Playing;
break;
case AVPlayerTimeControlStatus.Paused:
videoStatus = VideoStatus.Paused;
break;
break;
((IVideoController)_video).Status = videoStatus;
...
...
Two properties of AVPlayer must be accessed to set the Status property - the Status
property of type AVPlayerStatus and the TimeControlStatus property of type
AVPlayerTimeControlStatus . The Status property can then be set on the Video object
by casting it to IVideoController .
Windows
The following code example shows the UpdateStatus method on Windows sets the
Status property:
C#
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
namespace VideoDemos.Platforms.Windows
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
if (_isMediaPlayerAttached)
switch (_mediaPlayerElement.MediaPlayer.CurrentState)
case MediaPlayerState.Playing:
status = VideoStatus.Playing;
break;
case MediaPlayerState.Paused:
case MediaPlayerState.Stopped:
status = VideoStatus.Paused;
break;
((IVideoController)_video).Status = status;
_video.Position = _mediaPlayerElement.MediaPlayer.Position;
...
Status property. The Status property can then be set on the Video object by casting it
to IVideoController .
Positioning bar
The transport controls implemented by each platform include a positioning bar. This bar
resembles a slider or scroll bar, and shows the current location of the video within its
total duration. Users can manipulate the positioning bar to move forwards or backwards
to a new position in the video.
Implementing your own positioning bar requires the Video class to know the duration
of the video, and its current position within that duration.
Duration
One item of information that the Video control needs to support a custom positioning
bar is the duration of the video. The Video class defines a read-only bindable property
named Duration , of type TimeSpan . This property is defined as read-only because it
should only be set from the control's handler:
C#
namespace VideoDemos.Controls
...
BindableProperty.CreateReadOnly(nameof(Duration),
typeof(TimeSpan), typeof(Video), new TimeSpan(),
TimeSpan IVideoController.Duration
...
Usually, a read-only bindable property would have a private set accessor on the
Duration property to allow it to be set from within the class. However, for a View
derivative supported by handlers, the property must be set from outside the class but
only by the control's handler.
7 Note
The property-changed event handler for the Duration bindable property calls a
method named SetTimeToEnd , which is described in Calculating time to end.
For this reason, another property is defined with the name IVideoController.Duration .
This is an explicit interface implementation, and is made possible by the
IVideoController interface that the Video class implements:
C#
This interface makes it possible for a class external to Video to set the Duration
property by referencing the IVideoController interface. The property can be set from
other classes and the handler, but it's unlikely to be set inadvertently. Most importantly,
the Duration property can't be set through a data binding.
The duration of a video isn't available immediately after the Source property of the
Video control is set. The video must be partially downloaded before the native view can
Android
C#
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.Android
VideoView _videoView;
Video _video;
...
...
((IVideoController)_video).Duration =
TimeSpan.FromMilliseconds(_videoView.Duration);
...
On iOS and Mac Catalyst, the duration of a video is obtained from the
AVPlayerItem.Duration property, but not immediately after the AVPlayerItem is created.
It's possible to set an iOS observer for the Duration property, but the MauiVideoPlayer
class obtains the duration in the UpdateStatus method that's called 10 times a second:
C#
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
AVPlayerItem _playerItem;
...
return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 :
cmTime.Seconds);
...
if (_playerItem != null)
((IVideoController)_video).Duration =
ConvertTime(_playerItem.Duration);
...
...
C#
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
namespace VideoDemos.Platforms.Windows
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
MainThread.BeginInvokeOnMainThread(() =>
((IVideoController)_video).Duration =
_mediaPlayerElement.MediaPlayer.NaturalDuration;
});
...
For more information about running code on the main thread, see Create a thread on
the .NET MAUI UI thread.
Position
The Video control also needs a Position property that increases from zero to Duration
as the video plays. The Video class implements this property as a bindable property with
public get and set accessors:
C#
namespace VideoDemos.Controls
...
BindableProperty.Create(nameof(Position), typeof(TimeSpan),
typeof(Video), new TimeSpan(),
...
The get accessor returns the current position of the video as its playing. The set
accessor responds to user manipulation of the positioning bar by moving the video
position forwards or backwards.
7 Note
The property-changed event handler for the Position bindable property calls a
method named SetTimeToEnd , which is described in Calculating time to end.
On Android, iOS and Mac Catalyst, the property that obtains the current position only
has a get accessor. Instead, a Seek method is available to set the position. This seems
to be a more sensible approach than using a single Position property, which has an
inherent problem. As a video plays, a Position property must be continually updated to
reflect the new position. But you don't want most changes of the Position property to
cause the video player to move to a new position in the video. If that happens, the video
player would respond by seeking to the last value of the Position property, and the
video wouldn't advance.
Despite the difficulties of implementing a Position property with get and set
accessors, this approach is used because it can utilize data binding. The Position
property of the Video control can be bound to a Slider that's used both to display the
position and to seek a new position. However, several precautions are necessary when
implementing the Position property, to avoid feedback loops.
Android
C#
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.Android
VideoView _videoView;
Video _video;
...
...
TimeSpan timeSpan =
TimeSpan.FromMilliseconds(_videoView.CurrentPosition);
_video.Position = timeSpan;
if (Math.Abs(_videoView.CurrentPosition -
_video.Position.TotalMilliseconds) > 1000)
_videoView.SeekTo((int)_video.Position.TotalMilliseconds);
...
Every time the Position property is set by the UpdateStatus method, the Position
property fires a PropertyChanged event, which causes the property mapper for the
handler to call the UpdatePosition method. The UpdatePosition method should do
nothing for most of the property changes. Otherwise, with every change in the video's
position it would be moved to same position it just reached. To avoid this feedback
loop, the UpdatePosition only calls the Seek method on the VideoView object when the
difference between the Position property and the current position of the VideoView is
greater than one second.
On iOS and Mac Catalyst, the AVPlayerItem.CurrentTime property indicates the current
position of the video. The MauiVideoPlayer class sets the Position property in the
UpdateStatus method at the same time as it sets the Duration property:
C#
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
AVPlayer _player;
AVPlayerItem _playerItem;
Video _video;
...
return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 :
cmTime.Seconds);
...
if (_playerItem != null)
...
_video.Position = ConvertTime(_playerItem.CurrentTime);
_player.Seek(CMTime.FromSeconds(_video.Position.TotalSeconds, 1));
...
Every time the Position property is set by the UpdateStatus method, the Position
property fires a PropertyChanged event, which causes the property mapper for the
handler to call the UpdatePosition method. The UpdatePosition method should do
nothing for most of the property changes. Otherwise, with every change in the video's
position it would be moved to same position it just reached. To avoid this feedback
loop, the UpdatePosition only calls the Seek method on the AVPlayer object when the
difference between the Position property and the current position of the AVPlayer is
greater than one second.
Windows
C#
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
namespace VideoDemos.Platforms.Windows
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
if (_isMediaPlayerAttached)
...
_video.Position = _mediaPlayerElement.MediaPlayer.Position;
if (_isMediaPlayerAttached)
if (Math.Abs((_mediaPlayerElement.MediaPlayer.Position -
_video.Position).TotalSeconds) > 1)
_mediaPlayerElement.MediaPlayer.Position =
_video.Position;
...
Every time the Position property is set by the UpdateStatus method, the Position
property fires a PropertyChanged event, which causes the property mapper for the
handler to call the UpdatePosition method. The UpdatePosition method should do
nothing for most of the property changes. Otherwise, with every change in the video's
position it would be moved to same position it just reached. To avoid this feedback
loop, the UpdatePosition only sets the MediaPlayerElement.MediaPlayer.Position
property when the difference between the Position property and the current position
of the MediaPlayerElement is greater than one second.
Sometimes video players show the time remaining in the video. This value begins at the
video's duration when the video begins, and decreases down to zero when the video
ends.
The Video class includes a read-only TimeToEnd property that's calculated based on
changes to the Duration and Position properties:
C#
namespace VideoDemos.Controls
...
BindableProperty.CreateReadOnly(nameof(TimeToEnd),
typeof(TimeSpan), typeof(Video), new TimeSpan());
void SetTimeToEnd()
...
The SetTimeToEnd method is called from the property-changed event handlers of the
Duration and Position properties.
C#
namespace VideoDemos.Controls
BindableProperty.Create(nameof(Duration), typeof(TimeSpan),
typeof(PositionSlider), new TimeSpan(1),
});
BindableProperty.Create(nameof(Position), typeof(TimeSpan),
typeof(PositionSlider), new TimeSpan(0),
defaultBindingMode: BindingMode.TwoWay,
((Slider)bindable).Value = seconds;
});
public PositionSlider()
if (args.PropertyName == "Value")
if (Math.Abs(newPosition.TotalSeconds -
Position.TotalSeconds) / Duration.TotalSeconds > 0.01)
Position = newPosition;
};
The property-changed event handler for the Duration property sets the Maximum
property of the Slider to the TotalSeconds property of the TimeSpan value. Similarly, the
property-changed event handler for the Position property sets the Value property of
the Slider. This is the mechanism by which the Slider tracks the position of
PositionSlider .
The PositionSlider is updated from the underlying Slider in only one scenario, which is
when the user manipulates the Slider to indicate that the video should be advanced or
reversed to a new position. This is detected in the PropertyChanged handler in the
PositionSlider constructor. This event handler checks for a change in the Value
property, and if it's different from the Position property, then the Position property is
set from the Value property.
C#
using VideoDemos.Controls;
using VideoDemos.Handlers;
namespace VideoDemos;
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureMauiHandlers(handlers =>
handlers.AddHandler(typeof(Video), typeof(VideoHandler));
});
return builder.Build();
The handler is registered with the ConfigureMauiHandlers and AddHandler method. The
first argument to the AddHandler method is the cross-platform control type, with the
second argument being its handler type.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.PlayWebVideoPage"
Unloaded="OnContentPageUnloaded"
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4
" />
</ContentPage>
In this example, the VideoSourceConverter class converts the string that represents the
URI to a UriVideoSource . The video then begins loading and starts playing once a
sufficient quantity of data has been downloaded and buffered. On each platform, the
transport controls fade out if they're not used but can be restored by tapping on the
video.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.PlayVideoResourcePage"
Unloaded="OnContentPageUnloaded"
<controls:Video x:Name="video"
Source="video.mp4" />
</ContentPage>
In this example, the VideoSourceConverter class converts the string that represents the
filename of the video to a ResourceVideoSource . For each platform, the video begins
playing almost immediately after the video source is set because the file is in the app
package and doesn't need to be downloaded. On each platform, the transport controls
fade out if they're not used but can be restored by tapping on the video.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.PlayLibraryVideoPage"
Unloaded="OnContentPageUnloaded"
<Grid RowDefinitions="*,Auto">
<Button Grid.Row="1"
Margin="10"
HorizontalOptions="Center"
Clicked="OnShowVideoLibraryClicked" />
</Grid>
</ContentPage>
When the Button is tapped its Clicked event handler is executed, which is shown in the
following code example:
C#
button.IsEnabled = false;
if (!string.IsNullOrWhiteSpace(pickedVideo?.FileName))
File = pickedVideo.FullPath
};
button.IsEnabled = true;
The Clicked event handler uses .NET MAUI's MediaPicker class to let the user pick a
video file from the device. The picked video file is then encapsulated as a
FileVideoSource object and set as the Source property of the Video control. For more
information about the MediaPicker class, see Media picker. For each platform, the video
begins playing almost immediately after the video source is set because the file is on the
device and doesn't need to be downloaded. On each platform, the transport controls
fade out if they're not used but can be restored by tapping on the video.
XAML
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4
"
AutoPlay="False" />
XAML
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4
"
AreTransportControlsEnabled="False" />
If you set AutoPlay and AreTransportControlsEnabled to false , the video won't begin
playing and there will be no way to start it playing. In this scenario you'd need to call the
Play method from the code-behind file, or create your own transport controls.
In addition, you can set a video to loop by setting the IsLooping property to true:
XAML
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4
"
IsLooping="true" />
If you set the IsLooping property to true this ensures that the Video control
automatically sets the video position to the start after reaching its end.
Use custom transport controls
The following XAML example shows custom transport controls that play, pause, and
stop the video:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.CustomTransportPage"
Unloaded="OnContentPageUnloaded"
<Grid RowDefinitions="*,Auto">
<controls:Video x:Name="video"
AutoPlay="False"
AreTransportControlsEnabled="False"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4
" />
<ActivityIndicator Color="Gray"
IsVisible="False">
<ActivityIndicator.Triggers>
<DataTrigger TargetType="ActivityIndicator"
Path=Status}"
Value="{x:Static
controls:VideoStatus.NotReady}">
<Setter Property="IsVisible"
Value="True" />
<Setter Property="IsRunning"
Value="True" />
</DataTrigger>
</ActivityIndicator.Triggers>
</ActivityIndicator>
<Grid Grid.Row="1"
Margin="0,10"
ColumnDefinitions="0.5*,0.5*"
BindingContext="{x:Reference video}">
HorizontalOptions="Center"
Clicked="OnPlayPauseButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static
controls:VideoStatus.Playing}">
<Setter Property="Text"
</DataTrigger>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static
controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
<Button Grid.Column="1"
Text="⏹ Stop"
HorizontalOptions="Center"
Clicked="OnStopButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static
controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
</Grid>
</Grid>
</ContentPage>
video playback. Button appearance is defined using unicode characters and their text
equivalents, to create buttons that consist of an icon and text:
When the video is playing, the play button is updated to a pause button:
The UI also includes an ActivityIndicator that's displayed while the video is loading. Data
triggers are used to enable and disable the ActivityIndicator and the buttons, and to
switch the first button between play and pause. For more information about data
triggers, see Data triggers.
The code-behind file defines the event handlers for the button Clicked events:
C#
public partial class CustomTransportPage : ContentPage
...
if (video.Status == VideoStatus.Playing)
video.Pause();
video.Play();
video.Stop();
...
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.CustomPositionBarPage"
Unloaded="OnContentPageUnloaded"
<Grid RowDefinitions="*,Auto,Auto">
<controls:Video x:Name="video"
AreTransportControlsEnabled="False"
...
<Grid Grid.Row="1"
Margin="10,0"
ColumnDefinitions="0.25*,0.25*,0.25*,0.25*"
BindingContext="{x:Reference video}">
StringFormat='{0:hh\\:mm\\:ss}'}"
HorizontalOptions="Center"
VerticalOptions="Center" />
...
<Label Grid.Column="3"
Text="{Binding Path=TimeToEnd,
StringFormat='{0:hh\\:mm\\:ss}'}"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
<controls:PositionSlider Grid.Row="2"
Margin="10,0,10,10"
BindingContext="{x:Reference video}"
Duration="{Binding Duration}"
Position="{Binding Position}">
<controls:PositionSlider.Triggers>
<DataTrigger TargetType="controls:PositionSlider"
Binding="{Binding Status}"
Value="{x:Static
controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</controls:PositionSlider.Triggers>
</controls:PositionSlider>
</Grid>
</ContentPage>
The Position property of the Video object is bound to the Position property of the
PositionSlider , without performance issues, because the Video.Position property is
An event handler for the page's Unloaded event can be registered in XAML:
XAML
<ContentPage ...
xmlns:controls="clr-namespace:VideoDemos.Controls"
Unloaded="OnContentPageUnloaded">
<controls:Video x:Name="video"
... />
</ContentPage>
The event handler for the Unloaded event can then invoke the DisconnectHandler
method on its Handler instance:
C#
video.Handler?.DisconnectHandler();
PrependToMapping , which modifies the mapper for a handler before the .NET MAUI
control mappings have been applied.
ModifyMapping , which modifies an existing mapping.
AppendToMapping , which modifies the mapper for a handler after the .NET MAUI
Each of these methods has an identical signature that requires two arguments:
A string -based key. When modifying one of the mappings provided by .NET
MAUI, the key used by .NET MAUI must be specified. The key values used by .NET
MAUI control mappings are based on interface and property names, for example
nameof(IEntry.IsPassword) . The interfaces, and their properties, that abstract each
cross-platform control can be found here . Otherwise, this key can be an arbitrary
value that doesn't have to correspond to the name of a property exposed by a
type. For example, MyCustomization can be specified as a key, with any native view
modification being performed as the customization.
An Action that represents the method that performs the handler customization.
The Action specifies two arguments:
A handler argument that provides an instance of the handler being customized.
A view argument that provides an instance of the cross-platform control that
the handler implements.
) Important
Handler customizations are global and aren't scoped to a specific control instance.
Handler customization is allowed to happen anywhere in your app. Once a handler
is customized, it affects all controls of that type, everywhere in your app.
Each handler class exposes the native view for the cross-platform control via its
PlatformView property. This property can be accessed to set native view properties,
invoke native view methods, and subscribe to native view events. In addition, the cross-
platform control implemented by the handler is exposed via its VirtualView property.
Customize a control
The .NET MAUI Entry view is a single-line text input control, that implements the IEntry
interface. The EntryHandler maps the Entry view to the following native views for each
platform:
The following diagrams shows how the Entry view is mapped to its native views via the
EntryHandler :
The Entry property mapper, in the EntryHandler class, maps the cross-platform control
properties to the native view API. This ensures that when a property is set on an Entry,
the underlying native view is updated as required.
C#
namespace CustomizeHandlersDemo.Views;
public CustomizeEntryPage()
InitializeComponent();
ModifyEntry();
void ModifyEntry()
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization
", (handler, view) =>
#if ANDROID
handler.PlatformView.SetSelectAllOnFocus(true);
handler.PlatformView.PerformSelector(new
ObjCRuntime.Selector("selectAll"), null, 0.0f);
};
#elif WINDOWS
handler.PlatformView.SelectAll();
};
#endif
});
In this example, the Entry customization occurs in a page class. Therefore, all Entry
controls on Android, iOS, and Windows will be customized once an instance of the
CustomizeEntryPage is created. Customization is performed by accessing the handlers
PlatformView property, which provides access to the native view that maps to the cross-
platform control on each platform. Native code then customizes the handler by
selecting all of the text in the Entry when it gains focus.
C#
namespace CustomizeHandlersDemo.Controls
You can then customize the EntryHandler , via its property mapper, to perform the
desired modification only to MyEntry instances:
C#
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization
", (handler, view) =>
if (view is MyEntry)
#if ANDROID
handler.PlatformView.SetSelectAllOnFocus(true);
handler.PlatformView.PerformSelector(new
ObjCRuntime.Selector("selectAll"), null, 0.0f);
};
#elif WINDOWS
handler.PlatformView.SelectAll();
};
#endif
});
If the handler customization is performed in your App class, any MyEntry instances in the
app will be customized as per the handler modification.
The handler lifecycle can be used to perform handler customization. For example, to
subscribe to, and unsubscribe from, native view events you must register event handlers
for the HandlerChanged and HandlerChanging events on the cross-platform control being
customized:
XAML
<Entry HandlerChanged="OnEntryHandlerChanged"
HandlerChanging="OnEntryHandlerChanging" />
Conditional compilation
The code-behind file containing the event handlers for the HandlerChanged and
HandlerChanging events is shown in the following example, which uses conditional
compilation:
C#
#if ANDROID
using AndroidX.AppCompat.Widget;
using UIKit;
#elif WINDOWS
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
#endif
namespace CustomizeHandlersDemo.Views;
public CustomizeEntryHandlerLifecyclePage()
InitializeComponent();
#if ANDROID
(entry.Handler.PlatformView as
AppCompatEditText).SetSelectAllOnFocus(true);
(entry.Handler.PlatformView as UITextField).EditingDidBegin +=
OnEditingDidBegin;
#elif WINDOWS
#endif
if (e.OldHandler != null)
(e.OldHandler.PlatformView as UITextField).EditingDidBegin -=
OnEditingDidBegin;
#elif WINDOWS
#endif
nativeView.PerformSelector(new ObjCRuntime.Selector("selectAll"),
null, 0.0f);
#elif WINDOWS
nativeView.SelectAll();
#endif
The HandlerChanged event is raised after the native view that implements the cross-
platform control has been created and initialized. Therefore, its event handler is where
native event subscriptions should be performed. This requires casting the PlatformView
property of the handler to the type, or base type, of the native view so that native events
can be accessed. In this example, on iOS, Mac Catalyst, and Windows, the
OnHandlerChanged event subscribes to native view events that are raised when the native
The OnEditingDidBegin and OnGotFocus event handlers access the native view for the
Entry on their respective platforms, and select all text that's in the Entry.
The HandlerChanging event is raised before the existing handler is removed from the
cross-platform control, and before the new handler for the cross-platform control is
created. Therefore, its event handler is where native event subscriptions should be
removed, and other cleanup should be performed. The HandlerChangingEventArgs object
that accompanies this event has OldHandler and NewHandler properties, which will be
set to the old and new handlers respectively. In this example, the OnHandlerChanging
event removes the subscription to the native view events on iOS, Mac Catalyst, and
Windows.
Partial classes
Rather than using conditional compilation, it's also possible to use partial classes to
organize your control customization code into platform-specific folders and files. With
this approach, your customization code is separated into a cross-platform partial class
and a platform-specific partial class. The following example shows the cross-platform
partial class:
C#
namespace CustomizeHandlersDemo.Views;
public CustomizeEntryPartialMethodsPage()
InitializeComponent();
) Important
The cross-platform partial class shouldn't be placed in any of the Platforms child
folders of your project.
In this example, the two event handlers call partial methods named ChangedHandler and
ChangingHandler , whose signatures are defined in the cross-platform partial class. The
partial method implementations are then defined in the platform-specific partial classes,
which should be placed in the correct Platforms child folders to ensure that the build
system only attempts to build native code when building for the specific platform. For
example, the following code shows the CustomizeEntryPartialMethodsPage class in the
Platforms > Windows folder of the project:
C#
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace CustomizeHandlersDemo.Views
if (e.OldHandler != null)
(e.OldHandler.PlatformView as TextBox).GotFocus -=
OnGotFocus;
nativeView.SelectAll();
The advantage of this approach is that conditional compilation isn't required, and that
the partial methods don't have to be implemented on each platform. If an
implementation isn't provided on a platform, then the method and all calls to the
method are removed at compile time. For information about partial methods, see Partial
methods.
For information about the organization of the Platforms folder in a .NET MAUI project,
see Partial classes and methods. For information about how to configure multi-targeting
so that you don't have to place platform code into sub-folders of the Platforms folder,
see Configure multi-targeting.
Layouts
Article • 04/03/2023 • 7 minutes to read
.NET Multi-platform App UI (.NET MAUI) layout classes allow you to arrange and group
UI controls in your application. Choosing a layout class requires knowledge of how the
layout positions its child elements, and how the layout sizes its child elements. In
addition, it may be necessary to nest layouts to create your desired layout.
StackLayout
A StackLayout organizes elements in a one-dimensional stack, either horizontally or
vertically. The Orientation property specifies the direction of the elements, and the
default orientation is Vertical . StackLayout is typically used to arrange a subsection of
the UI on a page.
The following XAML shows how to create a vertical StackLayout containing three Label
objects:
XAML
<StackLayout Margin="20,35,20,25">
<Label Text="The StackLayout has its Margin property set, to control the
rendering position of the StackLayout." />
</StackLayout>
In a StackLayout, if an element's size is not explicitly set, it expands to fill the available
width, or height if the Orientation property is set to Horizontal .
A StackLayout is often used as a parent layout, which contains other child layouts.
However, a StackLayout should not be used to reproduce a Grid layout by using a
combination of StackLayout objects. The following code shows an example of this bad
practice:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Details.HomePage"
Padding="0,20,0,0">
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Name:" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Age:" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Occupation:" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Address:" />
</StackLayout>
</StackLayout>
</ContentPage>
This is wasteful because unnecessary layout calculations are performed. Instead, the
desired layout can be better achieved by using a Grid.
HorizontalStackLayout
A HorizontalStackLayout organizes child views in a one-dimensional horizontal stack,
and is a more performant alternative to a StackLayout. HorizontalStackLayout is typically
used to arrange a subsection of the UI on a page.
XAML
<HorizontalStackLayout Margin="20">
<Rectangle Fill="Red"
HeightRequest="30"
WidthRequest="30" />
<Label Text="Red"
FontSize="18" />
</HorizontalStackLayout>
VerticalStackLayout
A VerticalStackLayout organizes child views in a one-dimensional vertical stack, and is a
more performant alternative to a StackLayout. VerticalStackLayout is typically used to
arrange a subsection of the UI on a page.
The following XAML shows how to create a VerticalStackLayout containing three Label
objects:
XAML
<VerticalStackLayout Margin="20,35,20,25">
</VerticalStackLayout>
Grid
A Grid is used for displaying elements in rows and columns, which can have
proportional or absolute sizes. A grid's rows and columns are specified with the
RowDefinitions and ColumnDefinitions properties.
To position elements in specific Grid cells, use the Grid.Column and Grid.Row attached
properties. To make elements span across multiple rows and columns, use the
Grid.RowSpan and Grid.ColumnSpan attached properties.
7 Note
A Grid layout should not be confused with tables, and is not intended to present
tabular data.
The following XAML shows how to create a Grid with two rows and two columns:
XAML
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
WidthRequest="200" />
<Label Grid.Column="1"
<Label Grid.Row="1"
<Label Grid.Column="1"
Grid.Row="1"
Text="Column 1, Row 1" />
</Grid>
Space can be distributed within a column or row by using auto sizing, which lets
columns and rows size to fit their content. This is achieved by setting the height of a
RowDefinition , or the width of a ColumnDefinition , to Auto . Proportional sizing can also
be used to distribute available space among the rows and columns of the grid by
weighted proportions. This is achieved by setting the height of a RowDefinition , or the
width of a ColumnDefinition , to a value that uses the * operator.
U Caution
Try to ensure that as few rows and columns as possible are set to Auto size. Each
auto-sized row or column will cause the layout engine to perform additional layout
calculations. Instead, use fixed size rows and columns if possible. Alternatively, set
rows and columns to occupy a proportional amount of space with the
GridUnitType.Star enumeration value.
FlexLayout
A FlexLayout is similar to a StackLayout in that it displays child elements either
horizontally or vertically in a stack. However, a FlexLayout can also wrap its children if
there are too many to fit in a single row or column, and also enables more granular
control of the size, orientation, and alignment of its child elements.
The following XAML shows how to create a FlexLayout that displays its views in a single
column:
XAML
<FlexLayout Direction="Column"
AlignItems="Center"
JustifyContent="SpaceEvenly">
</FlexLayout>
The Direction property is set to Column , which causes the children of the
FlexLayout to be arranged in a single column of items.
The AlignItems property is set to Center , which causes each item to be
horizontally centered.
The JustifyContent property is set to SpaceEvenly , which allocates all leftover
vertical space equally between all the items, and above the first item, and below
the last item.
) Important
XAML
<AbsoluteLayout Margin="40">
<BoxView Color="Red"
AbsoluteLayout.LayoutFlags="PositionProportional"
Rotation="30" />
<BoxView Color="Green"
AbsoluteLayout.LayoutFlags="PositionProportional"
Rotation="60" />
<BoxView Color="Blue"
AbsoluteLayout.LayoutFlags="PositionProportional"
</AbsoluteLayout>
Each BoxView is given an explicit size of 100x100, and is displayed in the same
position, horizontally centered.
The red BoxView is rotated 30 degrees, and the green BoxView is rotated 60
degrees.
On each BoxView, the AbsoluteLayout.LayoutFlags attached property is set to
PositionProportional , indicating that the position is proportional to the remaining
space after width and height are accounted for.
U Caution
BindableLayout
A BindableLayout enables any layout class that derives from the Layout class to generate
its content by binding to a collection of items, with the option to set the appearance of
each item with a DataTemplate.
A bindable layout is populated with data by setting its ItemsSource property to any
collection that implements IEnumerable , and attaching it to a Layout-derived class. The
appearance of each item in the bindable layout can be defined by setting the
BindableLayout.ItemTemplate attached property to a DataTemplate.
The following XAML shows how to bind a StackLayout to a collection of items, and
define their appearance with a DataTemplate:
XAML
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"
Aspect="AspectFill"
WidthRequest="44"
HeightRequest="44" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
Bindable layouts should only be used when the collection of items to be displayed is
small, and scrolling and selection isn't required.
For more information, see BindableLayout.
Input transparency
Each visual element has an InputTransparent bindable property that's used to define
whether the element can receive input. Its default value is false , ensuring that the
element can receive input. When this property is true on an element, the element won't
receive any input. Instead, input will be passed to any elements that are visually behind
the element.
The Layout class, from which all layouts derive, has a CascadeInputTransparent bindable
property that controls whether child elements inherit the input transparency of the
layout. Its default value is true , ensuring that setting the InputTransparent property to
true on a layout class will result in all elements within the layout not receiving any
input.
AbsoluteLayout
Article • 01/09/2023 • 8 minutes to read
The .NET Multi-platform App UI (.NET MAUI) AbsoluteLayout is used to position and size
children using explicit values. The position is specified by the upper-left corner of the
child relative to the upper-left corner of the AbsoluteLayout, in device-independent
units. AbsoluteLayout also implements a proportional positioning and sizing feature. In
addition, unlike some other layout classes, AbsoluteLayout is able to position children so
that they overlap.
indicates whether properties of the layout bounds used to position and size the
child are interpreted proportionally. The default value of this property is
AbsoluteLayoutFlags.None .
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings and styled. For more information about
attached properties, see .NET MAUI Attached Properties.
Position and size children
The position and size of children in an AbsoluteLayout is defined by setting the
AbsoluteLayout.LayoutBounds attached property of each child, using absolute values or
proportional values. Absolute and proportional values can be mixed for children when
the position should scale, but the size should stay fixed, or vice versa. For information
about absolute values, see Absolute positioning and sizing. For information about
proportional values, see Proportional positioning and sizing.
x, y . With this format, the x and y values indicate the position of the upper-left
corner of the child relative to its parent. The child is unconstrained and sizes itself.
x, y, width, height . With this format, the x and y values indicate the position of
the upper-left corner of the child relative to its parent, while the width and height
values indicate the child's size.
To specify that a child sizes itself horizontally or vertically, or both, set the width and/or
height values to the AbsoluteLayout.AutoSize property. However, overuse of this
property can harm application performance, as it causes the layout engine to perform
additional layout calculations.
) Important
2 Warning
Using absolute values for positioning and sizing children can be problematic,
because different devices have different screen sizes and resolutions. Therefore, the
coordinates for the center of the screen on one device may be offset on other
devices.
The following XAML shows an AbsoluteLayout whose children are positioned using
absolute values:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AbsoluteLayoutDemos.Views.XAML.StylishHeaderDemoPage"
<AbsoluteLayout Margin="20">
<BoxView Color="Silver"
<BoxView Color="Silver"
<BoxView Color="Silver"
<BoxView Color="Silver"
FontSize="24"
</AbsoluteLayout>
</ContentPage>
In this example, the position of each BoxView object is defined using the first two
absolute values that are specified in the AbsoluteLayout.LayoutBounds attached
property. The size of each BoxView is defined using the third and forth values. The
position of the Label object is defined using the two absolute values that are specified in
the AbsoluteLayout.LayoutBounds attached property. Size values are not specified for the
Label, and so it's unconstrained and sizes itself. In all cases, the absolute values
represent device-independent units.
C#
public class StylishHeaderDemoPage : ContentPage
public StylishHeaderDemoPage()
};
absoluteLayout.Add(new BoxView
Color = Colors.Silver
}, new Rect(0, 10, 200, 5));
absoluteLayout.Add(new BoxView
Color = Colors.Silver
}, new Rect(0, 20, 200, 5));
absoluteLayout.Add(new BoxView
Color = Colors.Silver
}, new Rect(10, 0, 5, 65));
absoluteLayout.Add(new BoxView
Color = Colors.Silver
}, new Rect(20, 0, 5, 65));
absoluteLayout.Add(new Label
FontSize = 24
}, new Point(30,25));
Content = absoluteLayout;
}
In this example, the position and size of each BoxView is defined using a Rect object.
The position of the Label is defined using a Point object. The C# example uses the
following Add extension methods to add children to the AbsoluteLayout :
C#
using Microsoft.Maui.Layouts;
namespace Microsoft.Maui.Controls
if (view == null)
if (bounds.IsEmpty)
absoluteLayout.Add(view);
absoluteLayout.SetLayoutBounds(view, bounds);
absoluteLayout.SetLayoutFlags(view, flags);
if (view == null)
if (position.IsEmpty)
absoluteLayout.Add(view);
In C#, it's also possible to set the position and size of a child of an AbsoluteLayout after
it has been added to the layout, using the AbsoluteLayout.SetLayoutBounds method. The
first argument to this method is the child, and the second is a Rect object.
7 Note
An AbsoluteLayout that uses absolute values can position and size children so that
they don't fit within the bounds of the layout.
None , indicates that values will be interpreted as absolute. This is the default value
of the AbsoluteLayout.LayoutFlags attached property.
XProportional , indicates that the x value will be interpreted as proportional, while
Tip
For example, if you use the SizeProportional flag and set the width of a child to 0.25
and the height to 0.1, the child will be one-quarter of the width of the AbsoluteLayout
and one-tenth the height. The PositionProportional flag is similar. A position of (0,0)
puts the child in the upper-left corner, while a position of (1,1) puts the child in the
lower-right corner, and a position of (0.5,0.5) centers the child within the
AbsoluteLayout.
The following XAML shows an AbsoluteLayout whose children are positioned using
proportional values:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AbsoluteLayoutDemos.Views.XAML.ProportionalDemoPage"
Title="Proportional demo">
<AbsoluteLayout>
<BoxView Color="Blue"
AbsoluteLayout.LayoutBounds="0.5,0,100,25"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<BoxView Color="Green"
AbsoluteLayout.LayoutBounds="0,0.5,25,100"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<BoxView Color="Red"
AbsoluteLayout.LayoutBounds="1,0.5,25,100"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<BoxView Color="Black"
AbsoluteLayout.LayoutBounds="0.5,1,100,25"
AbsoluteLayout.LayoutFlags="PositionProportional" />
AbsoluteLayout.LayoutBounds="0.5,0.5,110,25"
AbsoluteLayout.LayoutFlags="PositionProportional" />
</AbsoluteLayout>
</ContentPage>
In this example, each child is positioned using proportional values but sized using
absolute values. This is accomplished by setting the AbsoluteLayout.LayoutFlags
attached property of each child to PositionProportional . The first two values that are
specified in the AbsoluteLayout.LayoutBounds attached property, for each child, define
the position using proportional values. The size of each child is defined with the third
and forth absolute values, using device-independent units.
C#
public ProportionalDemoPage()
AbsoluteLayout.SetLayoutFlags(blue,
AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutFlags(green,
AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutFlags(red,
AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutFlags(black,
AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutFlags(label,
AbsoluteLayoutFlags.PositionProportional);
};
In this example, the position and size of each child is set with the
AbsoluteLayout.SetLayoutBounds method. The first argument to the method is the child,
and the second is a Rect object. The position of each child is set with proportional
values, while the size of each child is set with absolute values, using device-independent
units.
7 Note
An AbsoluteLayout that uses proportional values can position and size children so
that they don't fit within the bounds of the layout by using values outside the 0-1
range.
BindableLayout
Article • 04/03/2023 • 6 minutes to read
.NET Multi-platform App UI (.NET MAUI) bindable layouts enable any layout class that
derives from the Layout class to generate its content by binding to a collection of items,
with the option to set the appearance of each item with a DataTemplate.
Bindable layouts are provided by the BindableLayout class, which exposes the following
attached properties:
layout.
ItemTemplate – specifies the DataTemplate to apply to each item in the collection
7 Note
The ItemTemplate property takes precedence when both the ItemTemplate and
ItemTemplateSelector properties are set.
EmptyView – specifies the string or view that will be displayed when the
7 Note
The EmptyViewTemplate property takes precedence when both the EmptyView and
EmptyViewTemplate properties are set.
All of these properties can be attached to the AbsoluteLayout, FlexLayout, Grid,
HorizontalStackLayout, StackLayout, and VerticalStackLayout classes, which all derive
from the Layout class.
Bindable layouts should only be used when the collection of items to be displayed is
small, and scrolling and selection isn't required. While scrolling can be provided by
wrapping a bindable layout in a ScrollView, this is not recommended as bindable layouts
lack UI virtualization. When scrolling is required, a scrollable view that includes UI
virtualization, such as ListView or CollectionView, should be used. Failure to observe this
recommendation can lead to performance issues.
) Important
While it's technically possible to attach a bindable layout to any layout class that
derives from the Layout class, it's not always practical to do so, particularly for the
AbsoluteLayout and Grid classes. For example, consider the scenario of wanting to
display a collection of data in a Grid using a bindable layout, where each item in
the collection is an object containing multiple properties. Each row in the Grid
should display an object from the collection, with each column in the Grid
displaying one of the object's properties. Because the DataTemplate for the
bindable layout can only contain a single object, it's necessary for that object to be
a layout class containing multiple views that each display one of the object's
properties in a specific Grid column. While this scenario can be realised with
bindable layouts, it results in a parent Grid containing a child Grid for each item in
the bound collection, which is a highly inefficient and problematic use of the Grid
layout.
XAML
C#
BindableLayout.SetItemsSource(grid, items);
XAML
Orientation="Horizontal"
...>
<BindableLayout.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"
Aspect="AspectFill"
WidthRequest="44"
HeightRequest="44"
... />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
C#
BindableLayout.SetItemsSource(stackLayout, viewModel.User.TopFollowers);
BindableLayout.SetItemTemplate(stackLayout, imageTemplate);
In this example, every item in the TopFollowers collection will be displayed by an Image
view defined in the DataTemplate:
For more information about data templates, see Data templates.
XAML
BindableLayout.ItemTemplateSelector="{StaticResource
TechItemTemplateSelector}"
... />
C#
BindableLayout.SetItemsSource(flexLayout, viewModel.User.FavoriteTech);
BindableLayout.SetItemTemplateSelector(flexLayout, dataTemplateSelector);
C#
For more information about data template selectors, see Create a DataTemplateSelector.
XAML
<StackLayout BindableLayout.ItemsSource="{Binding
UserWithoutAchievements.Achievements}"
BindableLayout.EmptyView="No achievements">
...
</StackLayout>
The result is that when the data bound collection is null , the string set as the EmptyView
property value is displayed:
<StackLayout BindableLayout.ItemsSource="{Binding
UserWithoutAchievements.Achievements}">
<BindableLayout.EmptyView>
<StackLayout>
<Label Text="None."
FontAttributes="Italic"
FontAttributes="Italic"
</StackLayout>
</BindableLayout.EmptyView>
...
</StackLayout>
The result is that when the data bound collection is null , the StackLayout and its child
views are displayed.
a view that contains multiple child views. In addition, the BindingContext of the
EmptyViewTemplate will be inherited from the BindingContext of the BindableLayout. The
following XAML example shows the EmptyViewTemplate property set to a DataTemplate
that contains a single view:
XAML
<StackLayout BindableLayout.ItemsSource="{Binding
UserWithoutAchievements.Achievements}">
<BindableLayout.EmptyViewTemplate>
<DataTemplate>
</DataTemplate>
</BindableLayout.EmptyViewTemplate>
...
</StackLayout>
The result is that when the data bound collection is null , the Label in the DataTemplate
is displayed:
7 Note
XAML
<ContentPage ...>
<ContentPage.Resources>
...
<ContentView x:Key="BasicEmptyView">
<StackLayout>
FontSize="14" />
</StackLayout>
</ContentView>
<ContentView x:Key="AdvancedEmptyView">
<StackLayout>
<Label Text="None."
FontAttributes="Italic"
FontSize="14" />
FontAttributes="Italic"
FontSize="14" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout>
...
<StackLayout x:Name="stackLayout"
BindableLayout.ItemsSource="{Binding
UserWithoutAchievements.Achievements}">
...
</StackLayout>
</StackLayout>
</ContentPage>
The XAML defines two ContentView objects in the page-level ResourceDictionary, with
the Switch object controlling which ContentView object will be set as the EmptyView
property value. When the Switch is toggled, the OnEmptyViewSwitchToggled event
handler executes the ToggleEmptyView method:
C#
BindableLayout.SetEmptyView(stackLayout, view);
The ToggleEmptyView method sets the EmptyView property of the StackLayout object to
one of the two ContentView objects stored in the ResourceDictionary, based on the
value of the Switch.IsToggled property. Then, when the data bound collection is null ,
the ContentView object set as the EmptyView property is displayed.
FlexLayout
Article • 12/23/2022 • 14 minutes to read
The .NET Multi-platform App UI (.NET MAUI) FlexLayout is a layout that can arrange its
children horizontally and vertically in a stack, and can also wrap its children if there are
too many to fit in a single row or column. In addition, FlexLayout can control orientation
and alignment, and adapt to different screen sizes. FlexLayout is based on the Cascading
Style Sheets (CSS) Flexible Box Layout Module .
will distribute space between and around children that have been laid out on
multiple lines. The default value of this property is Stretch . For more information,
see AlignContent.
AlignItems , of type FlexAlignItems , which indicates how the layout engine will
distribute space between and around children along the cross axis. The default
value of this property is Stretch . For more information, see AlignItems.
Direction , of type FlexDirection , which defines the direction and main axis of
children. The default value of this property is Row . For more information, see
Direction.
JustifyContent , of type FlexJustify , which specifies how space is distributed
between and around children along the main axis. The default value of this
property is Start . For more information, see JustifyContent.
Position , of type FlexPosition , which determines whether the position of children
are relative to each other, or by using fixed values. The default value of this
property is Relative .
Wrap , of type FlexWrap , which controls whether children are laid out in a single line
or in multiple lines. The default value of this property is NoWrap . For more
information, see Wrap.
AlignSelf , of type FlexAlignSelf , which is an attached property that indicates
how the layout engine will distribute space between and around children for a
specific child along the cross axis. The default value of this property is Auto . For
more information, see AlignSelf.
Basis , of type FlexBasis , which is an attached property that defines the initial
main size of the child before free space is distributed according to other property
values. The default value of this property is Auto . For more information, see Basis.
Grow , of type float , which is an attached property that specifies the amount of
available space the child should use on the main axis. The default value of this
property is 0.0. A validation callback ensures that when the property is set, its value
is greater than or equal to 0. For more information, see Grow.
Order , of type int , which is an attached property that determines whether the
child should be laid out before or after other children in the container. The default
value of this property is 0. For more information, see Order.
Shrink , of type float , which is an attached property that controls how a child
should shrink so that all children can fit inside the container. The default value of
this property is 1.0. A validation callback ensures that when the property is set, its
value is greater than or equal to 0. For more information, see Shrink.
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings and styled.
) Important
When items in a FlexLayout are arranged in a column, the FlexLayout has a vertical
main axis and a horizontal cross axis. When items in a FlexLayout are arranged in a
row, the FlexLayout has a horizontal main axis and a vertical cross axis.
Direction
The Direction property, of type FlexDirection , defines the direction and main axis of
children. The FlexDirection enumeration defines the following members:
When the Direction property is set to Column , or ColumnReverse , the main-axis will be
the y-axis and items will be stacked vertically. When the Direction property is set to
Row , or RowReverse , the main-axis will be the x-axis and children will be stacked
horizontally.
7 Note
In XAML, you can specify the value of this property using the enumeration member
names in lowercase, uppercase, or mixed case, or you can use the two additional
strings shown in parentheses.
Wrap
The Wrap property, of type FlexWrap , controls whether children are laid out in a single
line or in multiple lines. The FlexWrap enumeration defines the following members:
NoWrap , which indicates that children are laid out in a single line. This is the default
Reverse (or "wrap-reverse" in XAML), which indicates that items are laid out in
When the Wrap property is set to NoWrap and the main axis is constrained, and the main
axis is not wide or tall enough to fit all the children, the FlexLayout attempts to make the
items smaller. You can control the shrink factor of children with the Shrink attached
bindable property.
When the Wrap property is set to Wrap or WrapReverse , the AlignContent property can
be used to specify how the lines should be distributed.
JustifyContent
The JustifyContent property, of type FlexJustify , specifies how space is distributed
between and around children along the main axis. The FlexJustify enumeration
defines the following members:
Start (or "flex-start" in XAML), which indicates that children should be aligned at
the start. This is the default value of the JustifyContent property.
Center , which indicates that children should be aligned around the center.
End (or "flex-end" in XAML), which indicates that children should be aligned at the
end.
SpaceBetween (or "space-between" in XAML), which indicates that children should
be evenly distributed, with the first child being at the start and the last child being
at the end.
SpaceAround (or "space-around" in XAML), which indicates that children should be
evenly distributed, with the first and last children having a half-size space.
SpaceEvenly , which indicates that children should be evenly distributed, with all
children having equal space around them.
AlignItems
The AlignItems property, of type FlexAlignItems , indicates how the layout engine will
distribute space between and around children along the cross axis. The FlexAlignItems
enumeration defines the following members:
Stretch , which indicates that children should be stretched out. This is the default
the start.
End (or "flex-end" in XAML), which indicates that children should be aligned at the
end.
This is one of two properties that indicates how children are aligned on the cross axis.
Within each row, children are stretched or aligned on the start, center, or end of each
item.
For any individual child, the AlignItems setting can be overridden with the AlignSelf
attached bindable property.
AlignContent
The AlignContent property, of type FlexAlignContent , determines how the layout
engine will distribute space between and around children that have been laid out on
multiple lines. The FlexAlignContent enumeration defines the following members:
Stretch , which indicates that children should be stretched out. This is the default
value of the AlignContent property.
Center , which indicates that children should be aligned around the center.
Start (or "flex-start" in XAML), which indicates that children should be aligned at
the start.
End (or "flex-end" in XAML), which indicates that children should be aligned at the
end.
SpaceBetween (or "space-between" in XAML), which indicates that children should
be evenly distributed, with the first child being at the start and the last child being
at the end.
SpaceAround (or "space-around" in XAML), which indicates that children should be
evenly distributed, with the first and last children having a half-size space.
SpaceEvenly , which indicates that children should be evenly distributed, with all
children having equal space around them.
The AlignContent property has no effect when there is only one row or column.
AlignSelf
The AlignSelf property, of type FlexAlignSelf , indicates how the layout engine will
distribute space between and around children for a specific child along the cross axis.
The FlexAlignSelf enumeration defines the following members:
Auto , which indicates that a child should be aligned according to the alignment
value of its parent. This is the default value of the AlignSelf property.
Stretch , which indicates that a child should be stretched out.
Center , which indicates that a child should be aligned around the center.
Start (or "flex-start" in XAML), which indicates that a child should be aligned at
the start.
End (or "flex-end" in XAML), which indicates that a child should be aligned at the
end.
For any individual child of the FlexLayout, this property overrides the AlignItems
property set on the FlexLayout. The default setting of Auto means to use the AlignItems
setting.
In XAML, this property is set on a child without any reference to its FlexLayout parent:
XAML
<Label FlexLayout.AlignSelf="Center"
... />
C#
FlexLayout.SetAlignSelf(label, FlexAlignSelf.Center);
Order
The Order property, of type int , enables you to change the order that children of the
FlexLayout are arranged. The default value of this property is 0.
Usually, children are arranged in the order in which they are added to the FlexLayout.
However, this order can be overridden by setting this property to a non-zero integer
value on one or more children. The FlexLayout then arranges its children based on their
Order property values. Children with the same Order property values are arranged in
Basis
The Basis property, of type FlexBasis , defines the initial size of the child on the main
axis before free space is distributed according to other property values. The value
specified by this property is the size along the main axis of the parent FlexLayout.
Therefore, this property indicates the width of a child when children are arranged in
rows, or the height of a child when children are arranged in columns. This property is
called basis because it specifies a size that is the basis of all subsequent layout.
The FlexBasis type is a structure that enables size to be specified in device-
independent units, or as a percentage of the size of the FlexLayout. The default value of
the Basis property is Auto , which means that the child's requested width or height is
used.
XAML
<Label FlexLayout.Basis="40"
... />
C#
FlexLayout.SetBasis(label, 40);
XAML
<Label FlexLayout.Basis="25%"
... />
C#
The first argument to the FlexBasis constructor is a fractional float value that must be
in the range of 0 to 1. The second argument indicates that the size is relative, rather
than absolute.
Grow
The Grow property, of type float , specifies the amount of available space the child
should use on the main axis. The default value of this property is 0.0, and its value must
be greater than or equal to 0.
The Grow property is used when the Wrap property is set to NoWrap and a row of
children has a total width less than the width of the FlexLayout, or a column of children
has a shorter height than the FlexLayout. The Grow property indicates how to apportion
the leftover space among the children. If a single child is given a positive Grow value,
then that child takes up all the remaining space. Alternatively, the remaining space can
also be allocated among two or more children.
Shrink
The Shrink property, of type float , controls how a child should shrink so that all
children can fit inside the container. The default value of this property is 1.0, and its
value must be greater than or equal to 0.
The Shrink property is used when the Wrap property is set to NoWrap and the aggregate
width of a row of children is greater than the width of the FlexLayout, or the aggregate
height of a single column of children is greater than the height of the FlexLayout.
Normally the FlexLayout will display these children by constricting their sizes. The
Shrink property can indicate which children are given priority in being displayed at their
full sizes.
Tip
The Grow and Shrink values can both be set to accommodate situations where the
aggregate child sizes might sometimes be less than or sometimes greater than the
size of the FlexLayout.
Examples
The following examples demonstrate common uses of FlexLayout.
Stack
A FlexLayout can substitute for a StackLayout:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FlexLayoutDemos.Views.SimpleStackPage"
Title="Simple Stack">
<FlexLayout Direction="Column"
AlignItems="Center"
JustifyContent="SpaceEvenly">
FontSize="18" />
<Image Source="dotnet_bot_branded.png"
HeightRequest="300" />
</FlexLayout>
</ContentPage>
In this example, the Direction property is set to Column , which causes the children of
the FlexLayout to be arranged in a single column. The AlignItems property is set to
Center , which causes each child to be horizontally centered. The JustifyContent
property is set to SpaceEvenly which allocates all leftover vertical space equally between
all the children, above the first child and below the last child:
7 Note
The AlignSelf attached property can be used to override the AlignItems property
for a specific child.
Wrap items
A FlexLayout can wrap its children to additional rows or columns:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FlexLayoutDemos.Views.PhotoWrappingPage"
Title="Photo Wrapping">
<Grid>
<ScrollView>
<FlexLayout x:Name="flexLayout"
Wrap="Wrap"
JustifyContent="SpaceAround" />
</ScrollView>
...
</Grid>
</ContentPage>
In this example, the Direction property of the FlexLayout is not set, so it has the default
setting of Row , meaning that the children are arranged in rows and the main axis is
horizontal. The Wrap property is set to Wrap , which causes children to wrap to the next
row if there are too many children to fit on a row. The JustifyContent property is set to
SpaceAround which allocates all leftover space on the main axis so that each child is
surrounded by the same amount of space:
The code-behind file for this example retrieves a collection of photos and adds them to
the FlexLayout.
In addition, the FlexLayout is a child of a ScrollView. Therefore, if there are too many
rows to fit on the page, then the ScrollView has a default Orientation property of
Vertical and allows vertical scrolling.
Page layout
There is a standard layout in web design called the holy grail because it's a layout
format that is very desirable, but often hard to realize with perfection. The layout
consists of a header at the top of the page and a footer at the bottom, both extending
to the full width of the page. Occupying the center of the page is the main content, but
often with a columnar menu to the left of the content and supplementary information
(sometimes called an aside area) at the right. This layout can be realized with a
FlexLayout.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FlexLayoutDemos.Views.HolyGrailLayoutPage"
<FlexLayout Direction="Column">
<Label Text="HEADER"
FontSize="18"
BackgroundColor="Aqua"
HorizontalTextAlignment="Center" />
<FlexLayout FlexLayout.Grow="1">
<Label Text="CONTENT"
FontSize="18"
BackgroundColor="Gray"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FlexLayout.Grow="1" />
<BoxView FlexLayout.Basis="50"
FlexLayout.Order="-1"
Color="Blue" />
<BoxView FlexLayout.Basis="50"
Color="Green" />
</FlexLayout>
<Label Text="FOOTER"
FontSize="18"
BackgroundColor="Pink"
HorizontalTextAlignment="Center" />
</FlexLayout>
</ContentPage>
The navigation and aside areas are rendered with a BoxView on the left and right. The
first FlexLayout has a vertical main axis and contains three children arranged in a
column. These are the header, the body of the page, and the footer. The nested
FlexLayout has a horizontal main axis with three children arranged in a row:
In this example, the Order property is set on the first BoxView to a value less than its
siblings to cause it to appear as the first item in the row. The Basis property is set on
both BoxView objects to give them a width of 50 device-independent units. The Grow
property is set on the nested FlexLayout to indicate that this FlexLayout should occupy
all of the unused vertical space within the outer FlexLayout. In addition, the Grow
property is set on the Label representing the content, to indicate that this content is to
occupy all of the unused horizontal space within the nested FlexLayout.
7 Note
There's also a Shrink property that you can use when the size of children exceeds
the size of the FlexLayout but wrapping is not desired.
Grid
Article • 01/06/2023 • 14 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Grid, is a layout that organizes its children
into rows and columns, which can have proportional or absolute sizes. By default, a Grid
contains one row and one column. In addition, a Grid can be used as a parent layout
that contains other child layouts.
The Grid should not be confused with tables, and is not intended to present tabular
data. Unlike HTML tables, a Grid is intended for laying out content. For displaying
tabular data, consider using a ListView or CollectionView.
Column , of type int , which is an attached property that indicates the column
alignment of a view within a parent Grid. The default value of this property is 0. A
validation callback ensures that when the property is set, its value is greater than
or equal to 0.
ColumnDefinitions , of type ColumnDefinitionCollection , is a list of
ColumnDefinition objects that define the width of the grid columns.
ColumnSpacing , of type double , indicates the distance between grid columns. The
default value of this property is 0.
ColumnSpan , of type int , which is an attached property that indicates the total
number of columns that a view spans within a parent Grid. The default value of this
property is 1. A validation callback ensures that when the property is set, its value
is greater than or equal to 1.
Row , of type int , which is an attached property that indicates the row alignment of
a view within a parent Grid. The default value of this property is 0. A validation
callback ensures that when the property is set, its value is greater than or equal to
0.
RowDefinitions , of type RowDefinitionCollection , is a list of RowDefinition objects
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings and styled.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridTutorial.MainPage">
<Grid Margin="20,35,20,20">
<Label Text="By default, a Grid contains one row and one column." />
</Grid>
</ContentPage>
In this example, the Grid contains a single child Label that's automatically positioned in a
single location:
The layout behavior of a Grid can be defined with the RowDefinitions and
ColumnDefinitions properties, which are collections of RowDefinition and
ColumnDefinition objects, respectively. These collections define the row and column
characteristics of a Grid, and should contain one RowDefinition object for each row in
the Grid, and one ColumnDefinition object for each column in the Grid.
The RowDefinition class defines a Height property, of type GridLength , and the
ColumnDefinition class defines a Width property, of type GridLength . The GridLength
struct specifies a row height or a column width in terms of the GridUnitType
enumeration, which has three members:
(a number in XAML).
Auto – the row height or column width is autosized based on the cell contents
( Auto in XAML).
Star – leftover row height or column width is allocated proportionally (a number
followed by * in XAML).
A Grid row with a Height property of Auto constrains the height of views in that row in
the same way as a vertical StackLayout. Similarly, a column with a Width property of
Auto works much like a horizontal StackLayout.
U Caution
Try to ensure that as few rows and columns as possible are set to Auto size. Each
auto-sized row or column will cause the layout engine to perform additional layout
calculations. Instead, use fixed size rows and columns if possible. Alternatively, set
rows and columns to occupy a proportional amount of space with the
GridUnitType.Star enumeration value.
The following XAML shows how to create a Grid with three rows and two columns:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.XAML.BasicGridPage"
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
...
</Grid>
</ContentPage>
In this example, the Grid has an overall height that is the height of the page. The Grid
knows that the height of the third row is 100 device-independent units. It subtracts that
height from its own height, and allocates the remaining height proportionally between
the first and second rows based on the number before the star. In this example, the
height of the first row is twice that of the second row.
The two ColumnDefinition objects both set the Width to * , which is the same as 1* ,
meaning that the width of the screen is divided equally beneath the two columns.
) Important
Child views can be positioned in specific Grid cells with the Grid.Column and Grid.Row
attached properties. In addition, to make child views span across multiple rows and
columns, use the Grid.RowSpan and Grid.ColumnSpan attached properties.
The following XAML shows the same Grid definition, and also positions child views in
specific Grid cells:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.XAML.BasicGridPage"
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Column="1"
Color="Blue" />
<Label Grid.Column="1"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Color="Teal" />
<Label Grid.Row="1"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="1"
Color="Purple" />
<Label Grid.Row="1"
Grid.Column="1"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="2"
Grid.ColumnSpan="2"
Color="Red" />
<Label Grid.Row="2"
Grid.ColumnSpan="2"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
</ContentPage>
7 Note
The Grid.Row and Grid.Column properties are both indexed from 0, and so
Grid.Row="2" refers to the third row while Grid.Column="1" refers to the second
column. In addition, both of these properties have a default value of 0, and so don't
need to be set on child views that occupy the first row or first column of a Grid.
In this example, all three Grid rows are occupied by BoxView and Label views. The third
row is 100 device-independent units high, with the first two rows occupying the
remaining space (the first row is twice as high as the second row). The two columns are
equal in width and divide the Grid in half. The BoxView in the third row spans both
columns:
In addition, child views in a Grid can share cells. The order that the children appear in
the XAML is the order that the children are placed in the Grid. In the previous example,
the Label objects are only visible because they are rendered on top of the BoxView
objects. The Label objects would not be visible if the BoxView objects were rendered on
top of them.
C#
public BasicGridPage()
RowDefinitions =
new RowDefinition(),
},
ColumnDefinitions =
new ColumnDefinition(),
new ColumnDefinition()
};
// Row 0
// The BoxView and Label are in row 0 and column 0, and so only need
to be added to the
grid.Add(new BoxView
Color = Colors.Green
});
grid.Add(new Label
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
});
// This BoxView and Label are in row 0 and column 1, which are
specified as arguments
grid.Add(new BoxView
Color = Colors.Blue
}, 1, 0);
grid.Add(new Label
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 0);
// Row 1
// This BoxView and Label are in row 1 and column 0, which are
specified as arguments
grid.Add(new BoxView
Color = Colors.Teal
}, 0, 1);
grid.Add(new Label
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 0, 1);
// This BoxView and Label are in row 1 and column 1, which are
specified as arguments
grid.Add(new BoxView
Color = Colors.Purple
}, 1, 1);
grid.Add(new Label
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 1);
// Row 2
Grid.SetRow(boxView, 2);
Grid.SetColumnSpan(boxView, 2);
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
Grid.SetRow(label, 2);
Grid.SetColumnSpan(label, 2);
grid.Add(boxView);
grid.Add(label);
Content = grid;
XAML
<Grid RowDefinitions="1*, Auto, 25, 14, 20"
...
</Grid>
In this example, the Grid has five rows and four columns. The third, forth, and fifth rows
are set to absolute heights, with the second row auto-sizing to its content. The
remaining height is then allocated to the first row.
The forth column is set to an absolute width, with the third column auto-sizing to its
content. The remaining width is allocated proportionally between the first and second
columns based on the number before the star. In this example, the width of the second
column is twice that of the first column (because * is identical to 1* ).
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.XAML.GridSpacingPage"
<Grid RowSpacing="6"
ColumnSpacing="6">
...
</Grid>
</ContentPage>
This example creates a Grid whose rows and columns are separated by 6 device-
independent units of space:
Tip
C#
public GridSpacingPage()
RowSpacing = 6,
ColumnSpacing = 6,
...
};
...
Content = grid;
Alignment
Child views in a Grid can be positioned within their cells by the HorizontalOptions and
VerticalOptions properties. These properties can be set to the following fields from the
LayoutOptions struct:
Start
Center
End
Fill
The following XAML creates a Grid with nine equal-size cells, and places a Label in each
cell with a different alignment:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="GridDemos.Views.XAML.GridAlignmentPage"
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
HorizontalOptions="Start"
VerticalOptions="Start" />
<BoxView Grid.Column="1"
Color="LightSkyBlue" />
<Label Grid.Column="1"
Text="Upper center"
HorizontalOptions="Center"
VerticalOptions="Start"/>
<BoxView Grid.Column="2"
Color="CadetBlue" />
<Label Grid.Column="2"
Text="Upper right"
HorizontalOptions="End"
VerticalOptions="Start" />
<BoxView Grid.Row="1"
Color="CornflowerBlue" />
<Label Grid.Row="1"
Text="Center left"
HorizontalOptions="Start"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="1"
Color="DodgerBlue" />
<Label Grid.Row="1"
Grid.Column="1"
Text="Center center"
HorizontalOptions="Center"
VerticalOptions="Center" />
<BoxView Grid.Row="1"
Grid.Column="2"
Color="DarkSlateBlue" />
<Label Grid.Row="1"
Grid.Column="2"
Text="Center right"
HorizontalOptions="End"
VerticalOptions="Center" />
<BoxView Grid.Row="2"
Color="SteelBlue" />
<Label Grid.Row="2"
Text="Lower left"
HorizontalOptions="Start"
VerticalOptions="End" />
<BoxView Grid.Row="2"
Grid.Column="1"
Color="LightBlue" />
<Label Grid.Row="2"
Grid.Column="1"
Text="Lower center"
HorizontalOptions="Center"
VerticalOptions="End" />
<BoxView Grid.Row="2"
Grid.Column="2"
Color="BlueViolet" />
<Label Grid.Row="2"
Grid.Column="2"
Text="Lower right"
HorizontalOptions="End"
VerticalOptions="End" />
</Grid>
</ContentPage>
In this example, the Label objects in each row are all identically aligned vertically, but
use different horizontal alignments. Alternatively, this can be thought of as the Label
objects in each column being identically aligned horizontally, but using different vertical
alignments:
C#
public GridAlignmentPage()
RowDefinitions =
new RowDefinition(),
new RowDefinition(),
new RowDefinition()
},
ColumnDefinitions =
new ColumnDefinition(),
new ColumnDefinition(),
new ColumnDefinition()
};
// Row 0
grid.Add(new BoxView
Color = Colors.AliceBlue
});
grid.Add(new Label
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start
});
grid.Add(new BoxView
Color = Colors.LightSkyBlue
}, 1, 0);
grid.Add(new Label
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Start
}, 1, 0);
grid.Add(new BoxView
Color = Colors.CadetBlue
}, 2, 0);
grid.Add(new Label
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Start
}, 2, 0);
// Row 1
grid.Add(new BoxView
Color = Colors.CornflowerBlue
}, 0, 1);
grid.Add(new Label
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Center
}, 0, 1);
grid.Add(new BoxView
Color = Colors.DodgerBlue
}, 1, 1);
grid.Add(new Label
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}, 1, 1);
grid.Add(new BoxView
Color = Colors.DarkSlateBlue
}, 2, 1);
grid.Add(new Label
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
}, 2, 1);
// Row 2
grid.Add(new BoxView
Color = Colors.SteelBlue
}, 0, 2);
grid.Add(new Label
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.End
}, 0, 2);
grid.Add(new BoxView
Color = Colors.LightBlue
}, 1, 2);
grid.Add(new Label
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.End
}, 1, 2);
grid.Add(new BoxView
Color = Colors.BlueViolet
}, 2, 2);
grid.Add(new Label
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.End
}, 2, 2);
Content = grid;
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:GridDemos.Converters"
x:Class="GridDemos.Views.XAML.ColorSlidersGridPage"
<ContentPage.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment"
Value="Center" />
</Style>
</ContentPage.Resources>
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<BoxView x:Name="boxView"
Color="Black" />
<Grid Grid.Row="1"
Margin="20">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Slider x:Name="redSlider"
ValueChanged="OnSliderValueChanged" />
<Label Grid.Row="1"
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
<Slider x:Name="greenSlider"
Grid.Row="2"
ValueChanged="OnSliderValueChanged" />
<Label Grid.Row="3"
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
<Slider x:Name="blueSlider"
Grid.Row="4"
ValueChanged="OnSliderValueChanged" />
<Label Grid.Row="5"
Path=Value,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
</Grid>
</Grid>
</ContentPage>
In this example, the root Grid contains a BoxView in its first row, and a child Grid in its
second row. The child Grid contains Slider objects that manipulate the color displayed
by the BoxView, and Label objects that display the value of each Slider:
) Important
The deeper you nest Grid objects and other layouts, the more the nested layouts
will impact performance.
C#
BoxView boxView;
Slider redSlider;
Slider greenSlider;
Slider blueSlider;
public ColorSlidersGridPage()
Setters =
};
Resources.Add(labelStyle);
RowDefinitions =
new RowDefinition()
};
rootGrid.Add(boxView);
RowDefinitions =
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition(),
new RowDefinition()
};
redSlider.ValueChanged += OnSliderValueChanged;
childGrid.Add(redSlider);
Grid.SetRow(redLabel, 1);
childGrid.Add(redLabel);
greenSlider.ValueChanged += OnSliderValueChanged;
Grid.SetRow(greenSlider, 2);
childGrid.Add(greenSlider);
Grid.SetRow(greenLabel, 3);
childGrid.Add(greenLabel);
blueSlider.ValueChanged += OnSliderValueChanged;
Grid.SetRow(blueSlider, 4);
childGrid.Add(blueSlider);
Grid.SetRow(blueLabel, 5);
childGrid.Add(blueLabel);
rootGrid.Add(childGrid, 0, 1);
Content = rootGrid;
HorizontalStackLayout
Article • 02/09/2023 • 3 minutes to read
Spacing , of type double , indicates the amount of space between each child view.
The default value of this property is 0.
This property is backed by a BindableProperty object, which means that it can be the
target of data bindings and styled.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.HorizontalStackLayoutPage">
<HorizontalStackLayout Margin="20">
<Rectangle Fill="Red"
HeightRequest="30"
WidthRequest="30" />
<Label Text="Red"
FontSize="18" />
</HorizontalStackLayout>
</ContentPage>
7 Note
The value of the Margin property represents the distance between an element and
its adjacent elements. For more information, see Position controls.
Space between child views
The spacing between child views in a HorizontalStackLayout can be changed by setting
the Spacing property to a double value:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.HorizontalStackLayoutPage">
<HorizontalStackLayout Margin="20"
Spacing="10">
<Rectangle Fill="Red"
HeightRequest="30"
WidthRequest="30" />
<Label Text="Red"
FontSize="18" />
</HorizontalStackLayout>
</ContentPage>
Tip
The Spacing property can be set to negative values to make child views overlap.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.HorizontalStackLayoutPage">
<HorizontalStackLayout Margin="20"
HeightRequest="200">
<Label Text="Start"
BackgroundColor="Gray"
VerticalOptions="Start" />
<Label Text="Center"
BackgroundColor="Gray"
VerticalOptions="Center" />
<Label Text="End"
BackgroundColor="Gray"
VerticalOptions="End" />
<Label Text="Fill"
BackgroundColor="Gray"
VerticalOptions="Fill" />
</HorizontalStackLayout>
</ContentPage>
In this example, alignment preferences are set on the Label objects to control their
position within the HorizontalStackLayout. The Start , Center , End , and Fill fields are
used to define the alignment of the Label objects within the parent
HorizontalStackLayout:
A HorizontalStackLayout only respects the alignment preferences on child views that are
in the opposite direction to the orientation of the layout. Therefore, the Label child
views within the HorizontalStackLayout set their VerticalOptions properties to one of
the alignment fields:
Start , which positions the Label at the start of the HorizontalStackLayout.
Fill , which ensures that the Label fills the height of the HorizontalStackLayout.
XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.HorizontalStackLayoutPage">
<HorizontalStackLayout Margin="20"
Spacing="6">
<VerticalStackLayout Spacing="6">
<Rectangle Fill="Red"
WidthRequest="30"
HeightRequest="30" />
<Rectangle Fill="Yellow"
WidthRequest="30"
HeightRequest="30" />
<Rectangle Fill="Blue"
WidthRequest="30"
HeightRequest="30" />
</VerticalStackLayout>
<VerticalStackLayout Spacing="6">
<Rectangle Fill="Green"
WidthRequest="30"
HeightRequest="30" />
<Rectangle Fill="Orange"
WidthRequest="30"
HeightRequest="30" />
<Rectangle Fill="Purple"
WidthRequest="30"
HeightRequest="30" />
</VerticalStackLayout>
</HorizontalStackLayout>
</ContentPage>
) Important
The deeper you nest layout objects, the more the nested layouts will impact
performance.
StackLayout
Article • 02/09/2023 • 7 minutes to read
The .NET Multi-platform App UI (.NET MAUI) StackLayout organizes child views in a one-
dimensional stack, either horizontally or vertically. By default, a StackLayout is oriented
vertically. In addition, a StackLayout can be used as a parent layout that contains other
child layouts.
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings and styled.
Vertical orientation
The following XAML shows how to create a vertically oriented StackLayout that contains
different child views:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.XAML.VerticalStackLayoutPage"
<StackLayout Margin="20">
<BoxView Color="Red"
HeightRequest="40" />
<BoxView Color="Yellow"
HeightRequest="40" />
<BoxView Color="Blue"
HeightRequest="40" />
<BoxView Color="Green"
HeightRequest="40" />
<BoxView Color="Orange"
HeightRequest="40" />
<BoxView Color="Purple"
HeightRequest="40" />
</StackLayout>
</ContentPage>
This example creates a vertical StackLayout containing Label and BoxView objects. By
default, there's no space between the child views:
C#
public VerticalStackLayoutPage()
Content = stackLayout;
7 Note
The value of the Margin property represents the distance between an element and
its adjacent elements. For more information, see Position controls.
Horizontal orientation
The following XAML shows how to create a horizontally oriented StackLayout by setting
its Orientation property to Horizontal :
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.XAML.HorizontalStackLayoutPage"
<StackLayout Margin="20"
Orientation="Horizontal"
HorizontalOptions="Center">
<BoxView Color="Red"
WidthRequest="40" />
<BoxView Color="Yellow"
WidthRequest="40" />
<BoxView Color="Blue"
WidthRequest="40" />
<BoxView Color="Green"
WidthRequest="40" />
<BoxView Color="Orange"
WidthRequest="40" />
<BoxView Color="Purple"
WidthRequest="40" />
</StackLayout>
</ContentPage>
C#
public HorizontalStackLayoutPage()
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.Center
};
Content = stackLayout;
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.XAML.StackLayoutSpacingPage"
<StackLayout Margin="20"
Spacing="6">
<BoxView Color="Red"
HeightRequest="40" />
<BoxView Color="Yellow"
HeightRequest="40" />
<BoxView Color="Blue"
HeightRequest="40" />
<BoxView Color="Green"
HeightRequest="40" />
<BoxView Color="Orange"
HeightRequest="40" />
<BoxView Color="Purple"
HeightRequest="40" />
</StackLayout>
</ContentPage>
This example creates a vertical StackLayout containing Label and BoxView objects that
have six device-independent units of vertical space between them:
Tip
The Spacing property can be set to negative values to make child views overlap.
The equivalent C# code is:
C#
public StackLayoutSpacingPage()
Spacing = 6
};
Content = stackLayout;
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.XAML.AlignmentPage"
Title="Alignment demo">
<StackLayout Margin="20"
Spacing="6">
<Label Text="Start"
BackgroundColor="Gray"
HorizontalOptions="Start" />
<Label Text="Center"
BackgroundColor="Gray"
HorizontalOptions="Center" />
<Label Text="End"
BackgroundColor="Gray"
HorizontalOptions="End" />
<Label Text="Fill"
BackgroundColor="Gray"
HorizontalOptions="Fill" />
</StackLayout>
</ContentPage>
In this example, alignment preferences are set on the Label objects to control their
position within the StackLayout. The Start , Center , End , and Fill fields are used to
define the alignment of the Label objects within the parent StackLayout:
A StackLayout only respects the alignment preferences on child views that are in the
opposite direction to the StackLayout orientation. Therefore, the Label child views within
the vertically oriented StackLayout set their HorizontalOptions properties to one of the
alignment fields:
Start , which positions the Label on the left-hand side of the StackLayout.
Center , which centers the Label in the StackLayout.
End , which positions the Label on the right-hand side of the StackLayout.
Fill , which ensures that the Label fills the width of the StackLayout.
public AlignmentPage()
Spacing = 6
};
Content = stackLayout;
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.XAML.CombinedStackLayoutPage"
<StackLayout Margin="20">
...
<Frame BorderColor="Black"
Padding="5">
<StackLayout Orientation="Horizontal"
Spacing="15">
<BoxView Color="Red"
WidthRequest="40" />
<Label Text="Red"
FontSize="18"
VerticalOptions="Center" />
</StackLayout>
</Frame>
<Frame BorderColor="Black"
Padding="5">
<StackLayout Orientation="Horizontal"
Spacing="15">
<BoxView Color="Yellow"
WidthRequest="40" />
<Label Text="Yellow"
FontSize="18"
VerticalOptions="Center" />
</StackLayout>
</Frame>
<Frame BorderColor="Black"
Padding="5">
<StackLayout Orientation="Horizontal"
Spacing="15">
<BoxView Color="Blue"
WidthRequest="40" />
<Label Text="Blue"
FontSize="18"
VerticalOptions="Center" />
</StackLayout>
</Frame>
...
</StackLayout>
</ContentPage>
In this example, the parent StackLayout contains nested StackLayout objects inside
Frame objects. The parent StackLayout is oriented vertically, while the child StackLayout
objects are oriented horizontally:
) Important
The deeper you nest StackLayout objects and other layouts, the more the nested
layouts will impact performance.
C#
public CombinedStackLayoutPage()
BorderColor = Colors.Black,
};
Orientation = StackOrientation.Horizontal,
Spacing = 15
};
frame1.Content = frame1StackLayout;
BorderColor = Colors.Black,
};
Orientation = StackOrientation.Horizontal,
Spacing = 15
};
frame2.Content = frame2StackLayout;
BorderColor = Colors.Black,
};
Orientation = StackOrientation.Horizontal,
Spacing = 15
};
frame3.Content = frame3StackLayout;
...
stackLayout.Add(frame1);
stackLayout.Add(frame2);
stackLayout.Add(frame3);
stackLayout.Add(frame4);
stackLayout.Add(frame5);
stackLayout.Add(frame6);
Content = stackLayout;
VerticalStackLayout
Article • 02/09/2023 • 3 minutes to read
The .NET Multi-platform App UI (.NET MAUI) VerticalStackLayout organizes child views in
a one-dimensional vertical stack, and is a more performant alternative to a StackLayout.
In addition, a VerticalStackLayout can be used as a parent layout that contains other
child layouts.
Spacing , of type double , indicates the amount of space between each child view.
The default value of this property is 0.
This property is backed by a BindableProperty object, which means that it can be the
target of data bindings and styled.
The following XAML shows how to create a VerticalStackLayout that contains different
child views:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.VerticalStackLayoutPage">
<VerticalStackLayout Margin="20">
<Rectangle Fill="Red"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Yellow"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Blue"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Green"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Orange"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Purple"
HeightRequest="30"
WidthRequest="300" />
</VerticalStackLayout>
</ContentPage>
7 Note
The value of the Margin property represents the distance between an element and
its adjacent elements. For more information, see Position controls.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.VerticalStackLayoutPage">
<VerticalStackLayout Margin="20"
Spacing="10">
<Rectangle Fill="Red"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Yellow"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Blue"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Green"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Orange"
HeightRequest="30"
WidthRequest="300" />
<Rectangle Fill="Purple"
HeightRequest="30"
WidthRequest="300" />
</VerticalStackLayout>
</ContentPage>
This example creates a VerticalStackLayout containing Label and Rectangle objects that
have ten device-independent units of space between the child views:
Tip
The Spacing property can be set to negative values to make child views overlap.
Tip
Don't set the HorizontalOptions property of a VerticalStackLayout unless you need
to. The default value of LayoutOptions.Fill allows for the best layout optimization.
Changing this property has a cost and consumes memory, even when setting it
back to its default value.
The following XAML example sets alignment preferences on each child view in the
VerticalStackLayout:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.VerticalStackLayoutPage">
<VerticalStackLayout Margin="20"
Spacing="6">
<Label Text="Start"
BackgroundColor="Gray"
HorizontalOptions="Start" />
<Label Text="Center"
BackgroundColor="Gray"
HorizontalOptions="Center" />
<Label Text="End"
BackgroundColor="Gray"
HorizontalOptions="End" />
<Label Text="Fill"
BackgroundColor="Gray"
HorizontalOptions="Fill" />
</VerticalStackLayout>
</ContentPage>
In this example, alignment preferences are set on the Label objects to control their
position within the VerticalStackLayout. The Start , Center , End , and Fill fields are
used to define the alignment of the Label objects within the parent VerticalStackLayout:
A VerticalStackLayout only respects the alignment preferences on child views that are in
the opposite direction to the orientation of the layout. Therefore, the Label child views
within the VerticalStackLayout set their HorizontalOptions properties to one of the
alignment fields:
Start , which positions the Label on the left-hand side of the VerticalStackLayout.
Fill , which ensures that the Label fills the width of the VerticalStackLayout.
XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackLayoutDemos.Views.VerticalStackLayoutPage">
<VerticalStackLayout Margin="20"
Spacing="6">
<Frame BorderColor="Black"
Padding="5">
<HorizontalStackLayout Spacing="15">
<Rectangle Fill="Red"
HeightRequest="30"
WidthRequest="30" />
<Label Text="Red"
FontSize="18" />
</HorizontalStackLayout>
</Frame>
<Frame BorderColor="Black"
Padding="5">
<HorizontalStackLayout Spacing="15">
<Rectangle Fill="Yellow"
HeightRequest="30"
WidthRequest="30" />
<Label Text="Yellow"
FontSize="18" />
</HorizontalStackLayout>
</Frame>
<Frame BorderColor="Black"
Padding="5">
<HorizontalStackLayout Spacing="15">
<Rectangle Fill="Blue"
HeightRequest="30"
WidthRequest="30" />
<Label Text="Blue"
FontSize="18" />
</HorizontalStackLayout>
</Frame>
</VerticalStackLayout>
</ContentPage>
) Important
The deeper you nest layout objects, the more the nested layouts will impact
performance.
ContentPage
Article • 12/23/2022 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) ContentPage displays a single view, which
is often a layout such as as Grid or StackLayout, and is the most common page type.
ContentPage defines a Content property, of type View, which defines the view that
represents the page's content. This property is backed by a BindableProperty object,
which means that it can be the target of data bindings, and styled. In addition,
ContentPage inherits Title , IconImageSource , BackgroundImageSource , IsBusy , and
Padding bindable properties from the Page class.
7 Note
The Content property is the content property of the ContentPage class, and
therefore does not need to be explicitly set from XAML.
.NET MAUI apps typically contain multiple pages that derive from ContentPage, and
navigation between these pages can be performed. For more information about page
navigation, see NavigationPage.
A ContentPage can be templated with a control template. For more information, see
Control templates.
Create a ContentPage
To add a ContentPage to a .NET MAUI app:
1. In Solution Explorer right-click on your project or folder in your project, and select
New Item....
2. In the Add New Item dialog, expand Installed > C# Items, select .NET MAUI, and
select the .NET MAUI ContentPage (XAML) item template, enter a suitable page
name, and click the Add button:
Visual Studio then creates a new ContentPage-derived page, which will be similar to the
following example:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MyPage"
Title="MyPage"
BackgroundColor="White">
<StackLayout>
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>
The child of a ContentPage is typically a layout, such as Grid or StackLayout, with the
layout typically containing multiple views. However, the child of the ContentPage can be
a view that displays a collection, such as CollectionView.
7 Note
The value of the Title property will be shown on the navigation bar, when the app
performs navigation using a NavigationPage. For more information, see
NavigationPage.
FlyoutPage
Article • 04/03/2023 • 5 minutes to read
The .NET Multi-platform App UI (.NET MAUI) FlyoutPage is a page that manages two
related pages of information – a flyout page that presents items, and a detail page that
presents details about items on the flyout page.
In a popover layout, the detail page covers or partially covers the flyout page.
Selecting an item on the flyout page will navigate to the corresponding detail
page. Apps running on phones always use this layout behavior.
In a split layout, the flyout page is displayed on the left and the detail page is on
the right. Apps running on tablets or the desktop can use this layout behavior, with
Windows using it by default.
Detail , of type Page, defines the detail page displayed for the selected item in the
flyout page.
Flyout , of type Page, defines the flyout page.
between flyout and detail pages. The default value of this property is true .
IsPresented , of type bool , determines whether the flyout or detail page is
displayed. The default value of this property is false , which displays the detail
page. It should be set to true to display the flyout page.
The IsGestureEnabled , IsPresented , and FlyoutLayoutBehavior properties are backed
by BindableProperty objects, which means that they can be targets of data bindings, and
styled.
2 Warning
FlyoutPage is incompatible with .NET MAUI Shell apps, and an exception will be
thrown if you attempt to use FlyoutPage in a Shell app. For more information
about Shell apps, see Shell.
Create a FlyoutPage
To create a flyout page, create a FlyoutPage object and set it's Flyout and Detail
properties. The Flyout property should be set to ContentPage object, and the Detail
property should be set to a TabbedPage, NavigationPage, or ContentPage object. This
will help to ensure a consistent user experience across all platforms.
) Important
The following example shows a FlyoutPage that sets the Flyout and Detail properties:
XAML
<FlyoutPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:FlyoutPageNavigation"
x:Class="FlyoutPageNavigation.MainPage">
<FlyoutPage.Flyout>
<local:FlyoutMenuPage x:Name="flyoutPage" />
</FlyoutPage.Flyout>
<FlyoutPage.Detail>
<NavigationPage>
<x:Arguments>
<local:ContactsPage />
</x:Arguments>
</NavigationPage>
</FlyoutPage.Detail>
</FlyoutPage>
In this example, the Flyout property is set to a ContentPage object, and the Detail
property is set to a NavigationPage containing a ContentPage object.
The following example shows the definition of the FlyoutMenuPage object, which is of
type ContentPage:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:FlyoutPageNavigation"
x:Class="FlyoutPageNavigation.FlyoutMenuPage"
Padding="0,40,0,0"
IconImageSource="hamburger.png"
Title="Personal Organiser">
<CollectionView x:Name="collectionView"
x:FieldModifier="public"
SelectionMode="Single">
<CollectionView.ItemsSource>
<local:FlyoutPageItem Title="Contacts"
IconSource="contacts.png"
TargetType="{x:Type
local:ContactsPage}" />
<local:FlyoutPageItem Title="TodoList"
IconSource="todo.png"
TargetType="{x:Type
local:TodoListPage}" />
<local:FlyoutPageItem Title="Reminders"
IconSource="reminders.png"
TargetType="{x:Type
local:ReminderPage}" />
</x:Array>
</CollectionView.ItemsSource>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="5,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1"
Margin="20,0"
Text="{Binding Title}"
FontSize="20"
FontAttributes="Bold"
VerticalOptions="Center" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
In this example, the flyout page consists of a CollectionView that's populated with data
by setting its ItemsSource property to an array of FlyoutPageItem objects. The following
example shows the definition of the FlyoutPageItem class:
C#
Title and IconImageSource properties set. The icon will appear on the detail page,
7 Note
The Flyout page must have its Title property set, or an exception will occur.
SelectionChanged event. This enables the MainPage object to set the Detail property to
the page that represents the selected CollectionView item. The following example shows
the event handler:
C#
public MainPage()
...
flyoutPage.collectionView.SelectionChanged += OnSelectionChanged;
if (item != null)
Detail = new
NavigationPage((Page)Activator.CreateInstance(item.TargetType));
if (!((IFlyoutPageController)this).ShouldShowSplitMode)
IsPresented = false;
Layout behavior
How the FlyoutPage displays the flyout and detail pages depends on the form factor of
the device the app is running on, the orientation of the device, and the value of the
FlyoutLayoutBehavior property. This property should be set to a value of the
FlyoutLayoutBehavior enumeration, which defines the following members:
Split – the flyout page is displayed on the left and the detail page is on the right.
SplitOnLandscape – a split screen is used when the device is in landscape
orientation.
SplitOnPortrait – a split screen is used when the device is in portrait orientation.
XAML
<FlyoutPage ...
FlyoutLayoutBehavior="Split">
...
</FlyoutPage>
) Important
Brush.
BarBackgroundColor , of type Color, specifies the background color of the
navigation bar.
BackButtonTitle , of type string , represents the text to use for the back button.
the NavigationPage. The default value of this property is true . This is an attached
property.
HasBackButton , of type bool , represents whether the navigation bar includes a
back button. The default value of this property is true . This is an attached
property.
IconColor , of type Color, defines the background color of the icon in the
read-only property.
TitleIconImageSource , of type ImageSource , defines the icon that represents the
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
Popped is raised when when a page is popped from the navigation stack.
PoppedToRoot is raised when the last non-root page is popped from the navigation
stack.
All three events receive NavigationEventArgs objects that define a read-only Page
property, which retrieves the page that was popped from the navigation stack, or the
newly visible page on the stack.
2 Warning
NavigationPage is incompatible with .NET MAUI Shell apps, and an exception will
be thrown if you attempt to use NavigationPage in a Shell app. For more
information about Shell apps, see Shell.
When the second page returns back to the first page, a page is popped from the stack,
and the new topmost page then becomes active:
A NavigationPage consists of a navigation bar, with the active page being displayed
below the navigation bar. The following diagram shows the main components of the
navigation bar:
An optional icon can be displayed between the back button and the title.
Navigation methods are exposed by the Navigation property on any Page derived
types. These methods provide the ability to push pages onto the navigation stack, to
pop pages from the stack, and to manipulate the stack.
Tip
C#
public App()
InitializeComponent();
7 Note
The RootPage property of a NavigationPage provides access to the first page in the
navigation stack.
C#
In this example, the DetailsPage object is pushed onto the navigation stack, where it
becomes the active page.
7 Note
The PushAsync method has an override that includes a bool argument that
specifies whether to display a page transition during navigation. The PushAsync
method that lacks the bool argument enables the page transition by default.
To programmatically return to the previous page, the PopAsync method should be called
on the Navigation property of the current page:
C#
await Navigation.PopAsync();
In this example, the current page is removed from the navigation stack, with the new
topmost page becoming the active page.
7 Note
The PopAsync method has an override that includes a bool argument that specifies
whether to display a page transition during navigation. The PopAsync method that
lacks the bool argument enables the page transition by default.
The InsertPageBefore method inserts a specified page in the navigation stack before an
existing specified page, as shown in the following diagram:
The RemovePage method removes the specified page from the navigation stack, as
shown in the following diagram:
A modal page can be any of the page types supported by .NET MAUI. To display a page
modally, the app should push it onto the modal stack, where it will become the active
page:
To return to the previous page the app should pop the current page from the modal
stack, and the new topmost page becomes the active page:
Modal navigation methods are exposed by the Navigation property on any Page
derived types. These methods provide the ability to push pages onto the modal stack,
and pop pages from the modal stack. The Navigation property also exposes a
ModalStack property from which pages in the modal stack can be obtained. However,
7 Note
In this example, the DetailsPage object is pushed onto the modal stack, where it
becomes the active page.
7 Note
The PushModalAsync method has an override that includes a bool argument that
specifies whether to display a page transition during navigation. The
PushModalAsync method that lacks the bool argument enables the page transition
by default.
C#
await Navigation.PopModalAsync();
In this example, the current page is removed from the modal stack, with the new
topmost page becoming the active page.
7 Note
The PopModalAsync method has an override that includes a bool argument that
specifies whether to display a page transition during navigation. The PopModalAsync
method that lacks the bool argument enables the page transition by default.
C#
Age = 30,
Occupation = "Developer",
Country = "USA"
};
...
C#
Age = 30,
Occupation = "Developer",
Country = "USA"
};
BindingContext = contact
});
The advantage of passing navigation data via a page's BindingContext is that the new
page can use data binding to display the data:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.DetailsPage"
Title="Details">
<StackLayout>
</StackLayout>
</ContentPage>
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NavigationPageTitleView.TitleViewPage">
<NavigationPage.TitleView>
<Slider HeightRequest="44"
WidthRequest="300" />
</NavigationPage.TitleView>
...
</ContentPage>
C#
NavigationPage.SetTitleView(this, titleView);
) Important
Many views won't appear in the navigation bar unless the size of the view is
specified with the WidthRequest and HeightRequest properties.
Because the Layout class derives from the View class, the TitleView attached property
can be set to display a layout class that contains multiple views. However, this can result
in clipping if the view displayed in the navigation bar is larger than the default size of
the navigation bar. However, on Android, the height of the navigation bar can be
changed by setting the NavigationPage.BarHeight bindable property to a double
representing the new height.
Tip
BarBackgroundColor , of type Color, defines the background color of the tab bar.
BarTextColor , of type Color, represents the color of the text on the tab bar.
SelectedTabColor , of type Color, indicates the color of a tab when it's selected.
UnselectedTabColor , of type Color, represents the color of a tab when it's
unselected.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The title of a tab is defined by the Page.Title property of the child page, and the tab icon
is defined by the Page.IconImageSource property of the child page.
In a TabbedPage, each Page object is created when the TabbedPage is constructed. This
can lead to a poor user experience, particularly if the TabbedPage is the root page of
your app. However, .NET MAUI Shell enables pages accessed through a tab bar to be
created on demand, in response to navigation. For more information about Shell apps,
see Shell.
2 Warning
TabbedPage is incompatible with .NET MAUI Shell apps, and an exception will be
thrown if you attempt to use TabbedPage in a Shell app.
Create a TabbedPage
Two approaches can be used to create a TabbedPage:
) Important
Regardless of the approach taken, the location of the tab bar in a TabbedPage is
platform-dependent:
On iOS, the list of tabs appears at the bottom of the screen, and the page content
is above. Each tab consists of a title and an icon. In portrait orientation, tab bar
icons appear above tab titles. In landscape orientation, icons and titles appear side
by side. In addition, a regular or compact tab bar may be displayed, depending on
the device and orientation. If there are more than five tabs, a More tab will appear,
which can be used to access the additional tabs.
On Android, the list of tabs appears at the top of the screen, and the page content
is below. Each tab consists of a title and an icon. However, the tabs can be moved
to the bottom of the screen with a platform-specific. If there are more than five
tabs, and the tab list is at the bottom of the screen, a More tab will appear that can
be used to access the additional tabs. For information about moving the tabs to
the bottom of the screen, see TabbedPage toolbar placement on Android.
On Windows, the list of tabs appears at the top of the screen, and the page
content is below. Each tab consists of a title.
XAML
<TabbedPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TabbedPageWithNavigationPage"
x:Class="TabbedPageWithNavigationPage.MainPage">
<local:TodayPage />
<local:SchedulePage />
<local:SettingsPage />
</TabbedPage>
Page objects that are added as child elements of TabbedPage are added to the Children
collection. The Children property of the MultiPage<T> class, from which TabbedPage
derives, is the ContentProperty of MultiPage<T> . Therefore, in XAML it's not necessary to
explicitly assign the Page objects to the Children property.
The following screenshot shows the appearance of the resulting tab bar on the
TabbedPage:
The page content for a tab appears when the tab is selected.
XAML
<TabbedPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TabbedPageDemo"
x:Class="TabbedPageDemo.MainPage"
ItemsSource="{x:Static local:MonkeyDataModel.All}">
<TabbedPage.ItemTemplate>
<DataTemplate>
IconImageSource="monkeyicon.png">
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center" />
<Image Source="{Binding PhotoUrl}"
HorizontalOptions="Center"
WidthRequest="200"
HeightRequest="200" />
<StackLayout Orientation="Horizontal">
FontAttributes="Bold" />
</StackLayout>
...
</StackLayout>
</StackLayout>
</ContentPage>
</DataTemplate>
</TabbedPage.ItemTemplate>
</TabbedPage>
In this example, each tab consists of a ContentPage object that uses Image and Label
objects to display data for the tab:
XAML
<TabbedPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TabbedPageWithNavigationPage"
x:Class="TabbedPageWithNavigationPage.MainPage">
<local:TodayPage />
<NavigationPage Title="Schedule"
IconImageSource="schedule.png">
<x:Arguments>
<local:SchedulePage />
</x:Arguments>
</NavigationPage>
</TabbedPage>
In this example, the TabbedPage is populated with two Page objects. The first child is a
ContentPage object, and the second child is a NavigationPage object containing a
ContentPage object.
C#
For more information about performing navigation using the NavigationPage class, see
NavigationPage.
2 Warning
The .NET Multi-platform App UI (.NET MAUI) BlazorWebView is a control that enables
you to host a Blazor web app in your .NET MAUI app. These apps, known as Blazor
Hybrid apps, enable a Blazor web app to be integrated with platform features and UI
controls. The BlazorWebView control can be added to any page of a .NET MAUI app,
and pointed to the root of the Blazor app. The Razor components run natively in the
.NET process and render web UI to an embedded web view control. In .NET MAUI, Blazor
Hybrid apps can run on all the platforms supported by .NET MAUI.
HostPage , of type string? , which defines the root page of the Blazor web app.
RootComponents , of type RootComponentsCollection , which specifies the collection
Selector , of type string? , which defines the CSS selector string that specifies
BlazorWebView is initialized but before any component has been rendered. This
event enables retrieval of the platform-specific web view instance.
UrlLoading , with an accompanying UrlLoadingEventArgs object, is raised when a
Browser developer tools can be used to inspect .NET MAUI Blazor apps. For more
information, see Use browser developer tools with ASP.NET Core Blazor Hybrid.
7 Note
While Visual Studio installs all the required tooling to develop .NET MAUI Blazor
apps, end users of .NET MAUI Blazor apps on Windows must install the WebView2
runtime.
For more information about Blazor Hybrid apps, see ASP.NET Core Blazor Hybrid.
This project template creates a multi-targeted .NET MAUI Blazor app that can be
deployed to Android, iOS, macOS, and Windows. For step-by-step instructions on
creating a .NET MAUI Blazor app, see Build a .NET MAUI Blazor app.
The BlazorWebView created by the project template is defined in MainPage.xaml, and
points to the root of the Blazor app:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BlazorWebViewDemo"
x:Class="BlazorWebViewDemo.MainPage"
BackgroundColor="{DynamicResource PageBackgroundColor}">
<BlazorWebView HostPage="wwwroot/index.html">
<BlazorWebView.RootComponents>
</BlazorWebView.RootComponents>
</BlazorWebView>
</ContentPage>
The root Razor component for the app is in Main.razor, which Razor compiles into a type
named Main in the application's root namespace. The rest of the Razor components are
in the Pages and Shared project folders, and are identical to the components used in the
default Blazor web template. Static web assets for the app are in the wwwroot folder.
1. Add the Razor SDK, Microsoft.NET.Sdk.Razor to your project by editing its first line
of the CSPROJ project file:
XML
<Project Sdk="Microsoft.NET.Sdk.Razor">
The Razor SDK is required to build and package projects containing Razor files for
Blazor projects.
2. Add the root Razor component for the app to the project.
3. Add your Razor components to project folders named Pages and Shared.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyBlazorApp"
x:Class="MyBlazorApp.MainPage">
<BlazorWebView HostPage="wwwroot/index.html">
<BlazorWebView.RootComponents>
</BlazorWebView.RootComponents>
</BlazorWebView>
</ContentPage>
C#
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
fonts.AddFont("OpenSans-Regular.ttf",
"OpenSansRegular");
});
builder.Services.AddMauiBlazorWebView();
#if DEBUG
builder.Services.AddMauiBlazorWebViewDeveloperTools();
#endif
// e.g. builder.Services.AddSingleton<WeatherForecastService>
();
return builder.Build();
Border
Article • 12/23/2022 • 4 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Border is a container control that draws a
border, background, or both, around another control. A Border can only contain one
child object. If you want to put a border around multiple objects, wrap them in a
container object such as a layout. For more information about layouts, see Layouts.
Content , of type IView , represents the content to display in the border. This
property is the ContentProperty of the Border class, and therefore does not need
to be explicitly set from XAML.
Padding , of type Thickness , represents the distance between the border and its
child element.
StrokeShape , of type IShape , describes the shape of the border. This property has
a type converter applied to it that can convert a string to its equivalent IShape .
Stroke , of type Brush, indicates the brush used to paint the border.
StrokeThickness , of type double , indicates the width of the border. The default
border.
StrokeDashOffset , of type double , specifies the distance within the dash pattern
StrokeMiterLimit , of type double , specifies the limit on the ratio of the miter
length to half the stroke thickness. The default value of this property is 10.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
) Important
When creating a border using a shape, such as a Rectangle or Polygon, only closed
shapes should be used. Therefore, open shapes such as Line are unsupported.
The default value of the StrokeShape property is Rectangle. Therefore, a Border will be
rectangular by default.
For more information about the properties that control the shape and stroke of the
border, see Shapes.
Create a Border
To draw a border, create a Border object and set its properties to define its appearance.
Then, set its child to the control to which the border should be added.
The following XAML example shows how to draw a border around a Label:
XAML
<Border Stroke="#C49B33"
StrokeThickness="4"
StrokeShape="RoundRectangle 40,0,0,40"
Background="#2B0B98"
Padding="16,8"
HorizontalOptions="Center">
TextColor="White"
FontSize="18"
FontAttributes="Bold" />
</Border>
Alternatively, the StrokeShape property value can be specified using property tag syntax:
XAML
<Border Stroke="#C49B33"
StrokeThickness="4"
Background="#2B0B98"
Padding="16,8"
HorizontalOptions="Center">
<Border.StrokeShape>
</Border.StrokeShape>
TextColor="White"
FontSize="18"
FontAttributes="Bold" />
</Border>
using Microsoft.Maui.Controls.Shapes;
...
Stroke = Color.FromArgb("#C49B33"),
Background = Color.FromArgb("#2B0B98"),
StrokeThickness = 4,
HorizontalOptions = LayoutOptions.Center,
},
TextColor = Colors.White,
FontSize = 18,
FontAttributes = FontAttributes.Bold
};
In this example, a border with rounded top-left and bottom-right corners is drawn
around a Label. The border shape is defined as a RoundRectangle object, whose
CornerRadius property is set to a Thickness value that enables independent control of
each corner of the rectangle:
Because the Stroke property is of type Brush, borders can also be drawn using
gradients:
XAML
<Border StrokeThickness="4"
StrokeShape="RoundRectangle 40,0,0,40"
Background="#2B0B98"
Padding="16,8"
HorizontalOptions="Center">
<Border.Stroke>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="Orange"
Offset="0.1" />
<GradientStop Color="Brown"
Offset="1.0" />
</LinearGradientBrush>
</Border.Stroke>
TextColor="White"
FontSize="18"
FontAttributes="Bold" />
</Border>
C#
using Microsoft.Maui.Controls.Shapes;
...
StrokeThickness = 4,
Background = Color.FromArgb("#2B0B98"),
HorizontalOptions = LayoutOptions.Center,
},
},
},
TextColor = Colors.White,
FontSize = 18,
FontAttributes = FontAttributes.Bold
};
In this example, a border that uses a linear gradient is drawn around a Label:
Define the border shape with a string
In XAML, the value of the StrokeShape property can be defined using property-tag
syntax, or as a string . Valid string values for the StrokeShape property are:
Ellipse
Line , followed by one or two x- and y-coordinate pairs. For example, Line 10 20
draws a line from (10,20) to (0,0), and Line 10 20, 100 120 draws a line from
(10,20) to (100,120).
Path , followed by path markup syntax data. For example, Path M 10,100 L 100,100
100,50Z will draw a triangular border. For more information about path markup
Rectangle
RoundRectangle , optionally followed by a corner radius. For example,
) Important
While Line is a valid string value for the StrokeShape property, its use is not
supported.
String -based x- and y-coordinate pairs can be delimited by a single comma and/or one
or more spaces. For example, "40,10 70,80" and "40 10, 70 80" are both valid. Coordinate
pairs will be converted to Point objects that define X and Y properties, of type double .
BoxView
Article • 04/03/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) BoxView draws a simple rectangle or
square, of a specified width, height, and color.
BoxView. This property can be set to a single double uniform corner radius value,
or a CornerRadius structure defined by four double values that are applied to the
top left, top right, bottom left, and bottom right of the BoxView.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
Although BoxView can mimic simple graphics, a better alternative is to use .NET
MAUI Shapes or .NET MAUI Graphics.
Create a BoxView
To draw a rectangle or square, create a BoxView object and set its Color , WidthRequest,
and HeightRequest properties. Optionally, you can also set its CornerRadius property.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BasicBoxView"
x:Class="BasicBoxView.MainPage">
<BoxView Color="CornflowerBlue"
CornerRadius="10"
WidthRequest="160"
HeightRequest="160"
VerticalOptions="Center"
HorizontalOptions="Center" />
</ContentPage>
In this example, a cornflower blue BoxView is displayed in the center of the page:
7 Note
A BoxView can also be a child of an AbsoluteLayout. In this case, both the location
and size of the BoxView are set using the LayoutBounds attached bindable
property.
A BoxView can also be sized to resemble a line of a specific width and thickness.
Frame
Article • 04/03/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Frame is used to wrap a view or layout with
a border that can be configured with color, shadow, and other options. Frames can be
used to create borders around controls but can also be used to create more complex UI.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The Frame class inherits from ContentView, which provides a Content bindable property.
The Content property is the ContentProperty of the Frame class, and therefore does not
need to be explicitly set from XAML.
7 Note
The Frame class existed in Xamarin.Forms and is present in .NET MAUI for users
who are migrating their apps from Xamarin.Forms to .NET MAUI. If you're building
a new .NET MAUI app it's recommended to use Border instead, and to set shadows
using the Shadow bindable property on VisualElement. For more information, see
Border and Shadow.
Create a Frame
A Frame object typically wraps another control, such as a Label:
XAML
<Frame>
</Frame>
XAML
<Frame BorderColor="Gray"
CornerRadius="10">
</Frame>
C#
BorderColor = Colors.Gray
CornerRadius = 10,
};
XAML
<Frame BorderColor="Gray"
CornerRadius="5"
Padding="8">
<StackLayout>
FontSize="14"
FontAttributes="Bold" />
<BoxView Color="Gray"
HeightRequest="2"
HorizontalOptions="Fill" />
<Label Text="Frames can wrap more complex layouts to create more complex
UI components, such as this card!"/>
</StackLayout>
</Frame>
Round elements
The CornerRadius property of the Frame control is one approach to creating a circle
image. The following XAML shows how to create a circle image with a Frame:
XAML
<Frame Margin="10"
BorderColor="Black"
CornerRadius="50"
HeightRequest="60"
WidthRequest="60"
IsClippedToBounds="True"
HorizontalOptions="Center"
VerticalOptions="Center">
<Image Source="outdoors.jpg"
Aspect="AspectFill"
Margin="-20"
HeightRequest="100"
WidthRequest="100" />
</Frame>
The .NET Multi-platform App UI (.NET MAUI) GraphicsView is a graphics canvas on which
2D graphics can be drawn using types from the Microsoft.Maui.Graphics namespace. For
more information about Microsoft.Maui.Graphics, see Graphics.
GraphicsView defines the Drawable property, of type IDrawable , which specifies the
content that will be drawn. This property is backed by a BindableProperty, which means
it can be the target of data binding, and styled.
pressed.
DragInteraction , with TouchEventArgs , which is raised when the GraphicsView is
dragged.
EndInteraction , with TouchEventArgs , which is raised when the press that raised
Create a GraphicsView
A GraphicsView must define an IDrawable object that specifies the content that will be
drawn on the control. This can be achieved by creating an object that derives from
IDrawable , and by implementing its Draw method:
C#
namespace MyMauiApp
The Draw method has ICanvas and RectF arguments. The ICanvas argument is the
drawing canvas on which you draw graphical objects. The RectF argument is a struct
that contains data about the size and location of the drawing canvas. For more
information about drawing on an ICanvas, see Draw graphical objects.
In XAML, the IDrawable object should be declared as a resource, and then consumed by
a GraphicsView by specifying its key:
XAML
<ContentPage xmlns=http://schemas.microsoft.com/dotnet/2021/maui
xmlns:x=http://schemas.microsoft.com/winfx/2009/xaml
xmlns:drawable="clr-namespace:MyMauiApp"
x:Class="MyMauiApp.MainPage">
<ContentPage.Resources>
</ContentPage.Resources>
<VerticalStackLayout>
HeightRequest="300"
WidthRequest="400" />
</VerticalStackLayout>
</ContentPage>
Bottom , of type float , which represents the y-coordinate of the bottom edge of
the canvas.
Center , of type PointF , which specifies the coordinates of the center of the canvas.
Height , of type float , which defines the height of the canvas.
IsEmpty , of type bool , which indicates whether the canvas has a zero size and
location.
Left , of type float , which represents the x-coordinate of the left edge of the
canvas.
Location , of type PointF , which defines the coordinates of the upper-left corner of
the canvas.
Right , of type float , which represents the x-coordinate of the right edge of the
canvas.
Size , of type SizeF , which defines the width and height of the canvas.
Top , of type float , which represents the y-coordinate of the top edge of the
canvas.
Width , of type float , which defines the width of the canvas.
X , of type float , which defines the x-coordinate of the upper-left corner of the
canvas.
Y , of type float , which defines the y-coordinate of the upper-left corner of the
canvas.
These properties can be used to position and size graphical objects on the ICanvas. For
example, graphical objects can be placed at the center of the Canvas by using the
Center.X and Center.Y values as arguments to a drawing method. For information
about drawing on an ICanvas, see Draw graphical objects.
C#
graphicsView.Invalidate();
.NET MAUI automatically invalidates the GraphicsView as needed by the UI. For example,
when the element is first shown, comes into view, or is revealed by moving an element
from on top of it, it's redrawn. The only time you need to call Invalidate is when you
want to force the GraphicsView to redraw itself, such as if you have changed its content
while it's still visible.
Image
Article • 02/09/2023 • 7 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Image displays an image that can be
loaded from a local file, a URI, an embedded resource, or a stream. The standard
platform image formats are supported, including animated GIFs, and local Scalable
Vector Graphics (SVG) files are also supported. For more information about adding
images to a .NET MAUI app project, see Add images to a .NET MAUI app project.
image as opaque while rendering it. The default value of this property is false .
Source , of type ImageSource , specifies the source of the image.
These properties are backed by BindableProperty objects, which means that they can be
styled, and be the target of data bindings.
7 Note
Font icons can be displayed by an Image by specifying the font icon data as a
FontImageSource object. For more information, see Display font icons.
The ImageSource class defines the following methods that can be used to load an image
from different sources:
an assembly.
FromStream returns a StreamImageSource that reads an image from a stream that
) Important
Images will be displayed at their full resolution unless the size of the Image is
constrained by its layout, or the HeightRequest or WidthRequest property of the
Image is specified.
For information about adding app icons and a splash screen to your app, see App icons
and Splash screen.
To comply with Android resource naming rules, all local image filenames must be
lowercase, start and end with a letter character, and contain only alphanumeric
characters or underscores. For more information, see App resources overview on
developer.android.com.
) Important
.NET MAUI converts SVG files to PNG files. Therefore, when adding an SVG file to
your .NET MAUI app project, it should be referenced from XAML or C# with a .png
extension.
Adhering to these rules for file naming and placement enables the following XAML to
load and display an image:
XAML
Source = ImageSource.FromFile("dotnet_bot.png")
};
C#
XAML
C#
};
C#
Image caching
Caching of downloaded images is enabled by default, with cached images being stored
for 1 day. This behavior can be changed by setting properties of the UriImageSource
class.
Uri , of type Uri , represents the URI of the image to be downloaded for display.
CacheValidity , of type TimeSpan , specifies how long the image will be stored
locally for. The default value of this property is 1 day.
CachingEnabled , of type bool , defines whether image caching is enabled. The
These properties are backed by BindableProperty objects, which means that they can be
styled, and be the target of data bindings.
To set a specific cache period, set the Source property to an UriImageSource object that
sets its CacheValidity property:
XAML
<Image>
<Image.Source>
<UriImageSource Uri="https://aka.ms/campus.jpg"
CacheValidity="10:00:00:00" />
</Image.Source>
</Image>
C#
};
Embedded images are loaded based on their resource ID, which is compromised of the
name of the project and its location in the project. For example, placing dotnet_bot.png
in the root folder of a project named MyProject will result in a resource ID of
MyProject.dotnet_bot.png. Similarly, placing dotnet_bot.png in the Assets folder of a
project named MyProject will result in a resource ID of MyProject.Assets.dotnet_bot.png.
C#
Source = ImageSource.FromResource("MyProject.Assets.dotnet_bot.png")
};
By default, the ImageSource.FromResource method only looks for images in the same
assembly as the calling code. However, the assembly containing the embedded image
can be specified as the second argument to the ImageSource.FromResource method:
C#
Source = ImageSource.FromResource("MyLibrary.MyFolder.myimage.png",
typeof(MyLibrary.MyClass).GetTypeInfo().Assembly)
};
C#
using System.Reflection;
using System.Xml;
namespace ImageDemos
[ContentProperty("Source")]
if (String.IsNullOrEmpty(Source))
IXmlLineInfoProvider lineInfoProvider =
serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as
IXmlLineInfoProvider;
string assemblyName =
GetType().GetTypeInfo().Assembly.GetName().Name;
object IMarkupExtension.ProvideValue(IServiceProvider
serviceProvider)
return (this as
IMarkupExtension<ImageSource>).ProvideValue(serviceProvider);
XAML
<ContentPage ...
xmlns:local="clr-namespace:ImageDemos">
<StackLayout>
</StackLayout>
</ContentPage>
For more information about XAML markup extensions, see Create XAML markup
extensions.
C#
};
XAML
) Important
While the animated GIF support in .NET MAUI includes the ability to download files,
it does not support caching or streaming animated GIFs.
By default, when an animated GIF is loaded it will not be played. This is because the
IsAnimationPlaying property, that controls whether an animated GIF is playing or
stopped, has a default value of false . Therefore, when an animated GIF is loaded it will
not be played until the IsAnimationPlaying property is set to true . Playback can be
stopped by reseting the IsAnimationPlaying property to false . Note that this property
has no effect when displaying a non-GIF image source.
the display area, with blank space added to the top/bottom or sides depending on
whether the image is wide or tall.
AspectFill - clips the image so that it fills the display area while preserving the
aspect ratio.
Fill - stretches the image to completely and exactly fill the display area. This may
The .NET Multi-platform App UI (.NET MAUI) Label displays single-line and multi-line
text. Text displayed by a Label can be colored, spaced, and can have text decorations.
displayed text.
FontAttributes , of type FontAttributes , determines text style.
FontAutoScalingEnabled , of type bool , defines whether the text will reflect scaling
preferences set in the operating system. The default value of this property is true .
FontFamily , of type string , defines the font family.
Label.
Padding , of type Thickness , determines the label's padding.
Text , of type string , defines the text displayed as the content of the label.
TextColor , of type Color, defines the color of the displayed text.
TextType , of type TextType , determines whether the Label should display plain text
or HTML text.
VerticalTextAlignment , of type TextAlignment , defines the vertical alignment of
Create a Label
The following example shows how to create a Label:
XAML
C#
Set colors
Labels can be set to use a specific text color via the TextColor property.
XAML
<Label TextColor="#77d065"
XAML
CharacterSpacing="10" />
The result is that characters in the text displayed by the Label are spaced
CharacterSpacing device-independent units apart.
XAML
<Label>
<Label.Text>
First line
Second line
</Label.Text>
</Label>
In C#, text can be forced onto a new line with the "\n" character:
C#
NoWrap — does not wrap text, displaying only as much text as can fit on one line.
replace by an ellipsis.
TailTruncation — shows the beginning of the text, truncating the end.
When MaxLines is -1, which is its default value, the Label respects the value of the
LineBreakMode property to either show just one line, possibly truncated, or all lines
with all text.
When MaxLines is 0, the Label isn't displayed.
When MaxLines is 1, the result is identical to setting the LineBreakMode property to
NoWrap , HeadTruncation , MiddleTruncation , or TailTruncation . However, the Label
will respect the value of the LineBreakMode property with regard to placement of
an ellipsis, if applicable.
When MaxLines is greater than 1, the Label will display up to the specified number
of lines, while respecting the value of the LineBreakMode property with regard to
placement of an ellipsis, if applicable. However, setting the MaxLines property to a
value greater than 1 has no effect if the LineBreakMode property is set to NoWrap .
The following XAML example demonstrates setting the MaxLines property on a Label:
XAML
LineBreakMode="WordWrap"
MaxLines="2" />
7 Note
On iOS, the Label.LineHeight property changes the line height of text that fits
on a single line, and text that wraps onto multiple lines.
On Android, the Label.LineHeight property only changes the line height of
text that wraps onto multiple lines.
On Windows, the Label.LineHeight property changes the line height of text
that wraps onto multiple lines.
XAML
LineBreakMode="WordWrap"
LineHeight="1.8" />
The following screenshot shows the result of setting the Label.LineHeight property to
1.8:
Display HTML
The Label class has a TextType property, which determines whether the Label object
should display plain text, or HTML text. This property should be set to one of the
members of the TextType enumeration:
Text indicates that the Label will display plain text, and is the default value of the
TextType property.
Therefore, Label objects can display HTML by setting the TextType property to Html ,
and the Text property to a HTML string:
C#
TextType = TextType.Html
};
In the example above, the double quote characters in the HTML have to be escaped
using the \ symbol.
In XAML, HTML strings can become unreadable due to additionally escaping the < and
> symbols:
XAML
TextType="Html" />
Alternatively, for greater readability the HTML can be inlined in a CDATA section:
XAML
<Label TextType="Html">
<![CDATA[
]]>
</Label>
In this example, the Text property is set to the HTML string that's inlined in the CDATA
section. This works because the Text property is the ContentProperty for the Label
class.
) Important
Displaying HTML in a Label is limited to the HTML tags that are supported by the
underlying platform.
Decorate text
Underline and strikethrough text decorations can be applied to Label objects by setting
the TextDecorations property to one or more TextDecorations enumeration members:
None
Underline
Strikethrough
The following example demonstrates setting the TextDecorations property:
XAML
C#
7 Note
Text decorations can also be applied to Span instances. For more information about
the Span class, see Use formatted text.
Transform text
A Label can transform the casing of its text, stored in the Text property, by setting the
TextTransform property to a value of the TextTransform enumeration. This enumeration
has four values:
XAML
TextTransform="Uppercase" />
7 Note
FontAutoScalingEnabled , of type bool , defines whether the text will reflect scaling
preferences set in the operating system. The default value of this property is true .
FontFamily , of type string , defines the font family.
FontSize , of type double , defines the font size.
LineHeight , of type double , specifies the multiplier to apply to the default line
Text , of type string , defines the text displayed as the content of the Span.
TextColor , of type Color, defines the color of the displayed text.
TextDecorations , of type TextDecorations , specifies the text decorations
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
XAML
<Label LineBreakMode="WordWrap">
<Label.FormattedText>
<FormattedString>
<Span.GestureRecognizers>
</Span.GestureRecognizers>
</Span>
</FormattedString>
</Label.FormattedText>
</Label>
C#
formattedString.Spans.Add(span);
The following screenshot shows the resulting Label that contains three Span objects:
A Span can also respond to any gestures that are added to the span's
GestureRecognizers collection. For example, a TapGestureRecognizer has been added to
the second Span in the above examples. Therefore, when this Span is tapped the
TapGestureRecognizer will respond by executing the ICommand defined by the Command
property. For more information about tap gesture recognition, see Recognize a tap
gesture.
Create a hyperlink
The text displayed by Label and Span instances can be turned into hyperlinks with the
following approach:
The following example, shows a Label whose content is set from multiple Span objects:
XAML
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="here"
TextColor="Blue"
TextDecorations="Underline">
<Span.GestureRecognizers>
CommandParameter="https://learn.microsoft.com/dotnet/maui/" />
</Span.GestureRecognizers>
</Span>
</FormattedString>
</Label.FormattedText>
</Label>
In this example, the first and third Span instances contain text, while the second Span
represents a tappable hyperlink. It has its text color set to blue, and has an underline
text decoration. This creates the appearance of a hyperlink, as shown in the following
screenshot:
When the hyperlink is tapped, the TapGestureRecognizer will respond by executing the
ICommand defined by its Command property. In addition, the URL specified by the
CommandParameter property will be passed to the ICommand as a parameter.
The code-behind for the XAML page contains the TapCommand implementation:
C#
using System.Windows.Input;
public MainPage()
InitializeComponent();
BindingContext = this;
effect is that when the hyperlink is tapped on the page, a web browser appears and the
URL associated with the hyperlink is navigated to.
C#
BindableProperty.Create(nameof(Url), typeof(string),
typeof(HyperlinkSpan), null);
public HyperlinkSpan()
TextDecorations = TextDecorations.Underline;
TextColor = Colors.Blue;
GestureRecognizers.Add(new TapGestureRecognizer
});
The HyperlinkSpan class defines a Url property, and associated BindableProperty, and
the constructor sets the hyperlink appearance and the TapGestureRecognizer that will
respond when the hyperlink is tapped. When a HyperlinkSpan is tapped, the
TapGestureRecognizer will respond by executing the Launcher.OpenAsync method to
open the URL, specified by the Url property, in a web browser.
The HyperlinkSpan class can be consumed by adding an instance of the class to the
XAML:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:HyperlinkDemo"
x:Class="HyperlinkDemo.MainPage">
<StackLayout>
...
<Label>
<Label.FormattedText>
<FormattedString>
<local:HyperlinkSpan Text="here"
Url="https://learn.microsoft.com/dotnet/" />
</FormattedString>
</Label.FormattedText>
</Label>
</StackLayout>
</ContentPage>
Map
Article • 04/03/2023 • 26 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Map control is a cross-platform view for
displaying and annotating maps. The Map control uses the native map control on each
platform, and is provided by the Microsoft.Maui.Controls.Maps NuGet package .
) Important
The Map control isn't supported on Windows due to lack of a map control in
WinUI. However, you can still launch the native map app from your .NET MAUI app
on WinUI, to display a map at a specific location or to perform navigation. For more
information, see Launch the native map app.
Setup
The Map control uses the native map control on each platform. This provides a fast,
familiar maps experience for users, but means that some configuration steps are needed
to adhere to each platforms API requirements.
Map initialization
The Map control is provided by the Microsoft.Maui.Controls.Maps NuGet package ,
which should be added to your .NET MAUI app project.
After installing the NuGet package, it must be initialized in your app by calling the
UseMauiMap method on the MauiAppBuilder object in the CreateMauiApp method of your
MauiProgram class:
C#
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.UseMauiMaps();
return builder.Build();
Once the NuGet package has been added and initialized, Map APIs can be used in your
project.
Platform configuration
Additional configuration is required on Android before the map will display. In addition,
on iOS, Android, and Mac Catalyst, accessing the user's location requires location
permissions to have been granted to your app.
For more information, see Choosing the location services authorization to request on
developer.apple.com.
The XML representation for these keys in Info.plist is shown below. You should update
the string values to reflect how your app is using the location information:
XML
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Can we use your location when your app is being used?</string>
A prompt is then displayed when your app attempts to access the user's location,
requesting access:
Android
The configuration process for displaying and interacting with a map on Android is to:
1. Get a Google Maps API key and add it to your app manifest.
2. Specify the Google Play services version number in the manifest.
3. [optional] Specify location permissions in the manifest.
4. [optional] Specify the WRITE_EXTERNAL_STORAGE permission in the manifest.
To use the Map control on Android you must generate an API key, which will be
consumed by the Google Maps SDK on which the Map control relies on Android. To
do this, follow the instructions in Set up in the Google Cloud Console and Use API
Keys on developers.google.com.
Once you've obtained an API key it must be added within the <application> element of
your Platforms/Android/AndroidManifest.xml file, by specifying it as the value of the
com.google.android.geo.API_KEY metadata:
XML
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="PASTE-YOUR-API-KEY-HERE" />
</application>
This embeds the API key into the manifest. Without a valid API key the Map control will
display a blank grid.
7 Note
key. A key with this name can be used to authenticate to multiple Google Maps-
based APIs on Android. For backwards compatibility, the
com.google.android.maps.v2.API_KEY metadata name can be used, but only allows
authentication to the Android Maps API v2. An app can only specify one of the API
key metadata names.
XML
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
This embeds the version of Google Play services that the app was compiled with, into
the manifest.
If your app needs to access the user's location, you must request permission by adding
the ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permissions (or both) to the
manifest, as a child of the <manifest> element:
XML
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
/>
</manifest>
The ACCESS_COARSE_LOCATION permission allows the API to use WiFi or mobile data, or
both, to determine the device's location. The ACCESS_FINE_LOCATION permissions allows
the API to use the Global Positioning System (GPS), WiFi, or mobile data to determine a
precise a location as possible.
A prompt is then displayed when your app attempts to access the user's location,
requesting access:
element:
XML
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Map control
The Map class defines the following properties that control map appearance and
behavior:
IsShowingUser , of type bool , indicates whether the map is showing the user's
current location.
ItemsSource , of type IEnumerable , which specifies the collection of IEnumerable
pin items to be displayed.
ItemTemplate , of type DataTemplate, which specifies the DataTemplate to apply to
map.
IsZoomEnabled , of type bool , determines whether the map is allowed to zoom.
MapElements , of type IList<MapElement> , represents the list of elements on the
VisibleRegion , of type MapSpan , returns the currently displayed region of the map.
These properties, with the exception of the MapElements , Pins , and VisibleRegion
properties, are backed by BindableProperty objects, which mean they can be targets of
data bindings.
The Map class also defines a MapClicked event that's fired when the map is tapped. The
MapClickedEventArgs object that accompanies the event has a single property named
Location , of type Location . When the event is fired, the Location property is set to the
map location that was tapped. For information about the Location class, see Location
and distance.
XAML
<ContentPage ...
xmlns:maps="clr-
namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps
">
</ContentPage>
7 Note
In XAML, an xmlns namespace definition should be added for the Map control.
While this isn't required, it prevents a collision between the Polygon and Polyline
types, which exist in both the Microsoft.Maui.Controls.Maps and
Microsoft.Maui.Controls.Shapes namespaces.
C#
namespace WorkingWithMaps
public MapTypesPageCode()
Content = map;
This example calls the default Map constructor, which centers the map on Maui, Hawaii::
Alternatively, a MapSpan argument can be passed to a Map constructor to set the center
point and zoom level of the map when it's loaded. For more information, see Display a
specific location on a map.
) Important
By default, a Map will display a street map if the MapType property is undefined.
Alternatively, the MapType property can be set to one of the MapType enumeration
members:
XAML
C#
MapType = MapType.Satellite
};
XAML
<ContentPage ...
xmlns:maps="clr-
namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps
"
xmlns:sensors="clr-
namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials"
>
<maps:Map>
<x:Arguments>
<MapSpan>
<x:Arguments>
<sensors:Location>
<x:Arguments>
<x:Double>36.9628066</x:Double>
<x:Double>-122.0194722</x:Double>
</x:Arguments>
</sensors:Location>
<x:Double>0.01</x:Double>
<x:Double>0.01</x:Double>
</x:Arguments>
</MapSpan>
</x:Arguments>
</maps:Map>
</ContentPage>
C#
using Microsoft.Maui.Maps;
...
This example creates a Map object that shows the region that is specified by the
MapSpan object. The MapSpan object is centered on the latitude and longitude
represented by a Location object, and spans 0.01 latitude and 0.01 longitude degrees.
For information about the Location class, see Location and distance. For information
about passing arguments in XAML, see Pass arguments in XAML.
The result is that when the map is displayed, it's centered on a specific location, and
spans a specific number of latitude and longitude degrees:
Create a MapSpan object
There are a number of approaches for creating MapSpan objects. A common approach is
supply the required arguments to the MapSpan constructor. These are a latitude and
longitude represented by a Location object, and double values that represent the
degrees of latitude and longitude that are spanned by the MapSpan . For information
about the Location class, see Location and distance.
Alternatively, there are three methods in the MapSpan class that return new MapSpan
objects:
1. ClampLatitude returns a MapSpan with the same LongitudeDegrees as the method's
class instance, and a radius defined by its north and south arguments.
2. FromCenterAndRadius returns a MapSpan that is defined by its Location and
Distance arguments.
3. WithZoom returns a MapSpan with the same center as the method's class instance,
but with a radius multiplied by its double argument.
For information about the Distance struct, see Location and distance.
Once a MapSpan has been created, the following properties can be accessed to retrieve
data about it:
Center , of type Location , which represents the location in the geographical center
of the MapSpan .
LatitudeDegrees , of type double , which represents the degrees of latitude that are
spanned by the MapSpan .
LongitudeDegrees , of type double , which represents the degrees of longitude that
are spanned by the MapSpan .
Radius , of type Distance , which represents the MapSpan radius.
The following code shows an example of moving the displayed region on a map:
C#
using Microsoft.Maui.Maps;
using Microsoft.Maui.Controls.Maps.Map;
...
map.MoveToRegion(mapSpan);
C#
if (map.VisibleRegion != null)
In this example, the MoveToRegion method is called with a MapSpan argument that
specifies the current location of the map, via the Map.VisibleRegion property, and the
zoom level as degrees of latitude and longitude. The overall result is that the zoom level
of the map is changed, but its location isn't. An alternative approach for implementing
zoom on a map is to use the MapSpan.WithZoom method to control the zoom factor.
) Important
Zooming a map, whether via the map UI or programatically, requires that the
Map.IsZoomEnabled property is true . For more information about this property, see
Disable zoom.
7 Note
XAML
<maps:Map IsTrafficEnabled="true" />
C#
IsTrafficEnabled = true
};
Disable scroll
The Map class defines a IsScrollEnabled property of type bool . By default this property
is true , which indicates that the map is allowed to scroll. When this property is set to
false , the map will not scroll:
XAML
C#
IsScrollEnabled = false
};
Disable zoom
The Map class defines a IsZoomEnabled property of type bool . By default this property is
true , which indicates that zoom can be performed on the map. When this property is
XAML
IsZoomEnabled = false
};
XAML
C#
IsShowingUser = true
};
) Important
Accessing the user's location requires location permissions to have been granted to
the application. For more information, see Platform configuration.
Map clicks
The Map class defines a MapClicked event that's fired when the map is tapped. The
MapClickedEventArgs object that accompanies the event has a single property named
Location , of type Location . When the event is fired, the Location property is set to the
map location that was tapped. For information about the Location class, see Location
and distance.
The following code example shows an event handler for the MapClicked event:
C#
void OnMapClicked(object sender, MapClickedEventArgs e)
System.Diagnostics.Debug.WriteLine($"MapClick: {e.Location.Latitude},
{e.Location.Longitude}");
In this example, the OnMapClicked event handler outputs the latitude and longitude that
represents the tapped map location. The event handler must be registered with the
MapClicked event:
XAML
C#
map.MapClicked += OnMapClicked;
Location
The Location class encapsulates a location stored as latitude and longitude values. This
class defines the following properties:
Location , in meters.
Altitude , of type double? , which represents the altitude in meters in a reference
degrees.
Longitude , of type double , which represents the longitude of the location in
decimal degrees.
Speed , of type double? , which represents the speed in meters per second.
Timestamp , of type DateTimeOffset , which represents the timestamp when the
Location , in meters.
Location objects are created with one of the Location constructor overloads, which
typically require at a minimum latitude and longitude arguments specified as double
values:
C#
When creating a Location object, the latitude value will be clamped between -90.0 and
90.0, and the longitude value will be clamped between -180.0 and 180.0.
7 Note
The Location class also has CalculateDistance methods that calculate the distance
between two locations.
Distance
The Distance struct encapsulates a distance stored as a double value, which represents
the distance in meters. This struct defines three read-only properties:
the Distance .
Miles , of type double , which represents the distance in miles that's spanned by
the Distance .
Distance objects can be created with the Distance constructor, which requires a meters
C#
C#
Pins
The Map control allows locations to be marked with Pin objects. A Pin is a map marker
that opens an information window when tapped:
When a Pin object is added to the Map.Pins collection, the pin is rendered on the map.
Address , of type string , which typically represents the address for the pin
location. However, it can be any string content, not just an address.
Label, of type string , which typically represents the pin title.
Location , of type Location , which represents the latitude and longitude of the pin.
Type , of type PinType , which represents the type of pin.
These properties are backed by BindableProperty objects, which means a Pin can be
the target of data bindings. For more information about data binding Pin objects, see
Display a pin collection.
In addition, the Pin class defines MarkerClicked and InfoWindowClicked events. The
MarkerClicked event is fired when a pin is tapped, and the InfoWindowClicked event is
fired when the information window is tapped. The PinClickedEventArgs object that
accompanies both events has a single HideInfoWindow property, of type bool .
Display a pin
A Pin can be added to a Map in XAML:
XAML
<ContentPage ...
xmlns:maps="clr-
namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps
"
xmlns:sensors="clr-
namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials"
>
<maps:Map x:Name="map">
<x:Arguments>
<MapSpan>
<x:Arguments>
<sensors:Location>
<x:Arguments>
<x:Double>36.9628066</x:Double>
<x:Double>-122.0194722</x:Double>
</x:Arguments>
</sensors:Location>
<x:Double>0.01</x:Double>
<x:Double>0.01</x:Double>
</x:Arguments>
</MapSpan>
</x:Arguments>
<maps:Map.Pins>
Type="Place">
<maps:Pin.Location>
<sensors:Location>
<x:Arguments>
<x:Double>36.9628066</x:Double>
<x:Double>-122.0194722</x:Double>
</x:Arguments>
</sensors:Location>
</maps:Pin.Location>
</maps:Pin>
</maps:Map.Pins>
</maps:Map>
</ContentPage>
This XAML creates a Map object that shows the region that is specified by the MapSpan
object. The MapSpan object is centered on the latitude and longitude represented by a
Location object, which extends 0.01 latitude and longitude degrees. A Pin object is
added to the Map.Pins collection, and drawn on the Map at the location specified by its
Location property. For information about the Location class, see Location and distance.
For information about passing arguments in XAML to objects that lack default
constructors, see Pass arguments in XAML.
C#
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
...
...
};
Type = PinType.Place,
};
map.Pins.Add(pin);
The Pin class defines a MarkerClicked event, which is fired when a Pin is tapped. It's
not necessary to handle this event to display the information window. Instead, this event
should be handled when there's a requirement to be notified that a specific pin has
been tapped.
The Pin class also defines a InfoWindowClicked event that's fired when an information
window is tapped. This event should be handled when there's a requirement to be
notified that a specific information window has been tapped.
C#
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
...
Label = "Boardwalk",
Type = PinType.Place
};
args.HideInfoWindow = true;
};
Label = "Wharf",
Type = PinType.Place
};
Pin types
Pin objects include a Type property, of type PinType , which represents the type of pin.
) Important
The ItemTemplate property takes precedence when both the ItemTemplate and
ItemTemplateSelector properties are set.
A Map can be populated with pins by using data binding to bind its ItemsSource
property to an IEnumerable collection:
XAML
<ContentPage ...
xmlns:maps="clr-
namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps
">
<Grid>
...
<maps:Map x:Name="map"
ItemsSource="{Binding Positions}">
<maps:Map.ItemTemplate>
<DataTemplate>
Address="{Binding Address}"
</DataTemplate>
</maps:Map.ItemTemplate>
</maps:Map>
...
</Grid>
</ContentPage>
The ItemsSource property data binds to the Positions property of the connected
viewmodel, which returns an ObservableCollection of Position objects, which is a
custom type. Each Position object defines Address and Description properties, of type
string , and a Location property, of type Location .
The appearance of each item in the IEnumerable collection is defined by setting the
ItemTemplate property to a DataTemplate that contains a Pin object that data binds to
appropriate properties.
The following screenshot shows a Map displaying a Pin collection using data binding:
Choose item appearance at runtime
The appearance of each item in the IEnumerable collection can be chosen at runtime,
based on the item value, by setting the ItemTemplateSelector property to a
DataTemplateSelector:
XAML
<ContentPage ...
xmlns:templates="clr-namespace:WorkingWithMaps.Templates"
xmlns:maps="clr-
namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps
">
<ContentPage.Resources>
<templates:MapItemTemplateSelector x:Key="MapItemTemplateSelector">
<templates:MapItemTemplateSelector.DefaultTemplate>
<DataTemplate>
Address="{Binding Address}"
</DataTemplate>
</templates:MapItemTemplateSelector.DefaultTemplate>
<templates:MapItemTemplateSelector.SanFranTemplate>
<DataTemplate>
Address="{Binding Address}"
Label="Xamarin!" />
</DataTemplate>
</templates:MapItemTemplateSelector.SanFranTemplate>
</templates:MapItemTemplateSelector>
</ContentPage.Resources>
<Grid>
...
<maps:Map x:Name="map"
ItemsSource="{Binding Positions}"
ItemTemplateSelector="{StaticResource
MapItemTemplateSelector}">
...
</Grid>
</ContentPage>
C#
using WorkingWithMaps.Models;
namespace WorkingWithMaps.Templates;
7 Note
A use case for this functionality is binding properties of sub-classed Pin objects to
different properties, based on the Pin sub-type.
For more information about data template selectors, see Create a DataTemplateSelector.
A Polygon is a fully enclosed shape that can have a stroke and fill color. A Polyline is a
line that does not fully enclose an area. A Circle highlights a circular area of the map:
The Polygon , Polyline , and Circle classes derive from the MapElement class, which
exposes the following bindable properties:
In addition, the Polygon and Polyline classes both define a GeoPath property, which is
a list of Location objects that specify the points of the shape.
Center is a Location object that defines the center of the circle, in latitude and
longitude.
Radius is a Distance object that defines the radius of the circle in meters,
kilometers, or miles.
FillColor is a Color property that determines the color within the circle perimeter.
Create a polygon
A Polygon object can be added to a map by instantiating it and adding it to the map's
MapElements collection:
XAML
<ContentPage ...
xmlns:maps="clr-
namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps
"
xmlns:sensors="clr-
namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials"
>
<maps:Map>
<maps:Map.MapElements>
<maps:Polygon StrokeColor="#FF9900"
StrokeWidth="8"
FillColor="#88FF9900">
<maps:Polygon.Geopath>
<sensors:Location>
<x:Arguments>
<x:Double>47.6458676</x:Double>
<x:Double>-122.1356007</x:Double>
</x:Arguments>
</sensors:Location>
<sensors:Location>
<x:Arguments>
<x:Double>47.6458097</x:Double>
<x:Double>-122.142789</x:Double>
</x:Arguments>
</sensors:Location>
...
</maps:Polygon.Geopath>
</maps:Polygon>
</maps:Map.MapElements>
</maps:Map>
</ContentPage>
C#
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
...
// Instantiate a polygon
StrokeWidth = 8,
StrokeColor = Color.FromArgb("#1BA1E2"),
FillColor = Color.FromArgb("#881BA1E2"),
Geopath =
...
};
map.MapElements.Add(polygon);
The StrokeColor and StrokeWidth properties are specified to set the polygon's outline.
In this example, the FillColor property value matches the StrokeColor property value
but has an alpha value specified to make it transparent, allowing the underlying map to
be visible through the shape. The GeoPath property contains a list of Location objects
defining the geographic coordinates of the polygon points. A Polygon object is
rendered on the map once it has been added to the MapElements collection of the Map.
7 Note
A Polygon is a fully enclosed shape. The first and last points will automatically be
connected if they do not match.
Create a polyline
A Polyline object can be added to a map by instantiating it and adding it to the map's
MapElements collection:
XAML
<ContentPage ...
xmlns:maps="clr-
namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps
"
xmlns:sensors="clr-
namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials"
>
<maps:Map>
<maps:Map.MapElements>
<maps:Polyline StrokeColor="Black"
StrokeWidth="12">
<maps:Polyline.Geopath>
<sensors:Location>
<x:Arguments>
<x:Double>47.6381401</x:Double>
<x:Double>-122.1317367</x:Double>
</x:Arguments>
</sensors:Location>
<sensors:Location>
<x:Arguments>
<x:Double>47.6381473</x:Double>
<x:Double>-122.1350841</x:Double>
</x:Arguments>
</sensors:Location>
...
</maps:Polyline.Geopath>
</maps:Polyline>
</maps:Map.MapElements>
</maps:Map>
</ContentPage>
C#
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
...
// instantiate a polyline
StrokeColor = Colors.Blue,
StrokeWidth = 12,
Geopath =
...
};
map.MapElements.Add(polyline);
The StrokeColor and StrokeWidth properties are specified to set the line appearance.
The GeoPath property contains a list of Location objects defining the geographic
coordinates of the polyline points. A Polyline object is rendered on the map once it has
been added to the MapElements collection of the Map.
Create a circle
A Circle object can be added to a map by instantiating it and adding it to the map's
MapElements collection:
XAML
<ContentPage ...
xmlns:maps="clr-
namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps
"
xmlns:sensors="clr-
namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials"
>
<maps:Map>
<maps:Map.MapElements>
<maps:Circle StrokeColor="#88FF0000"
StrokeWidth="8"
FillColor="#88FFC0CB">
<maps:Circle.Center>
<sensors:Location>
<x:Arguments>
<x:Double>37.79752</x:Double>
<x:Double>-122.40183</x:Double>
</x:Arguments>
</sensors:Location>
</maps:Circle.Center>
<maps:Circle.Radius>
<Distance>
<x:Arguments>
<x:Double>250</x:Double>
</x:Arguments>
</Distance>
</maps:Circle.Radius>
</maps:Circle>
</maps:Map.MapElements>
</maps:Map>
</ContentPage>
C#
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
// Instantiate a Circle
StrokeWidth = 8,
FillColor = Color.FromArgb("#88FFC0CB")
};
map.MapElements.Add(circle);
The location of the Circle on the Map is determined by the value of the Center and
Radius properties. The Center property defines the center of the circle, in latitude and
longitude, while the Radius property defines the radius of the circle in meters. The
StrokeColor and StrokeWidth properties are specified to set the circle's outline. The
FillColor property value specifies the color within the circle perimeter. In this example,
both of the color values specify an alpha channel, allowing the underlying map to be
visible through the circle. The Circle object is rendered on the map once it has been
added to the MapElements collection of the Map.
7 Note
The GeographyUtils class has a ToCircumferencePositions extension method that
converts a Circle object (that defines Center and Radius property values) to a list
of Location objects that make up the latitude and longitude coordinates of the
circle perimeter.
7 Note
An alternative to using the Launcher class is to use Map class from the
Microsoft.Maui.ApplicationModel namespace. For more information, see Map.
The maps app on each platform uses a unique custom URI scheme. For information
about the maps URI scheme on iOS, see Map Links on developer.apple.com. For
information about the maps URI scheme on Android, see Maps Developer Guide and
Google Maps Intents for Android on developers.android.com. For information about
the maps URI scheme on Windows, see Launch the Windows Maps app.
if (DeviceInfo.Current.Platform == DevicePlatform.iOS ||
DeviceInfo.Current.Platform == DevicePlatform.MacCatalyst)
//
https://developer.apple.com/library/ios/featuredarticles/iPhoneURLScheme_Ref
erence/MapLinks/MapLinks.html
await Launcher.OpenAsync("http://maps.apple.com/?
q=394+Pacific+Ave+San+Francisco+CA");
await Launcher.OpenAsync("geo:0,0?q=394+Pacific+Ave+San+Francisco+CA");
This example code results in the native map app being launched on each platform, with
the map centered on a pin representing the specified location.
C#
if (DeviceInfo.Current.Platform == DevicePlatform.iOS ||
DeviceInfo.Current.Platform == DevicePlatform.MacCatalyst)
//
https://developer.apple.com/library/ios/featuredarticles/iPhoneURLScheme_Ref
erence/MapLinks/MapLinks.html
await Launcher.OpenAsync("http://maps.apple.com/?
daddr=San+Francisco,+CA&saddr=cupertino");
// opens the 'task chooser' so the user can pick Maps, Chrome or other
mapping app
await Launcher.OpenAsync("http://maps.google.com/?
daddr=San+Francisco,+CA&saddr=Mountain+View");
This example code results in the native map app being launched on each platform, with
the map centered on a route between the specified locations.
ScrollView
Article • 04/03/2023 • 9 minutes to read
The .NET Multi-platform App UI (.NET MAUI) ScrollView is a view that's capable of
scrolling its content. By default, ScrollView scrolls its content vertically. A ScrollView can
only have a single child, although this can be other layouts.
property.
HorizontalScrollBarVisibility , of type ScrollBarVisibility , represents when the
These properties are backed by BindableProperty objects, with the exception of the
Content property, which means that they can be targets of data bindings and styled.
The Content property is the ContentProperty of the ScrollView class, and therefore does
not need to be explicitly set from XAML.
2 Warning
ScrollView objects should not be nested. In addition, ScrollView objects should not
be nested with other controls that provide scrolling, such as CollectionView,
ListView, and WebView.
A StackLayout will often be the child of a ScrollView. In this scenario, the ScrollView
causes the StackLayout to be as tall as the sum of the heights of its children. Then the
ScrollView can determine the amount that its content can be scrolled. For more
information about the StackLayout, see StackLayout.
U Caution
which could be zero. While .NET MAUI protects against this eventuality, it's best to
avoid code that suggests something you don't want to happen.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ScrollViewDemos"
x:Class="ScrollViewDemos.Views.XAML.ColorListPage"
Title="ScrollView demo">
<ScrollView Margin="20">
<StackLayout BindableLayout.ItemsSource="{x:Static
local:NamedColor.All}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
WidthRequest="32"
VerticalOptions="Center" />
FontSize="24"
VerticalOptions="Center" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage>
In this example, the ScrollView has its content set to a StackLayout that uses a bindable
layout to display the Colors fields defined by .NET MAUI. By default, a ScrollView scrolls
vertically, which reveals more content:
C#
public ColorListPage()
HeightRequest = 32,
WidthRequest = 32,
VerticalOptions = LayoutOptions.Center
};
boxView.SetBinding(BoxView.ColorProperty, "Color");
FontSize = 24,
VerticalOptions = LayoutOptions.Center
};
label.SetBinding(Label.TextProperty, "FriendlyName");
Orientation = StackOrientation.Horizontal
};
horizontalStackLayout.Add(boxView);
horizontalStackLayout.Add(label);
return horizontalStackLayout;
});
BindableLayout.SetItemsSource(stackLayout, NamedColor.All);
BindableLayout.SetItemTemplate(stackLayout, dataTemplate);
Content = stackLayout
};
Content = scrollView;
A ScrollView will often be the child of a Grid. A ScrollView requires a specific height to
compute the difference between the height of its content and its own height, with the
difference being the amount that the ScrollView can scroll its content. When a
ScrollView is the child of a Grid, it doesn't receive a specific height. The Grid wants the
ScrollView to be as short as possible, which is either the height of the ScrollView
contents or zero. To handle this scenario, the RowDefinition of the Grid row that
contains the ScrollView should be set to * . This will cause the Grid to give the
ScrollView all the extra space not required by the other children, and the ScrollView will
then have a specific height.
The following XAML example has a ScrollView as a child layout to a Grid:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScrollViewDemos.Views.XAML.BlackCatPage"
<Grid Margin="20"
RowDefinitions="Auto,*,Auto">
FontSize="14"
FontAttributes="Bold"
HorizontalOptions="Center" />
<ScrollView x:Name="scrollView"
Grid.Row="1"
VerticalOptions="FillAndExpand"
Scrolled="OnScrollViewScrolled">
<StackLayout>
</StackLayout>
</ScrollView>
<Button Grid.Row="2"
Text="Scroll to end"
Clicked="OnButtonClicked" />
</Grid>
</ContentPage>
In this example, the root layout is a Grid that has a Label, ScrollView, and Button as its
children. The ScrollView has a StackLayout as its content, with the StackLayout
containing multiple Label objects. This arrangement ensures that the first Label is always
on-screen, while text displayed by the other Label objects can be scrolled:
The equivalent C# code is:
C#
public BlackCatPage()
};
scrollView.Content = stackLayout;
// ...
RowDefinitions =
};
grid.Add(titleLabel);
grid.Add(scrollView, 0, 1);
grid.Add(button, 0, 2);
Content = grid;
Orientation
ScrollView has an Orientation property, which represents the scrolling direction of the
ScrollView. This property is of type ScrollOrientation , which defines the following
members:
Vertical indicates that the ScrollView will scroll vertically. This member is the
default value of the Orientation property.
Horizontal indicates that the ScrollView will scroll horizontally.
Both indicates that the ScrollView will scroll horizontally and vertically.
Detect scrolling
ScrollView defines a Scrolled event that's raised to indicate that scrolling occurred. The
ScrolledEventArgs object that accompanies the Scrolled event has ScrollX and
) Important
The following XAML example shows a ScrollView that sets an event handler for the
Scrolled event:
XAML
<ScrollView Scrolled="OnScrollViewScrolled">
...
</ScrollView>
C#
scrollView.Scrolled += OnScrollViewScrolled;
In this example, the OnScrollViewScrolled event handler is executed when the Scrolled
event fires:
C#
In this example, the OnScrollViewScrolled event handler outputs the values of the
ScrolledEventArgs object that accompanies the event.
7 Note
The Scrolled event is raised for user initiated scrolls, and for programmatic scrolls.
Scroll programmatically
ScrollView defines two ScrollToAsync methods, that asynchronously scroll the
ScrollView. One of the overloads scrolls to a specified position in the ScrollView, while
the other scrolls a specified element into view. Both overloads have an additional
argument that can be used to indicate whether to animate the scroll.
) Important
C#
The third argument to the ScrollToAsync is the animated argument, which determines
whether a scrolling animation is displayed when programmatically scrolling a ScrollView.
C#
The third argument to the ScrollToAsync is the animated argument, which determines
whether a scrolling animation is displayed when programmatically scrolling a ScrollView.
When scrolling an element into view, the exact position of the element after the scroll
has completed can be set with the second argument, position , of the ScrollToAsync
method. This argument accepts a ScrollToPosition enumeration member:
MakeVisible indicates that the element should be scrolled until it's visible in the
ScrollView.
Start indicates that the element should be scrolled to the start of the ScrollView.
Center indicates that the element should be scrolled to the center of the
ScrollView.
End indicates that the element should be scrolled to the end of the ScrollView.
vertical, scroll bar is visible. The ScrollBarVisibility enumeration defines the following
members:
Default indicates the default scroll bar behavior for the platform, and is the
Always indicates that scroll bars will be visible, even when the content fits in the
view.
Never indicates that scroll bars will not be visible, even if the content doesn't fit in
the view.
Shapes
Article • 04/03/2023 • 6 minutes to read
A .NET Multi-platform App UI (.NET MAUI) Shape is a type of View that enables you to
draw a shape to the screen. Shape objects can be used inside layout classes and most
controls, because the Shape class derives from the View class. .NET MAUI Shapes is
available in the Microsoft.Maui.Controls.Shapes namespace.
Aspect, of type Stretch, describes how the shape fills its allocated space. The
default value of this property is Stretch.None .
Fill, of type Brush, indicates the brush used to paint the shape's interior.
Stroke, of type Brush, indicates the brush used to paint the shape's outline.
StrokeDashArray, of type DoubleCollection , which represents a collection of
double values that indicate the pattern of dashes and gaps that are used to outline
a shape.
StrokeDashOffset, of type double , specifies the distance within the dash pattern
where a dash begins. The default value of this property is 0.0.
StrokeDashPattern, of type float[] , indicates the pattern of dashes and gaps that
are used when drawing the stroke for a shape.
StrokeLineCap, of type PenLineCap, describes the shape at the start and end of a
line or segment. The default value of this property is PenLineCap.Flat .
StrokeLineJoin, of type PenLineJoin, specifies the type of join that is used at the
vertices of a shape. The default value of this property is PenLineJoin.Miter .
StrokeMiterLimit, of type double , specifies the limit on the ratio of the miter length
to half the StrokeThickness of a shape. The default value of this property is 10.0.
StrokeThickness, of type double , indicates the width of the shape outline. The
default value of this property is 1.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
.NET MAUI defines a number of objects that derive from the Shape class. These are
Ellipse, Line, Path, Polygon, Polyline, Rectangle, and RoundRectangle.
Paint shapes
Brush objects are used to paint a shapes's Stroke and Fill:
XAML
<Ellipse Fill="DarkBlue"
Stroke="Red"
StrokeThickness="4"
WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Start" />
) Important
Brush objects use a type converter that enables Color values to specified for the
Stroke property.
If you don't specify a Brush object for Stroke, or if you set StrokeThickness to 0, then the
border around the shape is not drawn.
For more information about Brush objects, see Brushes. For more information about
valid Color values, see Colors.
Stretch shapes
Shape objects have an Aspect property, of type Stretch. This property determines how a
Shape object's contents is stretched to fill the Shape object's layout space. A Shape
object's layout space is the amount of space the Shape is allocated by the .NET MAUI
layout system, because of either an explicit WidthRequest and HeightRequest setting or
because of its HorizontalOptions and VerticalOptions settings.
None , which indicates that the content preserves its original size. This is the default
value of the Shape.Aspect property.
Fill, which indicates that the content is resized to fill the destination dimensions.
The aspect ratio is not preserved.
Uniform , which indicates that the content is resized to fit the destination
dimensions, while preserving the aspect ratio. If the aspect ratio of the destination
rectangle differs from the source, the source content is clipped to fit in the
destination dimensions.
XAML
<Path Aspect="Uniform"
Stroke="Yellow"
Fill="Red"
BackgroundColor="LightGray"
HorizontalOptions="Start"
HeightRequest="100"
WidthRequest="100">
<Path.Data>
</Path.Data>
</Path>
In this example, a Path object draws a heart. The Path object's WidthRequest and
HeightRequest properties are set to 100 device-independent units, and its Aspect
property is set to Uniform . As a result, the object's contents are resized to fit the
destination dimensions, while preserving the aspect ratio:
Shape objects also have a StrokeDashOffset property, of type double , which specifies
the distance within the dash pattern where a dash begins. Failure to set this property will
result in the Shape having a solid outline.
Dashed shapes can be drawn by setting both the StrokeDashArray and StrokeDashOffset
properties. The StrokeDashArray property should be set to one or more double values,
with each pair delimited by a single comma and/or one or more spaces. For example,
"0.5 1.0" and "0.5,1.0" are both valid.
XAML
<Rectangle Fill="DarkBlue"
Stroke="Red"
StrokeThickness="4"
StrokeDashArray="1,1"
StrokeDashOffset="6"
WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Start" />
Shape objects have a StrokeLineCap property, of type PenLineCap, that describes the
shape at the start and end of a line, or segment. The PenLineCap enumeration defines
the following members:
Flat , which represents a cap that doesn't extend past the last point of the line.
This is comparable to no line cap, and is the default value of the StrokeLineCap
property.
Square , which represents a rectangle that has a height equal to the line thickness
and a length equal to half the line thickness.
Round , which represents a semicircle that has a diameter equal to the line
thickness.
) Important
The StrokeLineCap property has no effect if you set it on a shape that has no start
or end points. For example, this property has no effect if you set it on an Ellipse, or
Rectangle.
XAML
<Line X1="0"
Y1="20"
X2="300"
Y2="20"
StrokeLineCap="Round"
Stroke="Red"
StrokeThickness="12" />
In this example, the red line is rounded at the start and end of the line:
StrokeLineJoin property.
Bevel , which represents beveled vertices.
7 Note
XAML
Stroke="DarkBlue"
StrokeThickness="20"
StrokeLineJoin="Round" />
In this example, the dark blue polyline has rounded joins at its vertices:
Ellipse
Article • 02/09/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Ellipse class derives from the Shape class,
and can be used to draw ellipses and circles. For information on the properties that the
Ellipse class inherits from the Shape class, see Shapes.
The Ellipse class sets the Aspect property, inherited from the Shape class, to
Stretch.Fill . For more information about the Aspect property, see Stretch shapes.
Create an Ellipse
To draw an ellipse, create an Ellipse object and set its WidthRequest and HeightRequest
properties. To paint the inside of the ellipse, set its Fill property to a Brush-derived
object. To give the ellipse an outline, set its Stroke property to a Brush-derived object.
The StrokeThickness property specifies the thickness of the ellipse outline. For more
information about Brush objects, see Brushes.
To draw a circle, make the WidthRequest and HeightRequest properties of the Ellipse
object equal.
XAML
<Ellipse Fill="Red"
WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Start" />
In this example, a red filled ellipse with dimensions 150x50 (device-independent units) is
drawn:
<Ellipse Stroke="Red"
StrokeThickness="4"
WidthRequest="150"
HeightRequest="150"
HorizontalOptions="Start" />
For information about drawing a dashed ellipse, see Draw dashed shapes.
Fill rules
Article • 02/09/2023 • 4 minutes to read
Several .NET Multi-platform App UI (.NET MAUI) Shapes classes have FillRule
properties, of type FillRule. These include Polygon, Polyline, and GeometryGroup.
The FillRule enumeration defines EvenOdd and Nonzero members. Each member
represents a different rule for determining whether a point is in the fill region of a
shape.
) Important
All shapes are considered closed for the purposes of fill rules.
EvenOdd
The EvenOdd fill rule draws a ray from the point to infinity in any direction and counts
the number of segments within the shape that the ray crosses. If this number is odd, the
point is inside. If this number is even, the point is outside.
The following XAML example creates and renders a composite shape, with the FillRule
defaulting to EvenOdd:
XAML
<Path Stroke="Black"
Fill="#CCCCFF"
Aspect="Uniform"
HorizontalOptions="Start">
<Path.Data>
<GeometryGroup>
<EllipseGeometry RadiusX="50"
RadiusY="50"
Center="75,75" />
<EllipseGeometry RadiusX="70"
RadiusY="70"
Center="75,75" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="75,75" />
<EllipseGeometry RadiusX="120"
RadiusY="120"
Center="75,75" />
</GeometryGroup>
</Path.Data>
</Path>
In the composite shape, notice that the center and third rings are not filled. This is
because a ray drawn from any point within either of those two rings passes through an
even number of segments:
In the image above, the red circles represent points, and the lines represent arbitrary
rays. For the upper point, the two arbitrary rays each pass through an even number of
line segments. Therefore, the ring the point is in isn't filled. For the lower point, the two
arbitrary rays each pass through an odd number of line segments. Therefore, the ring
the point is in is filled.
Nonzero
The Nonzero fill rule draws a ray from the point to infinity in any direction and then
examines the places where a segment of the shape crosses the ray. Starting with a count
of zero, the count is incremented each time a segment crosses the ray from left to right
and decremented each time a segment crosses the ray from right to left. After counting
the crossings, if the result is zero then the point is outside the polygon. Otherwise, it's
inside.
The following XAML example creates and renders a composite shape, with the FillRule
set to Nonzero:
XAML
<Path Stroke="Black"
Fill="#CCCCFF"
Aspect="Uniform"
HorizontalOptions="Start">
<Path.Data>
<GeometryGroup FillRule="Nonzero">
<EllipseGeometry RadiusX="50"
RadiusY="50"
Center="75,75" />
<EllipseGeometry RadiusX="70"
RadiusY="70"
Center="75,75" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="75,75" />
<EllipseGeometry RadiusX="120"
RadiusY="120"
Center="75,75" />
</GeometryGroup>
</Path.Data>
</Path>
In the composite shape, notice that all rings are filled. This is because all the segments
are running in the same direction, and so a ray drawn from any point will cross one or
more segments and the sum of the crossings will not equal zero:
In the image above the red arrows represent the direction the segments are drawn, and
black arrow represents an arbitrary ray running from a point in the innermost ring.
Starting with a value of zero, for each segment that the ray crosses, a value of one is
added because the segment crosses the ray from left to right.
XAML
<Path Stroke="Black"
Fill="#CCCCFF">
<Path.Data>
<GeometryGroup FillRule="Nonzero">
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="120,120">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="50,50"
IsLargeArc="True"
SweepDirection="CounterClockwise"
Point="140,120" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
<PathFigure StartPoint="120,100">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="70,70"
IsLargeArc="True"
SweepDirection="CounterClockwise"
Point="140,100" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
<PathFigure StartPoint="120,70">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="100,100"
IsLargeArc="True"
SweepDirection="CounterClockwise"
Point="140,70" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
<PathFigure StartPoint="120,300">
<PathFigure.Segments>
<ArcSegment Size="130,130"
IsLargeArc="True"
SweepDirection="Clockwise"
Point="140,300" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryGroup>
</Path.Data>
</Path>
In this example, a series of arc segments are drawn, that aren't closed:
In the image above, the third arc from the center is not filled. This is because the sum of
the values from a given ray crossing the segments in its path is zero:
In the image above, the red circle represents a point, the black lines represent arbitrary
rays that move out from the point in the non-filled region, and the red arrows represent
the direction the segments are drawn. As can be seen, the sum of the values from the
rays crossing the segments is zero:
The arbitrary ray that travels diagonally right crosses two segments that run in
different directions. Therefore, the segments cancel each other out giving a value
of zero.
The arbitrary ray that travels diagonally left crosses a total of six segments.
However, the crossings cancel each other out so that zero is the final sum.
The .NET Multi-platform App UI (.NET MAUI) Geometry class, and the classes that derive
from it, enable you to describe the geometry of a 2D shape. Geometry objects can be
simple, such as rectangles and circles, or composite, created from two or more
geometry objects. In addition, more complex geometries can be created that include
arcs and curves.
The Geometry class is the parent class for several classes that define different categories
of geometries:
7 Note
The Geometry and Shape classes seem similar, in that they both describe 2D shapes, but
have an important difference. The Geometry class derives from the BindableObject class,
while the Shape class derives from the View class. Therefore, Shape objects can render
themselves and participate in the layout system, while Geometry objects cannot. While
Shape objects are more readily usable than Geometry objects, Geometry objects are
more versatile. While a Shape object is used to render 2D graphics, a Geometry object
can be used to define the geometric region for 2D graphics, and define a region for
clipping.
The following classes have properties that can be set to Geometry objects:
The Path class uses a Geometry to describe its contents. You can render a
Geometry by setting the Path.Data property to a Geometry object, and setting the
Path object's Fill and Stroke properties.
The VisualElement class has a Clip property, of type Geometry, that defines the
outline of the contents of an element. When the Clip property is set to a Geometry
object, only the area that is within the region of the Geometry will be visible. For
more information, see Clip with a Geometry.
The classes that derive from the Geometry class can be grouped into three categories:
simple geometries, path geometries, and composite geometries.
Simple geometries
The simple geometry classes are EllipseGeometry, LineGeometry, and
RectangleGeometry. They are used to create basic geometric shapes, such as circles,
lines, and rectangles. These same shapes, as well as more complex shapes, can be
created using a PathGeometry or by combining geometry objects together, but these
classes provide a simpler approach for producing these basic geometric shapes.
EllipseGeometry
An ellipse geometry represents the geometry or an ellipse or circle, and is defined by a
center point, an x-radius, and a y-radius.
Center, of type Point , which represents the center point of the geometry.
RadiusX, of type double , which represents the x-radius value of the geometry. The
default value of this property is 0.0.
RadiusY, of type double , which represents the y-radius value of the geometry. The
default value of this property is 0.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The following example shows how to create and render an EllipseGeometry in a Path
object:
XAML
<Path Fill="Blue"
Stroke="Red">
<Path.Data>
<EllipseGeometry Center="50,50"
RadiusX="50"
RadiusY="50" />
</Path.Data>
</Path>
In this example, the center of the EllipseGeometry is set to (50,50) and the x-radius and
y-radius are both set to 50. This creates a red circle with a diameter of 100 device-
independent units, whose interior is painted blue:
LineGeometry
A line geometry represents the geometry of a line, and is defined by specifying the start
point of the line and the end point.
StartPoint, of type Point , which represents the start point of the line.
EndPoint, of type Point , which represents the end point of the line.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The following example shows how to create and render a LineGeometry in a Path object:
XAML
<Path Stroke="Black">
<Path.Data>
<LineGeometry StartPoint="10,20"
EndPoint="100,130" />
</Path.Data>
</Path>
Setting the Fill property of a Path that renders a LineGeometry will have no effect,
because a line has no interior.
RectangleGeometry
A rectangle geometry represents the geometry of a rectangle or square, and is defined
with a Rect structure that specifies its relative position and its height and width.
The RectangleGeometry class defines the Rect property, of type Rect , which represents
the dimensions of the rectangle. This property is backed by a BindableProperty object,
which means that it can be the target of data bindings, and styled.
The following example shows how to create and render a RectangleGeometry in a Path
object:
XAML
<Path Fill="Blue"
Stroke="Red">
<Path.Data>
</Path>
The position and dimensions of the rectangle are defined by a Rect structure. In this
example, the position is (10,10), the width is 150, and the height is 100 device-
independent units:
Path geometries
A path geometry describes a complex shape that can be composed of arcs, curves,
ellipses, lines, and rectangles.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
For more information about the FillRule enumeration, see .NET MAUI Shapes: Fill rules.
7 Note
All the above classes derive from the abstract PathSegment class.
The segments within a PathFigure are combined into a single geometric shape with the
end point of each segment being the start point of the next segment. The StartPoint
property of a PathFigure specifies the point from which the first segment is drawn. Each
subsequent segment starts at the end point of the previous segment. For example, a
vertical line from 10,50 to 10,150 can be defined by setting the StartPoint property to
10,50 and creating a LineSegment with a Point property setting of 10,150 :
XAML
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,50">
<PathFigure.Segments>
<PathSegmentCollection>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
Create an ArcSegment
An ArcSegment creates an elliptical arc between two points. An elliptical arc is defined
by its start and end points, x- and y-radius, x-axis rotation factor, a value indicating
whether the arc should be greater than 180 degrees, and a value describing the
direction in which the arc is drawn.
Point, of type Point , which represents the endpoint of the elliptical arc. The default
value of this property is (0,0).
Size, of type Size , which represents the x- and y-radius of the arc. The default
value of this property is (0,0).
RotationAngle, of type double , which represents the amount in degrees by which
the ellipse is rotated around the x-axis. The default value of this property is 0.
SweepDirection, of type SweepDirection, which specifies the direction in which the
arc is drawn. The default value of this property is
SweepDirection.CounterClockwise .
IsLargeArc, of type bool , which indicates whether the arc should be greater than
180 degrees. The default value of this property is false .
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
The ArcSegment class does not contain a property for the starting point of the arc.
It only defines the end point of the arc it represents. The start point of the arc is the
current point of the PathFigure to which the ArcSegment is added.
The following example shows how to create and render an ArcSegment in a Path object:
XAML
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="100,50"
RotationAngle="45"
IsLargeArc="True"
SweepDirection="CounterClockwise"
Point="200,100" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
Create a BezierSegment
A BezierSegment creates a cubic Bezier curve between two points. A cubic Bezier curve
is defined by four points: a start point, an end point, and two control points.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
The BezierSegment class does not contain a property for the starting point of the
curve. The start point of the curve is the current point of the PathFigure to which
the BezierSegment is added.
The two control points of a cubic Bezier curve behave like magnets, attracting portions
of what would otherwise be a straight line toward themselves and producing a curve.
The first control point affects the start portion of the curve. The second control point
affects the end portion of the curve. The curve doesn't necessarily pass through either of
the control points. Instead, each control point moves its portion of the line toward itself,
but not through itself.
The following example shows how to create and render a BezierSegment in a Path
object:
XAML
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<BezierSegment Point1="100,0"
Point2="200,200"
Point3="300,10" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
In this example, a cubic Bezier curve is drawn from (10,10) to (300,10). The curve has two
control points at (100,0) and (200,200):
Create a LineSegment
A LineSegment creates a line between two points.
The LineSegment class defines the Point property, of type Point , which represents the
end point of the line segment. The default value of this property is (0,0), and it's backed
by a BindableProperty object, which means that it can be the target of data bindings,
and styled.
7 Note
The LineSegment class does not contain a property for the starting point of the
line. It only defines the end point. The start point of the line is the current point of
the PathFigure to which the LineSegment is added.
The following example shows how to create and render LineSegment objects in a Path
object:
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Start">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True"
StartPoint="10,100">
<PathFigure.Segments>
<PathSegmentCollection>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
In this example, a line segment is drawn from (10,100) to (100,100), and from (100,100)
to (100,50). In addition, the PathFigure is closed because its IsClosed property is set to
true . This results in a triangle being drawn:
Create a PolyBezierSegment
A PolyBezierSegment creates one or more cubic Bezier curves.
The PolyBezierSegment class defines the Points property, of type PointCollection, which
represents the points that define the PolyBezierSegment. A PointCollection is an
ObservableCollection of Point objects. This property is backed by a BindableProperty
object, which means that it can be the target of data bindings, and styled.
7 Note
The PolyBezierSegment class does not contain a property for the starting point of
the curve. The start point of the curve is the current point of the PathFigure to
which the PolyBezierSegment is added.
The following example shows how to create and render a PolyBezierSegment in a Path
object:
XAML
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
In this example, the PolyBezierSegment specifies two cubic Bezier curves. The first curve
is from (10,10) to (150,100) with a control point of (0,0), and another control point of
(100,0). The second curve is from (150,100) to (300,10) with a control point of (150,0)
and another control point of (200,0):
Create a PolyLineSegment
A PolyLineSegment creates one or more line segments.
The PolyLineSegment class defines the Points property, of type PointCollection, which
represents the points that define the PolyLineSegment. A PointCollection is an
ObservableCollection of Point objects. This property is backed by a BindableProperty
object, which means that it can be the target of data bindings, and styled.
7 Note
The PolyLineSegment class does not contain a property for the starting point of
the line. The start point of the line is the current point of the PathFigure to which
the PolyLineSegment is added.
The following example shows how to create and render a PolyLineSegment in a Path
object:
XAML
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
In this example, the PolyLineSegment specifies two lines. The first line is from (10,10) to
(50,10), and the second line is from (50,10) to (50,50):
Create a PolyQuadraticBezierSegment
A PolyQuadraticBezierSegment creates one or more quadratic Bezier curves.
7 Note
The PolyQuadraticBezierSegment class does not contain a property for the starting
point of the curve. The start point of the curve is the current point of the
PathFigure to which the PolyQuadraticBezierSegment is added.
XAML
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<PolyQuadraticBezierSegment Points="100,100
150,50 0,100 15,200" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
In this example, the PolyQuadraticBezierSegment specifies two Bezier curves. The first
curve is from (10,10) to (150,50) with a control point at (100,100). The second curve is
from (100,100) to (15,200) with a control point at (0,100):
Create a QuadraticBezierSegment
A QuadraticBezierSegment creates a quadratic Bezier curve between two points.
Point1, of type Point , which represents the control point of the curve. The default
value of this property is (0,0).
Point2, of type Point , which represents the end point of the curve. The default
value of this property is (0,0).
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
The QuadraticBezierSegment class does not contain a property for the starting
point of the curve. The start point of the curve is the current point of the
PathFigure to which the QuadraticBezierSegment is added.
The following example shows how to create and render a QuadraticBezierSegment in a
Path object:
XAML
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="200,200"
Point2="300,10" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
In this example, a quadratic Bezier curve is drawn from (10,10) to (300,10). The curve has
a control point at (200,200):
XAML
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="10,50">
<PathFigure.Segments>
<BezierSegment Point1="100,0"
Point2="200,200"
Point3="300,100"/>
<ArcSegment Size="50,50"
RotationAngle="45"
IsLargeArc="True"
SweepDirection="Clockwise"
Point="200,100"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
In this example, a BezierSegment is first defined using four points. The example then
adds a LineSegment, which is drawn between the end point of the BezierSegment to the
point specified by the LineSegment. Finally, an ArcSegment is drawn from the end point
of the LineSegment to the point specified by the ArcSegment.
Even more complex geometries can be created by using multiple PathFigure objects
within a PathGeometry. The following example creates a PathGeometry from seven
PathFigure objects, some of which contain multiple PathSegment objects:
XAML
<Path Stroke="Red"
StrokeThickness="12"
StrokeLineJoin="Round">
<Path.Data>
<PathGeometry>
<!-- H -->
<PathFigure StartPoint="0,0">
</PathFigure>
<PathFigure StartPoint="0,50">
</PathFigure>
<PathFigure StartPoint="50,0">
</PathFigure>
<!-- E -->
Point2="60, 60"
Point2="60, 110"
</PathFigure>
<!-- L -->
</PathFigure>
<!-- L -->
</PathFigure>
<!-- O -->
Point="300, 49.9"
IsLargeArc="True" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
In this example, the word "Hello" is drawn using a combination of LineSegment and
BezierSegment objects, along with a single ArcSegment object:
Composite geometries
Composite geometry objects can be created using a GeometryGroup. The
GeometryGroup class creates a composite geometry from one or more Geometry
objects. Any number of Geometry objects can be added to a GeometryGroup.
Children, of type GeometryCollection, which specifies the objects that define the
GeometryGroup. A GeometryCollection is an ObservableCollection of Geometry
objects.
FillRule, of type FillRule, which specifies how the intersecting areas in the
GeometryGroup are combined. The default value of this property is
FillRule.EvenOdd .
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
For more information about the FillRule enumeration, see Fill rules.
To draw a composite geometry, set the required Geometry objects as the children of a
GeometryGroup, and display them with a Path object. The following XAML shows an
example of this:
XAML
<Path Stroke="Green"
StrokeThickness="2"
Fill="Orange">
<Path.Data>
<GeometryGroup>
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="150,150" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="250,150" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="150,250" />
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="250,250" />
</GeometryGroup>
</Path.Data>
</Path>
In this example, four EllipseGeometry objects with identical x-radius and y-radius
coordinates, but with different center coordinates, are combined. This creates four
overlapping circles, whose interiors are filled orange due to the default EvenOdd fill rule:
RoundRectangleGeometry
A round rectangle geometry represents the geometry of a rectangle, or square, with
rounded corners, and is defined by a corner radius and a Rect structure that specifies its
relative position and its height and width.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
XAML
<Path Fill="Blue"
Stroke="Red">
<Path.Data>
<RoundRectangleGeometry CornerRadius="5"
Rect="10,10,150,100" />
</Path.Data>
</Path>
The position and dimensions of the rectangle are defined by a Rect structure. In this
example, the position is (10,10), the width is 150, and the height is 100 device-
independent units. In addition, the rectangle corners are rounded with a radius of 5
device-independent units.
The following example shows how to use a Geometry object as the clip region for an
Image:
XAML
<Image Source="monkeyface.png">
<Image.Clip>
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="180,180" />
</Image.Clip>
</Image>
In this example, an EllipseGeometry with RadiusX and RadiusY values of 100, and a
Center value of (180,180) is set to the Clip property of an Image. Only the part of the
image that is within the area of the ellipse will be displayed:
7 Note
Simple geometries, path geometries, and composite geometries can all be used to
clip VisualElement objects.
Other features
The GeometryHelper class provides the following helper methods:
The .NET Multi-platform App UI (.NET MAUI) Line class derives from the Shape class, and
can be used to draw lines. For information on the properties that the Line class inherits
from the Shape class, see Shapes.
X1, of type double, indicates the x-coordinate of the start point of the line. The
default value of this property is 0.0.
Y1, of type double, indicates the y-coordinate of the start point of the line. The
default value of this property is 0.0.
X2, of type double, indicates the x-coordinate of the end point of the line. The
default value of this property is 0.0.
Y2, of type double, indicates the y-coordinate of the end point of the line. The
default value of this property is 0.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
For information about controlling how line ends are drawn, see Control line ends.
Create a Line
To draw a line, create a Line object and set its X1 and Y1 properties to its start point,
and its X2 and Y2 properties to its end point. In addition, set its Stroke property to a
Brush-derived object because a line without a stroke is invisible. For more information
about Brush objects, see Brushes.
7 Note
Setting the Fill property of a Line has no effect, because a line has no interior.
XAML
<Line X1="40"
Y1="0"
X2="0"
Y2="120"
Stroke="Red" />
Because the X1, Y1, X2, and Y2 properties have default values of 0, it's possible to draw
some lines with minimal syntax:
XAML
<Line Stroke="Red"
X2="200" />
In this example, a horizontal line that's 200 device-independent units long is defined.
Because the other properties are 0 by default, a line is drawn from (0,0) to (200,0).
XAML
<Line X1="40"
Y1="0"
X2="0"
Y2="120"
Stroke="DarkBlue"
StrokeDashArray="1,1"
StrokeDashOffset="6" />
In this example, a dark blue dashed diagonal line is drawn from (40,0) to (0,120):
For more information about drawing a dashed line, see Draw dashed shapes.
Path
Article • 02/09/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Path class derives from the Shape class,
and can be used to draw curves and complex shapes. These curves and shapes are often
described using Geometry objects. For information on the properties that the Path class
inherits from the Shape class, see Shapes.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
Create a Path
To draw a path, create a Path object and set its Data property. There are two techniques
for setting the Data property:
You can set a string value for Data in XAML, using path markup syntax. With this
approach, the Path.Data value is consuming a serialization format for graphics.
Typically, you don't edit this string value by hand after it's created. Instead, you use
design tools to manipulate the data, and export it as a string fragment that's
consumable by the Data property.
You can set the Data property to a Geometry object. This can be a specific
Geometry object, or a GeometryGroup which acts as a container that can combine
multiple geometry objects into a single object.
XAML
<Path Data="M 10,100 L 100,100 100,50Z"
Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Start" />
The Data string begins with the move command, indicated by M , which establishes an
absolute start point for the path. L is the line command, which creates a straight line
from the start point to the specified end point. Z is the close command, which creates a
line that connects the current point to the starting point. The result is a triangle:
For more information about path markup syntax, see Path markup syntax.
The following XAML example shows how to draw a triangle using a PathGeometry
object:
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Start">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True"
StartPoint="10,100">
<PathFigure.Segments>
<PathSegmentCollection>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
In this example, the start point of the triangle is (10,100). A line segment is drawn from
(10,100) to (100,100), and from (100,100) to (100,50). Then the figures first and last
segments are connected, because the PathFigure.IsClosed property is set to true . The
result is a triangle:
.NET Multi-platform App UI (.NET MAUI) path markup syntax enables you to compactly
specify path geometries in XAML.
XAML
<Path Stroke="Black"
Path markup syntax is composed of an optional FillRule value, and one or more figure
descriptions. This syntax can be expressed as: <Path Data=" [fillRule] figureDescription
[figureDescription] * " ... />
In this syntax:
fillRule is an optional FillRule that specifies whether the geometry should use the
EvenOdd or Nonzero fill rule. F0 is used to specify the EvenOdd fill rule, while F1
is used to specify the Nonzero fill rule. For more information about fill rules, see Fill
rules.
figureDescription represents a figure composed of a move command, draw
commands, and an optional close command. A move command specifies the start
point of the figure. Draw commands describe the figure's contents, and the
optional close command closes the figure.
In the example above, the path markup syntax specifies a start point using the move
command ( M ), a series of straight lines using the line command ( L ), and closes the path
with the close command ( Z ).
In path markup syntax, spaces are not required before or after commands. In addition,
two numbers don't have to be separated by a comma or white space, but this can only
be achieved when the string is unambiguous.
Tip
Path markup syntax is compatible with Scalable Vector Graphics (SVG) image path
definitions, and so it can be useful for porting graphics from SVG format.
While path markup syntax is intended for consumption in XAML, it can be converted to
a Geometry object in code by invoking the ConvertFromInvariantString method in the
PathGeometryConverter class:
C#
Move command
The move command specifies the start point of a new figure. The syntax for this
command is: M startPoint or m startPoint.
In this syntax, startPoint is a Point structure that specifies the start point of a new figure.
If you list multiple points after the move command, a line is drawn to those points.
Draw commands
A draw command can consist of several shape commands. The following draw
commands are available:
Line ( L or l ).
Horizontal line ( H or h ).
Vertical line ( V or v ).
Elliptical arc ( A or a ).
Cubic Bezier curve ( C or c ).
Quadratic Bezier curve ( Q or q ).
Smooth cubic Bezier curve ( S or s ).
Smooth quadratic Bezier curve ( T or t ).
In this syntax, endPoint is a Point that represents the end point of the line.
For information about creating a straight line as a PathGeometry object, see Create a
LineSegment.
In this syntax, x is a double that represents the x-coordinate of the end point of the line.
In this syntax, y is a double that represents the y-coordinate of the end point of the line.
In this syntax:
isLargeArcFlag should be set to 1 if the angle of the arc should be 180 degrees or
For information about creating an elliptical arc as a PathGeometry object, see Create an
ArcSegment.
In this syntax:
controlPoint1 is a Point that represents the first control point of the curve, which
determines the starting tangent of the curve.
controlPoint2 is a Point that represents the second control point of the curve,
which determines the ending tangent of the curve.
endPoint is a Point that represents the point to which the curve is drawn.
For information about creating a cubic Bezier curve as a PathGeometry object, see
Create a BezierSegment.
In this syntax:
controlPoint is a Point that represents the control point of the curve, which
determines the starting and ending tangents of the curve.
endPoint is a Point that represents the point to which the curve is drawn.
In this syntax:
controlPoint2 is a Point that represents the second control point of the curve,
which determines the ending tangent of the curve.
endPoint is a Point that represents the point to which the curve is drawn.
The first control point is assumed to be the reflection of the second control point of the
previous command, relative to the current point. If there is no previous command, or the
previous command was not a cubic Bezier curve command or a smooth cubic Bezier
curve command, the first control point is assumed to be coincident with the current
point.
In this syntax, endPoint is a Point that represents the point to which the curve is drawn.
The control point is assumed to be the reflection of the control point of the previous
command relative to the current point. If there is no previous command or if the
previous command was not a quadratic Bezier curve or a smooth quadratic Bezier curve
command, the control point is assumed to be coincident with the current point.
Close command
The close command ends the current figure and creates a line that connects the current
point to the starting point of the figure. Therefore, this command creates a line-join
between the last segment and the first segment of the figure.
Additional values
Instead of a standard numerical value, you can also use the following case-sensitive
special values:
In addition, you may also use case-insensitive scientific notation. Therefore, +1.e17 is a
valid value.
Path transforms
Article • 02/09/2023 • 12 minutes to read
A .NET Multi-platform App UI (.NET MAUI) Transform defines how to transform a Path
object from one coordinate space to another coordinate space. When a transform is
applied to a Path object, it changes how the object is rendered in the UI.
Transforms can be categorized into four general classifications: rotation, scaling, skew,
and translation. .NET MAUI defines a class for each of these transform classifications:
.NET MAUI also provides the following classes for creating more complex
transformations:
All of these classes derive from the Transform class, which defines a Value property of
type Matrix, which represents the current transformation as a Matrix object. This
property is backed by a BindableProperty object, which means that it can be the target
of data bindings, and styled. For more information about the Matrix struct, see
Transform matrix.
To apply a transform to a Path, you create a transform class and set it as the value of the
Path.RenderTransform property.
Rotation transform
A rotate transform rotates a Path object clockwise about a specified point in a 2D x-y
coordinate system.
The RotateTransform class, which derives from the Transform class, defines the following
properties:
Angle, of type double , represents the angle, in degrees, of clockwise rotation. The
default value of this property is 0.0.
CenterX, of type double , represents the x-coordinate of the rotation center point.
The default value of this property is 0.0.
CenterY, of type double , represents the y-coordinate of the rotation center point.
The default value of this property is 0.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The CenterX and CenterY properties specify the point about which the Path object is
rotated. This center point is expressed in the coordinate space of the object that's
transformed. By default, the rotation is applied to (0,0), which is the upper-left corner of
the Path object.
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z">
<Path.RenderTransform>
<RotateTransform CenterX="0"
CenterY="0"
Angle="45" />
</Path.RenderTransform>
</Path>
In this example, the Path object is rotated 45 degrees about its upper-left corner.
Scale transform
A scale transform scales a Path object in the 2D x-y coordinate system.
The ScaleTransform class, which derives from the Transform class, defines the following
properties:
ScaleX, of type double , which represents the x-axis scale factor. The default value
of this property is 1.0.
ScaleY, of type double , which represents the y-axis scale factor. The default value
of this property is 1.0.
CenterX, of type double , which represents the x-coordinate of the center point of
this transform. The default value of this property is 0.0.
CenterY, of type double , which represents the y-coordinate of the center point of
this transform. The default value of this property is 0.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The value of ScaleX and ScaleY have a huge impact on the resulting scaling:
Values between 0 and 1 decrease the width and height of the scaled object.
Values greater than 1 increase the width and height of the scaled object.
Values of 1 indicate that the object is not scaled.
Negative values flip the scale object horizontally and vertically.
Values between 0 and -1 flip the scale object and decrease its width and height.
Values less than -1 flip the object and increase its width and height.
Values of -1 flip the scaled object but do not change its horizontal or vertical size.
The CenterX and CenterY properties specify the point about which the Path object is
scaled. This center point is expressed in the coordinate space of the object that's
transformed. By default, scaling is applied to (0,0), which is the upper-left corner of the
Path object. This has the effect of moving the Path object and making it appear larger,
because when you apply a transform you change the coordinate space in which the Path
object resides.
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z">
<Path.RenderTransform>
<ScaleTransform CenterX="0"
CenterY="0"
ScaleX="1.5"
ScaleY="1.5" />
</Path.RenderTransform>
</Path>
In this example, the Path object is scaled to 1.5 times the size.
Skew transform
A skew transform skews a Path object in the 2D x-y coordinate system, and is useful for
creating the illusion of 3D depth in a 2D object.
The SkewTransform class, which derives from the Transform class, defines the following
properties:
AngleX, of type double , which represents the x-axis skew angle, which is measured
in degrees counterclockwise from the y-axis. The default value of this property is
0.0.
AngleY, of type double , which represents the y-axis skew angle, which is measured
in degrees counterclockwise from the x-axis. The default value of this property is
0.0.
CenterX, of type double , which represents the x-coordinate of the transform
center. The default value of this property is 0.0.
CenterY, of type double , which represents the y-coordinate of the transform
center. The default value of this property is 0.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
To predict the effect of a skew transformation, consider that AngleX skews x-axis values
relative to the original coordinate system. Therefore, for an AngleX of 30, the y-axis
rotates 30 degrees through the origin and skews the values in x by 30 degrees from that
origin. Similarly, an AngleY of 30 skews the y values of the Path object by 30 degrees
from the origin.
7 Note
To skew a Path object in place, set the CenterX and CenterY properties to the
object's center point.
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z">
<Path.RenderTransform>
<SkewTransform CenterX="0"
CenterY="0"
AngleX="45"
AngleY="0" />
</Path.RenderTransform>
</Path>
In this example, a horizontal skew of 45 degrees is applied to the Path object, from a
center point of (0,0).
Translate transform
A translate transform moves an object in the 2D x-y coordinate system.
The TranslateTransform class, which derives from the Transform class, defines the
following properties:
X, of type double , which represents the distance to move along the x-axis. The
default value of this property is 0.0.
Y, of type double , which represents the distance to move along the y-axis. The
default value of this property is 0.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
Negative X values move an object to the left, while positive values move an object to the
right. Negative Y values move an object up, while positive values move an object down.
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z">
<Path.RenderTransform>
<TranslateTransform X="50"
Y="50" />
</Path.RenderTransform>
</Path>
In this example, the Path object is moved 50 device-independent units to the right, and
50 device-independent units down.
Multiple transforms
.NET MAUI has two classes that support applying multiple transforms to a Path object.
These are TransformGroup, and CompositeTransform. A TransformGroup performs
transforms in any desired order, while a CompositeTransform performs transforms in a
specific order.
Transform groups
Transform groups represent composite transforms composed of multiple Transform
objects.
The TransformGroup class, which derives from the Transform class, defines a Children
property, of type TransformCollection, which represents a collection of Transform
objects. This property is backed by a BindableProperty object, which means that it can
be the target of data bindings, and styled.
The following example shows how to perform a composite transform using the
TransformGroup class:
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1.5"
ScaleY="1.5" />
</TransformGroup>
</Path.RenderTransform>
</Path>
In this example, the Path object is scaled to 1.5 times its size, and then rotated by 45
degrees.
Composite transforms
A composite transform applies multiple transforms to an object.
The CompositeTransform class, which derives from the Transform class, defines the
following properties:
CenterX, of type double , which represents the x-coordinate of the center point of
this transform. The default value of this property is 0.0.
CenterY, of type double , which represents the y-coordinate of the center point of
this transform. The default value of this property is 0.0.
ScaleX, of type double , which represents the x-axis scale factor. The default value
of this property is 1.0.
ScaleY, of type double , which represents the y-axis scale factor. The default value
of this property is 1.0.
SkewX, of type double , which represents the x-axis skew angle, which is measured
in degrees counterclockwise from the y-axis. The default value of this property is
0.0.
SkewY, of type double , which represents the y-axis skew angle, which is measured
in degrees counterclockwise from the x-axis. The default value of this property is
0.0.
Rotation, of type double , represents the angle, in degrees, of clockwise rotation.
The default value of this property is 0.0.
TranslateX, of type double , which represents the distance to move along the x-axis.
The default value of this property is 0.0.
TranslateY, of type double , which represents the distance to move along the y-axis.
The default value of this property is 0.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
If you want to apply multiple transforms to an object in a different order, you should
create a TransformGroup and insert the transforms in your intended order.
) Important
A CompositeTransform uses the same center points, CenterX and CenterY , for all
transformations. If you want to specify different center points per transform, use a
TransformGroup,
The following example shows how to perform a composite transform using the
CompositeTransform class:
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
HeightRequest="100"
WidthRequest="100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z">
<Path.RenderTransform>
<CompositeTransform ScaleX="1.5"
ScaleY="1.5"
Rotation="45"
TranslateX="50"
TranslateY="50" />
</Path.RenderTransform>
</Path>
In this example, the Path object is scaled to 1.5 times its size, then rotated by 45
degrees, and then translated by 50 device-independent units.
Transform matrix
A transform can be described in terms of a 3x3 affine transformation matrix, that
performs transformations in 2D space. This 3x3 matrix is represented by the Matrix
struct, which is a collection of three rows and three columns of double values.
The OffsetX and OffsetY properties are so named because they specify the amount to
translate the coordinate space along the x-axis, and y-axis, respectively.
In addition, the Matrix struct exposes a series of methods that can be used to
manipulate the matrix values, including Append, Invert, Multiply, Prepend and many
more.
M11
M12
0.0
M21
M22
0.0
OffsetX
OffsetY
1.0
7 Note
An affine transformation matrix has its final column equal to (0,0,1), so only the
members in the first two columns need to be specified.
By manipulating matrix values, you can rotate, scale, skew, and translate Path objects.
For example, if you change the OffsetX value to 100, you can use it move a Path object
100 device-independent units along the x-axis. If you change the M22 value to 3, you
can use it to stretch a Path object to three times its current height. If you change both
values, you move the Path object 100 device-independent units along the x-axis and
stretch its height by a factor of 3. In addition, affine transformation matrices can be
multiplied to form any number of linear transformations, such as rotation and skew,
followed by translation.
Custom transforms
The MatrixTransform class, which derives from the Transform class, defines a Matrix
property, of type Matrix, which represents the matrix that defines the transformation.
This property is backed by a BindableProperty object, which means that it can be the
target of data bindings, and styled.
The following example shows how to transform a Path object using a MatrixTransform:
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z">
<Path.RenderTransform>
<MatrixTransform>
<MatrixTransform.Matrix>
<Matrix OffsetX="10"
OffsetY="100"
M11="1.5"
M12="1" />
</MatrixTransform.Matrix>
</MatrixTransform>
</Path.RenderTransform>
</Path>
In this example, the Path object is stretched, skewed, and offset in both the X and Y
dimensions.
Alternatively, this can be written in a simplified form that uses a type converter that's
built into .NET MAUI:
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z">
<Path.RenderTransform>
</Path.RenderTransform>
</Path>
In addition, the previous example can be simplified even further by specifying the same
six members as the value of the RenderTransform property:
XAML
<Path Stroke="Black"
Aspect="Uniform"
HorizontalOptions="Center"
RenderTransform="1.5 1 0 1 10 100"
Data="M13.908992,16.207977L32.000049,16.207977 32.000049,31.999985
13.908992,30.109983z" />
Polygon
Article • 02/09/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Polygon class derives from the Shape class,
and can be used to draw polygons, which are connected series of lines that form closed
shapes. For information on the properties that the Polygon class inherits from the Shape
class, see Shapes.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
For more information about the FillRule enumeration, see Fill rules.
Create a Polygon
To draw a polygon, create a Polygon object and set its Points property to the vertices of
a shape. A line is automatically drawn that connects the first and last points. To paint the
inside of the polygon, set its Fill property to a Brush-derived object. To give the polygon
an outline, set its Stroke property to a Brush-derived object. The StrokeThickness
property specifies the thickness of the polygon outline. For more information about
Brush objects, see Brushes.
XAML
<Polygon Points="40,10 70,80 10,50"
Fill="AliceBlue"
Stroke="Green"
StrokeThickness="5" />
XAML
Fill="AliceBlue"
Stroke="Green"
StrokeThickness="5"
StrokeDashArray="1,1"
StrokeDashOffset="6" />
For more information about drawing a dashed polygon, see Draw dashed shapes.
The following XAML example shows a polygon that uses the default fill rule:
XAML
<Polygon Points="0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, 48 192,
150 200 144 48"
Fill="Blue"
Stroke="Red"
StrokeThickness="3" />
In this example, the fill behavior of each polygon is determined using the EvenOdd fill
rule.
The following XAML example shows a polygon that uses the Nonzero fill rule:
XAML
<Polygon Points="0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, 48 192,
150 200 144 48"
Fill="Black"
FillRule="Nonzero"
Stroke="Yellow"
StrokeThickness="3" />
In this example, the fill behavior of each polygon is determined using the Nonzero fill
rule.
Polyline
Article • 02/09/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Polyline class derives from the Shape class,
and can be used to draw a series of connected straight lines. A polyline is similar to a
polygon, except the last point in a polyline is not connected to the first point. For
information on the properties that the Polyline class inherits from the Shape class, see
Shapes.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
For more information about the FillRule enumeration, see Fill rules.
Create a Polyline
To draw a polyline, create a Polyline object and set its Points property to the vertices of
a shape. To give the polyline an outline, set its Stroke property to a Brush-derived object.
The StrokeThickness property specifies the thickness of the polyline outline. For more
information about Brush objects, see Brushes.
) Important
If you set the Fill property of a Polyline to a Brush-derived object, the interior space
of the polyline is painted, even if the start point and end point do not intersect.
The following XAML example shows how to draw a polyline:
XAML
<Polyline Points="0,0 10,30 15,0 18,60 23,30 35,30 40,0 43,60 48,30 100,30"
Stroke="Red" />
XAML
<Polyline Points="0,0 10,30 15,0 18,60 23,30 35,30 40,0 43,60 48,30 100,30"
Stroke="Red"
StrokeThickness="2"
StrokeDashArray="1,1"
StrokeDashOffset="6" />
For more information about drawing a dashed polyline, see Draw dashed shapes.
The following XAML example shows a polyline that uses the default fill rule:
XAML
<Polyline Points="0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, 48 192,
150 200 144 48"
Fill="Blue"
Stroke="Red"
StrokeThickness="3" />
In this example, the fill behavior of the polyline is determined using the EvenOdd fill
rule.
The following XAML example shows a polyline that uses the Nonzero fill rule:
XAML
<Polyline Points="0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, 48 192,
150 200 144 48"
Fill="Black"
FillRule="Nonzero"
Stroke="Yellow"
StrokeThickness="3" />
In this example, the fill behavior of the polyline is determined using the Nonzero fill rule.
Rectangle
Article • 02/09/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Rectangle class derives from the Shape
class, and can be used to draw rectangles and squares. For information on the
properties that the Rectangle class inherits from the Shape class, see .NET MAUI Shapes.
RadiusX, of type double , which is the x-axis radius that's used to round the corners
of the rectangle. The default value of this property is 0.0.
RadiusY, of type double , which is the y-axis radius that's used to round the corners
of the rectangle. The default value of this property is 0.0.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The Rectangle class sets the Aspect property, inherited from the Shape class, to
Stretch.Fill . For more information about the Aspect property, see Stretch shapes.
Create a Rectangle
To draw a rectangle, create a Rectangle object and sets its WidthRequest and
HeightRequest properties. To paint the inside of the rectangle, set its Fill property to a
Brush-derived object. To give the rectangle an outline, set its Stroke property to a Brush-
derived object. The StrokeThickness property specifies the thickness of the rectangle
outline. For more information about Brush objects, see Brushes.
To give the rectangle rounded corners, set its RadiusX and RadiusY properties. These
properties set the x-axis and y-axis radii that's used to round the corners of the
rectangle.
7 Note
XAML
<Rectangle Fill="Red"
WidthRequest="150"
HeightRequest="50"
HorizontalOptions="Start" />
The following XAML example shows how to draw a filled rectangle, with rounded
corners:
XAML
<Rectangle Fill="Blue"
Stroke="Black"
StrokeThickness="3"
RadiusX="50"
RadiusY="10"
WidthRequest="200"
HeightRequest="100"
HorizontalOptions="Start" />
For information about drawing a dashed rectangle, see Draw dashed shapes.
WebView
Article • 02/09/2023 • 10 minutes to read
The .NET Multi-platform App UI (.NET MAUI) WebView displays remote web pages, local
HTML files, and HTML strings, in an app. The content displayed a WebView includes
support for Cascading Style Sheets (CSS), and JavaScript. By default, .NET MAUI projects
include the platform permissions required for a WebView to display a remote web page.
is a read-only property.
Source , of type WebViewSource , represents the location that the WebView displays.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
WebView defines a Navigating event that's raised when page navigation starts, and a
Navigated event that's raised when page navigation completes. The
Cancel property of type bool that can be used to cancel navigation. The
WebNavigatedEventArgs object that accompanies the Navigated event defines a Result
) Important
XAML
C#
Source = "https://learn.microsoft.com/dotnet/maui"
};
7 Note
Despite the Source property being of type WebViewSource , the property can be set
to a string-based URI. This is because .NET MAUI includes a type converter, and an
implicit conversion operator, that converts the string-based URI to a
UrlWebViewSource object.
XAML
<WebView>
<WebView.Source>
<HtmlWebViewSource Html="<HTML><BODY><H1>.NET
MAUI</H1><P>Welcome to
WebView.</P></BODY><HTML>" />
</WebView.Source>
</WebView>
In XAML, HTML strings can become unreadable due to escaping the < and > symbols.
Therefore, for greater readability the HTML can be inlined in a CDATA section:
XAML
<WebView>
<WebView.Source>
<HtmlWebViewSource>
<HtmlWebViewSource.Html>
<![CDATA[
<HTML>
<BODY>
<H1>.NET MAUI</H1>
<P>Welcome to WebView.</P>
</BODY>
</HTML>
]]>
</HtmlWebViewSource.Html>
</HtmlWebViewSource>
</WebView.Source>
</WebView>
C#
};
XAML
<WebView>
<WebView.Source>
<HtmlWebViewSource>
<HtmlWebViewSource.Html>
<![CDATA[
<html>
<head>
</head>
<body>
<h1>.NET MAUI</h1>
</body>
</html>
]]>
</HtmlWebViewSource.Html>
</HtmlWebViewSource>
</WebView.Source>
</WebView>
The local HTML file can load Cascading Style Sheets (CSS), JavaScript, and images, if
they've also been added to your app project with the MauiAsset build action.
Reload content
WebView has a Reload method that can be called to reload its source:
C#
...
webView.Reload();
Perform navigation
WebView supports programmatic navigation with the GoBack and GoForward methods.
These methods enable navigation through the WebView page stack, and should only be
called after inspecting the values of the CanGoBack and CanGoForward properties:
C#
...
// Go backwards, if allowed.
if (webView.CanGoBack)
webView.GoBack();
// Go forwards, if allowed.
if (webView.CanGoForward)
webView.GoForward();
) Important
The permission requests from a web page to the WebView control are different than
permission requests from the .NET MAUI app to the user. .NET MAUI app permissions
are requested and approved by the user, for the whole app. The WebView control is
dependent on the apps ability to access the hardware. To illustrate this concept,
consider a web page that requests access to the device's camera. Even if that request is
approved by the WebView control, yet the .NET MAUI app didn't have approval by the
user to access the camera, the web page wouldn't be able to access the camera.
The following steps demonstrate how to intercept permission requests from the
WebView control to use the camera. If you are trying to use the microphone, the steps
would be similar except that you would use microphone-related permissions instead of
camera-related permissions.
1. First, add the required app permissions to the Android manifest. Open the
Platforms/Android/AndroidManifest.xml file and add the following in the manifest
node:
XML
2. At some point in your app, such as when the page containing a WebView control is
loaded, request permission from the user to allow the app access to the camera.
C#
if (status != PermissionStatus.Granted)
await Permissions.RequestAsync<Permissions.Camera>();
3. Add the following class to the Platforms/Android folder, changing the root
namespace to match your project's namespace:
C#
using Android.Webkit;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
namespace MauiAppWebViewHandlers.Platforms.Android;
if (resource.Equals(PermissionRequest.ResourceVideoCapture,
StringComparison.OrdinalIgnoreCase))
PermissionStatus status =
Permissions.CheckStatusAsync<Permissions.Camera>().Result;
if (status != PermissionStatus.Granted)
request.Deny();
else
request.Grant(request.GetResources());
return;
base.OnPermissionRequest(request);
4. Use the SetWebChromeClient method on the Android's WebView control to set the
chrome client to MyWebChromeClient . The following two items demonstrate how
you can set the chrome client:
Given a .NET MAUI WebView control named theWebViewControl , you can set
the chrome client directly on the platform view, which is the Android control:
C#
((IWebViewHandler)theWebViewControl.Handler).PlatformView.SetWebCh
romeClient(new
MyWebChromeClient((IWebViewHandler)theWebViewControl.Handler));
You can also use handler property mapping to force all WebView controls to
use your chrome client. For more information, see Handlers.
C#
private static void CustomizeWebViewHandler()
#if ANDROID26_0_OR_GREATER
Microsoft.Maui.Handlers.WebViewHandler.Mapper.ModifyMapping(
nameof(Android.Webkit.WebView.WebChromeClient),
#endif
Set cookies
Cookies can be set on a WebView so that they are sent with the web request to the
specified URL. Set the cookies by adding Cookie objects to a CookieContainer , and then
set the container as the value of the WebView.Cookies bindable property. The following
code shows an example:
C#
using System.Net;
Name = "DotNetMAUICookie",
Expires = DateTime.Now.AddDays(1),
Domain = uri.Host,
Path = "/"
};
cookieContainer.Add(uri, cookie);
webView.Cookies = cookieContainer;
In this example, a single Cookie is added to the CookieContainer object, which is then
set as the value of the WebView.Cookies property. When the WebView sends a web
request to the specified URL, the cookie is sent with the request.
Invoke JavaScript
WebView includes the ability to invoke a JavaScript function from C# and return any
result to the calling C# code. This interop is accomplished with the
EvaluateJavaScriptAsync method, which is shown in the following example:
C#
...
HTML
<html>
<body>
<script type="text/javascript">
function factorial(num) {
return 1;
num *= i;
return num;
</script>
</body>
</html>
C#
await Launcher.OpenAsync("https://learn.microsoft.com/dotnet/maui");
The .NET Multi-platform App UI (.NET MAUI) Button displays text and responds to a tap
or click that directs the app to carry out a task. A Button usually displays a short text
string indicating a command, but it can also display a bitmap image, or a combination
of text and an image. When the Button is pressed with a finger or clicked with a mouse
it initiates that command.
button's text.
Command , of type ICommand , defines the command that's executed when the button
is tapped.
CommandParameter , of type object , is the parameter that's passed to Command .
ContentLayout , of type ButtonContentLayout , defines the object that controls the
position of the button image and the spacing between the button's image and
text.
CornerRadius , of type int , describes the corner radius of the button's border.
scaling preferences set in the operating system. The default value of this property
is true .
FontFamily , of type string , defines the font family.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
While Button defines an ImageSource property, that allows you to display a image
on the Button, this property is intended to be used when displaying a small icon
next to the Button text.
In addition, Button defines Clicked , Pressed , and Released events. The Clicked event is
raised when a Button tap with a finger or mouse pointer is released from the button's
surface. The Pressed event is raised when a finger presses on a Button, or a mouse
button is pressed with the pointer positioned over the Button. The Released event is
raised when the finger or mouse button is released. Generally, a Clicked event is also
raised at the same time as the Released event, but if the finger or mouse pointer slides
away from the surface of the Button before being released, the Clicked event might not
occur.
) Important
A Button must have its IsEnabled property set to true for it to respond to taps.
Create a Button
To create a button, create a Button object and handle its Clicked event.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ButtonDemos.BasicButtonClickPage"
<StackLayout>
VerticalOptions="Center"
HorizontalOptions="Center"
Clicked="OnButtonClicked" />
<Label x:Name="label"
FontSize="18"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>
The Text property specifies the text that appears in the Button. The Clicked event is set
to an event handler named OnButtonClicked . This handler is located in the code-behind
file:
C#
public BasicButtonClickPage ()
InitializeComponent ();
In this example, when the Button is tapped, the OnButtonClicked method executes. The
sender argument is the Button object responsible for this event. You can use this to
access the Button object, or to distinguish between multiple Button objects sharing the
same Clicked event. The Clicked handler calls an animation function that rotates the
Label 360 degrees in 1000 milliseconds:
C#
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center
};
The following example shows a very simple viewmodel class that defines a property of
type double named Number , and two properties of type ICommand named
MultiplyBy2Command and DivideBy2Command :
C#
double number = 1;
public CommandDemoViewModel()
get
return number;
set
if (number != value)
number = value;
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Number"));
In this example, the two ICommand properties are initialized in the class's constructor with
two objects of type Command . The Command constructors include a little function (called
the execute constructor argument) that either doubles or halves the value of the Number
property.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ButtonDemos"
x:Class="ButtonDemos.BasicButtonCommandPage"
<ContentPage.BindingContext>
<local:CommandDemoViewModel />
</ContentPage.BindingContext>
<StackLayout>
FontSize="18"
VerticalOptions="Center"
HorizontalOptions="Center" />
VerticalOptions="Center"
HorizontalOptions="Center"
VerticalOptions="Center"
HorizontalOptions="Center"
</StackLayout>
</ContentPage>
In this example, the Label element and two Button objects contain bindings to the three
properties in the CommandDemoViewModel class. As the two Button objects are tapped, the
commands are executed, and the number changes value. The advantage of this
approach over Clicked handlers is that all the logic involving the functionality of this
page is located in the viewmodel rather than the code-behind file, achieving a better
separation of the user interface from the business logic.
It's also possible for the Command objects to control the enabling and disabling of the
Button objects. For example, suppose you want to limit the range of number values
between 210 and 2–10. You can add another function to the constructor (called the
canExecute argument) that returns true if the Button should be enabled:
C#
···
public CommandDemoViewModel()
execute: () =>
Number *= 2;
((Command)MultiplyBy2Command).ChangeCanExecute();
((Command)DivideBy2Command).ChangeCanExecute();
},
execute: () =>
Number /= 2;
((Command)MultiplyBy2Command).ChangeCanExecute();
((Command)DivideBy2Command).ChangeCanExecute();
},
···
In this example, the calls to the ChangeCanExecute method of Command are required so
that the Command method can call the canExecute method and determine whether the
Button should be disabled or not. With this code change, as the number reaches the
limit, the Button is disabled.
It's also possible for two or more Button elements to be bound to the same ICommand
property. The Button elements can be distinguished using the CommandParameter
property of Button. In this case, you'll want to use the generic Command<T> class. The
CommandParameter object is then passed as an argument to the execute and canExecute
The following XAML example shows a Label and a Button with handlers attached for the
Pressed and Released events:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ButtonDemos.PressAndReleaseButtonPage"
<StackLayout>
VerticalOptions="Center"
HorizontalOptions="Center"
Pressed="OnButtonPressed"
Released="OnButtonReleased" />
<Label x:Name="label"
FontSize="18"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>
The code-behind file animates the Label when a Pressed event occurs, but suspends the
rotation when a Released event occurs:
C#
IDispatcherTimer timer;
public PressAndReleaseButtonPage()
InitializeComponent();
timer = Dispatcher.CreateTimer();
timer.Interval = TimeSpan.FromMilliseconds(16);
};
stopwatch.Start();
timer.Start();
stopwatch.Stop();
timer.Stop();
The result is that the Label only rotates while a finger is in contact with the Button, and
stops when the finger is released.
The following XAML example shows how to define a visual state for the Pressed state:
XAML
...>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Scale"
Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="Scale"
Value="0.8" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
In this example, the Pressed VisualState specifies that when the Button is pressed, its
Scale property will be changed from its default value of 1 to 0.8. The Normal VisualState
specifies that when the Button is in a normal state, its Scale property will be set to 1.
Therefore, the overall effect is that when the Button is pressed, it's rescaled to be slightly
smaller, and when the Button is released, it's rescaled to its default size.
You can specify how the Text and ImageSource properties are arranged on the Button
using the ContentLayout property of Button. This property is of type
ButtonContentLayout , and its constructor has two arguments:
In XAML, you can create a Button and set the ContentLayout property by specifying only
the enumeration member, or the spacing, or both in any order separated by commas:
XAML
ImageSource="button.png"
C#
File = "button.png"
},
ContentLayout = new
Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Right,
20)
};
Disable a Button
Sometimes an app enters a state where a Button click is not a valid operation. In such
cases, the Button can be disabled by setting its IsEnabled property to false .
ImageButton
Article • 04/03/2023 • 5 minutes to read
The .NET Multi-platform App UI (.NET MAUI) ImageButton view combines the Button
view and Image view to create a button whose content is an image. When you press the
ImageButton with a finger or click it with a mouse, it directs the app to carry out a task.
However, unlike the Button the ImageButton view has no concept of text and text
appearance.
Aspect , of type Aspect , determines how the image will be scaled to fit the display
area.
BorderColor , of type Color, describes the border color of the button.
Command , of type ICommand , defines the command that's executed when the button
is tapped.
CommandParameter , of type object , is the parameter that's passed to Command .
CornerRadius , of type int , describes the corner radius of the button's border.
IsLoading , of type bool , represents the loading status of the image. The default
opaque when rendering it. The default value of this property is false .
IsPressed , of type bool , represents whether the button is being pressed. The
button.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The Aspect property can be set to one of the members of the Aspect enumeration:
Fill - stretches the image to completely and exactly fill the ImageButton. This
aspect ratio.
AspectFit - letterboxes the image (if necessary) so that the entire image fits into
the ImageButton, with blank space added to the top/bottom or sides depending
on whether the image is wide or tall. This is the default value of the Aspect
enumeration.
Center - centers the image in the ImageButton while preserving the aspect ratio.
In addition, ImageButton defines Clicked , Pressed , and Released events. The Clicked
event is raised when an ImageButton tap with a finger or mouse pointer is released from
the button's surface. The Pressed event is raised when a finger presses on an
ImageButton, or a mouse button is pressed with the pointer positioned over the
ImageButton. The Released event is raised when the finger or mouse button is released.
Generally, a Clicked event is also raised at the same time as the Released event, but if
the finger or mouse pointer slides away from the surface of the ImageButton before
being released, the Clicked event might not occur.
) Important
An ImageButton must have its IsEnabled property set to true for it to respond to
taps.
Create an ImageButton
To create an image button, create an ImageButton object, set its Source property and
handle it's Clicked event.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ControlGallery.Views.XAML.ImageButtonDemoPage"
Title="ImageButton Demo">
<StackLayout>
<ImageButton Source="image.png"
Clicked="OnImageButtonClicked"
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
The Source property specifies the image that appears in the ImageButton. The Clicked
event is set to an event handler named OnImageButtonClicked . This handler is located in
the code-behind file:
C#
int clickTotal;
public ImageButtonDemoPage()
InitializeComponent();
clickTotal += 1;
C#
Label label;
int clickTotal = 0;
...
Source = "XamarinLogo.png",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};
clickTotal += 1;
};
namespace.
CommandParameter property of type Object.
For more information about these events, see Press and release the button in the Button
article.
XAML
<ImageButton Source="image.png"
...>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Scale"
Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="Scale"
Value="0.8" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ImageButton>
In this example, the Pressed VisualState specifies that when the ImageButton is pressed,
its Scale property will be changed from its default value of 1 to 0.8. The Normal
VisualState specifies that when the ImageButton is in a normal state, its Scale property
will be set to 1. Therefore, the overall effect is that when the ImageButton is pressed, it's
rescaled to be slightly smaller, and when the ImageButton is released, it's rescaled to its
default size.
Disable an ImageButton
Sometimes an app enters a state where an ImageButton click is not a valid operation. In
those cases, the ImageButton should be disabled by setting its IsEnabled property to
false .
RadioButton
Article • 04/03/2023 • 10 minutes to read
The .NET Multi-platform App UI (.NET MAUI) RadioButton is a type of button that allows
users to select one option from a set. Each option is represented by one radio button,
and you can only select one radio button in a group. By default, each RadioButton
displays text:
However, on some platforms a RadioButton can display a View, and on all platforms the
appearance of each RadioButton can be redefined with a ControlTemplate:
Content , of type object , which defines the string or View to be displayed by the
RadioButton.
IsChecked , of type bool , which defines whether the RadioButton is checked. This
property uses a TwoWay binding, and has a default value of false .
GroupName , of type string , which defines the name that specifies which
RadioButton controls are mutually exclusive. This property has a default value of
null .
Value , of type object , which defines an optional unique value associated with the
RadioButton.
BorderColor , of type Color, which defines the border stroke color.
BorderWidth , of type double , which defines the width of the RadioButton border.
CharacterSpacing , of type double , which defines the spacing between characters
TextColor , of type Color, which defines the color of any displayed text.
TextTransform , of type TextTransform , which defines the casing of any displayed
text.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
RadioButton also defines a CheckedChanged event that's raised when the IsChecked
property changes, either through user or programmatic manipulation. The
CheckedChangedEventArgs object that accompanies the CheckedChanged event has a
single property named Value , of type bool . When the event is raised, the value of the
CheckedChangedEventArgs.Value property is set to the new value of the IsChecked
property.
GroupName , of type string , which defines the group name for RadioButton objects
in an ILayout .
SelectedValue , of type object , which represents the value of the checked
RadioButton object within an ILayout group. This attached property uses a TwoWay
binding by default.
For more information about the GroupName attached property, see Group RadioButtons.
For more information about the SelectedValue attached property, see Respond to
RadioButton state changes.
Create RadioButtons
The appearance of a RadioButton is defined by the type of data assigned to the
RadioButton.Content property:
XAML
<StackLayout>
<RadioButton Content="Monkey"
IsChecked="true" />
</StackLayout>
In this example, RadioButton objects are implicitly grouped inside the same parent
container. This XAML results in the appearance shown in the following screenshot:
XAML
<StackLayout>
<RadioButton>
<RadioButton.Content>
</RadioButton.Content>
</RadioButton>
<RadioButton>
<RadioButton.Content>
</RadioButton.Content>
</RadioButton>
<RadioButton>
<RadioButton.Content>
</RadioButton.Content>
</RadioButton>
<RadioButton>
<RadioButton.Content>
</RadioButton.Content>
</RadioButton>
</StackLayout>
In this example, RadioButton objects are implicitly grouped inside the same parent
container. This XAML results in the appearance shown in the following screenshot:
7 Note
XAML
<StackLayout>
<RadioButton Value="Cat">
<RadioButton.Content>
</RadioButton.Content>
</RadioButton>
<RadioButton Value="Dog">
<RadioButton.Content>
</RadioButton.Content>
</RadioButton>
<RadioButton Value="Elephant">
<RadioButton.Content>
</RadioButton.Content>
</RadioButton>
<RadioButton Value="Monkey">
<RadioButton.Content>
</RadioButton.Content>
</RadioButton>
</StackLayout>
In this example, each RadioButton has an Image as its content, while also defining a
string-based value. This enables the value of the checked radio button to be easily
identified.
Group RadioButtons
Radio buttons work in groups, and there are three approaches to grouping radio
buttons:
Place them inside the same parent container. This is known as implicit grouping.
Set the GroupName property on each radio button in the group to the same value.
This is known as explicit grouping.
Set the RadioButtonGroup.GroupName attached property on a parent container,
which in turn sets the GroupName property of any RadioButton objects in the
container. This is also known as explicit grouping.
) Important
RadioButton objects don't have to belong to the same parent to be grouped. They
are mutually exclusive provided that they share a group name.
XAML
<RadioButton Content="Red"
GroupName="colors" />
<RadioButton Content="Green"
GroupName="colors" />
<RadioButton Content="Blue"
GroupName="colors" />
<RadioButton Content="Other"
GroupName="colors" />
In this example, each RadioButton is mutually exclusive because it shares the same
GroupName value.
XAML
<StackLayout RadioButtonGroup.GroupName="colors">
</StackLayout>
In this example, each RadioButton in the StackLayout will have its GroupName property
set to colors , and will be mutually exclusive.
7 Note
XAML
<RadioButton Content="Red"
GroupName="colors"
CheckedChanged="OnColorsRadioButtonCheckedChanged" />
C#
The sender argument is the RadioButton responsible for this event. You can use this to
access the RadioButton object, or to distinguish between multiple RadioButton objects
sharing the same CheckedChanged event handler.
Respond to a property change
The RadioButtonGroup class defines a SelectedValue attached property, of type object ,
which can be set on an ILayout object. This attached property represents the value of
the checked RadioButton within a group defined on a layout.
XAML
RadioButtonGroup.SelectedValue="{Binding Selection}">
<RadioButton Content="Cat"
Value="Cat" />
<RadioButton Content="Dog"
Value="Dog" />
<RadioButton Content="Elephant"
Value="Elephant" />
<RadioButton Content="Monkey"
Value="Monkey"/>
<Label x:Name="animalLabel">
<Label.FormattedText>
<FormattedString>
</FormattedString>
</Label.FormattedText>
</Label>
</StackLayout>
the binding context. In addition, the Selection property is updated to the Value
property of the checked RadioButton.
XAML
<ContentPage ...>
<ContentPage.Resources>
<Style TargetType="RadioButton">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CheckedStates">
<VisualState x:Name="Checked">
<VisualState.Setters>
<Setter Property="TextColor"
Value="Green" />
<Setter Property="Opacity"
Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.Setters>
<Setter Property="TextColor"
Value="Red" />
<Setter Property="Opacity"
Value="0.5" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
<StackLayout>
</StackLayout>
</ContentPage>
In this example, the implicit Style targets RadioButton objects. The Checked VisualState
specifies that when a RadioButton is checked, its TextColor property will be set to green
with an Opacity value of 1. The Unchecked VisualState specifies that when a RadioButton
is in a unchecked state, its TextColor property will be set to red with an Opacity value
of 0.5. Therefore, the overall effect is that when a RadioButton is unchecked it's red and
partially transparent, and is green without transparency when it's checked:
For more information about visual states, see Visual states.
The following XAML shows a ControlTemplate that can be used to redefine the visual
structure of RadioButton objects:
XAML
<ContentPage ...>
<ContentPage.Resources>
<ControlTemplate x:Key="RadioButtonTemplate">
<Border Stroke="#F3F2F1"
StrokeThickness="2"
StrokeShape="RoundRectangle 10"
BackgroundColor="#F3F2F1"
HeightRequest="90"
WidthRequest="90"
HorizontalOptions="Start"
VerticalOptions="Start">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CheckedStates">
<VisualState x:Name="Checked">
<VisualState.Setters>
<Setter Property="Stroke"
Value="#FF3300" />
<Setter TargetName="check"
Property="Opacity"
Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unchecked">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="#F3F2F1" />
<Setter Property="Stroke"
Value="#F3F2F1" />
<Setter TargetName="check"
Property="Opacity"
Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
<Grid Margin="4"
WidthRequest="90">
<Grid Margin="0,0,4,0"
WidthRequest="18"
HeightRequest="18"
HorizontalOptions="End"
VerticalOptions="Start">
<Ellipse Stroke="Blue"
Fill="White"
WidthRequest="16"
HeightRequest="16"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Ellipse x:Name="check"
Fill="Blue"
WidthRequest="8"
HeightRequest="8"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
<ContentPresenter />
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="RadioButton">
<Setter Property="ControlTemplate"
</Style>
</ContentPage.Resources>
</ContentPage>
In this example, the root element of the ControlTemplate is a Border object that defines
Checked and Unchecked visual states. The Border object uses a combination of Grid,
7 Note
The ContentPresenter object marks the location in the visual structure where
RadioButton content will be displayed.
The following XAML shows RadioButton objects that consume the ControlTemplate via
the implicit style:
XAML
<StackLayout>
<StackLayout RadioButtonGroup.GroupName="animals"
Orientation="Horizontal">
<RadioButton Value="Cat">
<RadioButton.Content>
<StackLayout>
<Image Source="cat.png"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Label Text="Cat"
HorizontalOptions="Center"
VerticalOptions="End" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Value="Dog">
<RadioButton.Content>
<StackLayout>
<Image Source="dog.png"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Label Text="Dog"
HorizontalOptions="Center"
VerticalOptions="End" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Value="Elephant">
<RadioButton.Content>
<StackLayout>
<Image Source="elephant.png"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Label Text="Elephant"
HorizontalOptions="Center"
VerticalOptions="End" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
<RadioButton Value="Monkey">
<RadioButton.Content>
<StackLayout>
<Image Source="monkey.png"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Label Text="Monkey"
HorizontalOptions="Center"
VerticalOptions="End" />
</StackLayout>
</RadioButton.Content>
</RadioButton>
</StackLayout>
</StackLayout>
In this example, the visual structure defined for each RadioButton is replaced with the
visual structure defined in the ControlTemplate, and so at runtime the objects in the
ControlTemplate become part of the visual tree for each RadioButton. In addition, the
content for each RadioButton is substituted into the ContentPresenter defined in the
control template. This results in the following RadioButton appearance:
Disable a RadioButton
Sometimes an app enters a state where a RadioButton being checked is not a valid
operation. In such cases, the RadioButton can be disabled by setting its IsEnabled
property to false .
RefreshView
Article • 04/03/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) RefreshView is a container control that
provides pull to refresh functionality for scrollable content. Therefore, the child of a
RefreshView must be a scrollable control, such as ScrollView, CollectionView, or ListView.
Command .
IsRefreshing , of type bool , which indicates the current state of the RefreshView.
RefreshColor , of type Color, the color of the progress circle that appears during
the refresh.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
Create a RefreshView
To add a RefreshView to a page, create a RefreshView object and set its IsRefreshing
and Command properties. Then set its child to a scrollable control.
XAML
Command="{Binding RefreshCommand}">
<ScrollView>
<FlexLayout Direction="Row"
Wrap="Wrap"
AlignItems="Center"
AlignContent="Center"
BindableLayout.ItemsSource="{Binding Items}"
BindableLayout.ItemTemplate="{StaticResource
ColorItemTemplate}" />
</ScrollView>
</RefreshView>
// IsRefreshing is true
refreshView.IsRefreshing = false;
});
refreshView.Command = refreshCommand;
scrollView.Content = flexLayout;
refreshView.Content = scrollView;
The value of the RefreshView.IsRefreshing property indicates the current state of the
RefreshView. When a refresh is triggered by the user, this property will automatically
transition to true . Once the refresh completes, you should reset the property to false .
When the user initiates a refresh, the ICommand defined by the Command property is
executed, which should refresh the items being displayed. A refresh visualization is
shown while the refresh occurs, which consists of an animated progress circle. The
following screenshot shows the progress circle on iOS:
7 Note
Manually setting the IsRefreshing property to true will trigger the refresh
visualization, and will execute the ICommand defined by the Command property.
RefreshView appearance
In addition to the properties that RefreshView inherits from the VisualElement class,
RefreshView also defines the RefreshColor property. This property can be set to define
the color of the progress circle that appears during the refresh:
XAML
<RefreshView RefreshColor="Teal"
... />
The following Android screenshot shows a RefreshView with the RefreshColor property:
In addition, the BackgroundColor property can be set to a Color that represents the
background color of the progress circle.
7 Note
On iOS, the BackgroundColor property sets the background color of the UIView that
contains the progress circle.
Disable a RefreshView
An app may enter a state where pull to refresh is not a valid operation. In such cases, the
RefreshView can be disabled by setting its IsEnabled property to false . This will
prevent users from being able to trigger pull to refresh.
Alternatively, when defining the Command property, the CanExecute delegate of the
ICommand can be specified to enable or disable the command.
SearchBar
Article • 04/03/2023 • 3 minutes to read
The .NET Multi-platform App UI (.NET MAUI) SearchBar is a user input control used to
initiating a search. The SearchBar control supports placeholder text, query input, search
execution, and cancellation. The following iOS screenshot shows a SearchBar query with
results displayed in a ListView:
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
7 Note
SearchBar derives from the InputView class, from which it inherits additional
properties and events.
Create a SearchBar
To create a search bar, create a SearchBar object and set its Placeholder property to text
that instructs the user to enter a search term.
XAML
C#
The following XAML example shows an event handler attached to the TextChanged event
and uses a ListView to display search results:
XAML
In this example, the TextChanged event is set to an event handler named OnTextChanged .
This handler is located in the code-behind file:
C#
searchResults.ItemsSource =
DataService.GetSearchResults(searchBar.Text);
ListView.
The following example shows a viewmodel class that contains an ICommand property
named PerformSearch :
C#
public class SearchViewModel : INotifyPropertyChanged
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
SearchResults = DataService.GetSearchResults(query);
});
get
return searchResults;
set
searchResults = value;
NotifyPropertyChanged();
7 Note
XAML
<ContentPage ...>
<ContentPage.BindingContext>
<viewmodels:SearchViewModel />
</ContentPage.BindingContext>
<StackLayout>
<SearchBar x:Name="searchBar"
SearchCommand="{Binding PerformSearch}"
<ListView x:Name="searchResults"
</StackLayout>
</ContentPage>
The .NET Multi-platform App UI (.NET MAUI) SwipeView is a container control that wraps
around an item of content, and provides context menu items that are revealed by a
swipe gesture:
LeftItems , of type SwipeItems , which represents the swipe items that can be
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
In addition, the SwipeView inherits the Content property from the ContentView class.
The Content property is the content property of the SwipeView class, and therefore
does not need to be explicitly set.
In addition, SwipeView includes Open and Close methods, which programmatically open
and close the swipe items, respectively.
Create a SwipeView
A SwipeView must define the content that the SwipeView wraps around, and the swipe
items that are revealed by the swipe gesture. The swipe items are one or more
SwipeItem objects that are placed in one of the four SwipeView directional collections -
LeftItems , RightItems , TopItems , or BottomItems .
XAML
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Invoked="OnFavoriteSwipeItemInvoked" />
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
<Grid HeightRequest="60"
WidthRequest="300"
BackgroundColor="LightGray">
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
</SwipeView>
C#
// SwipeItems
Text = "Favorite",
IconImageSource = "favorite.png",
BackgroundColor = Colors.LightGreen
};
favoriteSwipeItem.Invoked += OnFavoriteSwipeItemInvoked;
Text = "Delete",
IconImageSource = "delete.png",
BackgroundColor = Colors.LightPink
};
deleteSwipeItem.Invoked += OnDeleteSwipeItemInvoked;
// SwipeView content
HeightRequest = 60,
WidthRequest = 300,
BackgroundColor = Colors.LightGray
};
grid.Add(new Label
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
});
Content = grid
};
The swipe items are used to perform actions on the SwipeView content, and are
revealed when the control is swiped from the left side:
By default, a swipe item is executed when it is tapped by the user. However, this
behavior can be changed. For more information, see Swipe mode.
Once a swipe item has been executed the swipe items are hidden and the SwipeView
content is re-displayed. However, this behavior can be changed. For more information,
see Swipe behavior.
7 Note
Swipe content and swipe items can be placed inline, or defined as resources.
Swipe items
The LeftItems , RightItems , TopItems , and BottomItems collections are all of type
SwipeItems . The SwipeItems class defines the following properties:
Mode , of type SwipeMode , which indicates the effect of a swipe interaction. For more
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
Each swipe item is defined as a SwipeItem object that's placed into one of the four
SwipeItems directional collections. The SwipeItem class derives from the MenuItem class,
) Important
The following example shows two SwipeItem objects in the LeftItems collection of a
SwipeView:
XAML
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Invoked="OnFavoriteSwipeItemInvoked" />
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
When a SwipeItem is tapped, its Invoked event fires and is handled by its registered
event handler. In addition, the MenuItem.Clicked event fires. Alternatively, the Command
property can be set to an ICommand implementation that will be executed when the
SwipeItem is invoked.
7 Note
In addition to defining swipe items as SwipeItem objects, it's also possible to define
custom swipe item views. For more information, see Custom swipe items.
Swipe direction
SwipeView supports four different swipe directions, with the swipe direction being
defined by the directional SwipeItems collection the SwipeItem objects are added to.
Each swipe direction can hold its own swipe items. For example, the following example
shows a SwipeView whose swipe items depend on the swipe direction:
XAML
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
</SwipeItems>
</SwipeView.LeftItems>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
<SwipeItem Text="Share"
IconImageSource="share.png"
BackgroundColor="LightYellow"
</SwipeItems>
</SwipeView.RightItems>
</SwipeView>
In this example, the SwipeView content can be swiped right or left. Swiping to the right
will show the Delete swipe item, while swiping to the left will show the Favorite and
Share swipe items.
2 Warning
The SwipeStarted , SwipeChanging , and SwipeEnded events report the swipe direction via
the SwipeDirection property in the event arguments. This property is of type
SwipeDirection , which is an enumeration consisting of four members:
Right indicates that a right swipe occurred.
Swipe threshold
SwipeView includes a Threshold property, of type double , which represents the number
of device-independent units that trigger a swipe gesture to fully reveal swipe items.
The following example shows a SwipeView that sets the Threshold property:
XAML
<SwipeView Threshold="200">
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen" />
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
In this example, the SwipeView must be swiped for 200 device-independent units before
the SwipeItem is fully revealed.
Swipe mode
The SwipeItems class has a Mode property, which indicates the effect of a swipe
interaction. This property should be set to one of the SwipeMode enumeration members:
Reveal indicates that a swipe reveals the swipe items. This is the default value of
In reveal mode, the user swipes a SwipeView to open a menu consisting of one or more
swipe items, and must explicitly tap a swipe item to execute it. After the swipe item has
been executed the swipe items are closed and the SwipeView content is re-displayed. In
execute mode, the user swipes a SwipeView to open a menu consisting of one more
swipe items, which are then automatically executed. Following execution, the swipe
items are closed and the SwipeView content is re-displayed.
The following example shows a SwipeView configured to use execute mode:
XAML
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems Mode="Execute">
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
In this example, the SwipeView content can be swiped right to reveal the swipe item,
which is executed immediately. Following execution, the SwipeView content is re-
displayed.
Swipe behavior
The SwipeItems class has a SwipeBehaviorOnInvoked property, which indicates how a
SwipeView behaves after a swipe item is invoked. This property should be set to one of
the SwipeBehaviorOnInvoked enumeration members:
Auto indicates that in reveal mode the SwipeView closes after a swipe item is
invoked, and in execute mode the SwipeView remains open after a swipe item is
invoked. This is the default value of the SwipeItems.SwipeBehaviorOnInvoked
property.
Close indicates that the SwipeView closes after a swipe item is invoked.
RemainOpen indicates that the SwipeView remains open after a swipe item is
invoked.
The following example shows a SwipeView configured to remain open after a swipe item
is invoked:
XAML
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems SwipeBehaviorOnInvoked="RemainOpen">
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Invoked="OnFavoriteSwipeItemInvoked" />
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
Command .
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The SwipeItemView class also defines an Invoked event that's raised when the item is
tapped, after the Command is executed.
XAML
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
CommandParameter="{Binding Source={x:Reference
resultEntry}, Path=Text}">
<StackLayout Margin="10"
WidthRequest="300">
<Entry x:Name="resultEntry"
Placeholder="Enter answer"
HorizontalOptions="CenterAndExpand" />
<Label Text="Check"
FontAttributes="Bold"
HorizontalOptions="Center" />
</StackLayout>
</SwipeItemView>
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
The Open method requires an OpenSwipeItem argument, to specify the direction the
SwipeView will be opened from. The OpenSwipeItem enumeration has four members:
LeftItems , which indicates that the SwipeView will be opened from the left, to
In addition, the Open method also accepts an optional bool argument that defines
whether the SwipeView will be animated when it opens.
Given a SwipeView named swipeView , the following example shows how to open a
SwipeView to reveal the swipe items in the LeftItems collection:
C#
swipeView.Open(OpenSwipeItem.LeftItems);
C#
swipeView.Close();
7 Note
The Close method also accepts an optional bool argument that defines whether
the SwipeView will be animated when it closes.
Disable a SwipeView
An app may enter a state where swiping an item of content is not a valid operation. In
such cases, the SwipeView can be disabled by setting its IsEnabled property to false .
This will prevent users from being able to swipe content to reveal swipe items.
The .NET Multi-platform App UI (.NET MAUI) CheckBox is a type of button that can
either be checked or empty. When a checkbox is checked, it's considered to be on.
When a checkbox is empty, it's considered to be off.
IsChecked , of type bool , which indicates whether the CheckBox is checked. This
property has a default binding mode of TwoWay .
Color , of type Color, which indicates the color of the CheckBox.
These properties are backed by BindableProperty objects, which means that they can be
styled, and be the target of data bindings.
CheckBox defines a CheckedChanged event that's raised when the IsChecked property
changes, either through user manipulation or when an application sets the IsChecked
property. The CheckedChangedEventArgs object that accompanies the CheckedChanged
event has a single property named Value , of type bool . When the event is raised, the
value of the Value property is set to the new value of the IsChecked property.
Create a CheckBox
The following example shows how to instantiate a CheckBox in XAML:
XAML
<CheckBox />
By default, the CheckBox is empty. The CheckBox can be checked by user manipulation,
or by setting the IsChecked property to true :
XAML
<CheckBox IsChecked="true" />
C#
XAML
The code-behind file contains the handler for the CheckedChanged event:
C#
The sender argument is the CheckBox responsible for this event. You can use this to
access the CheckBox object, or to distinguish between multiple CheckBox objects
sharing the same CheckedChanged event handler.
Alternatively, an event handler for the CheckedChanged event can be registered in code:
C#
};
XAML
<Label Text="Lorem ipsum dolor sit amet, elit rutrum, enim hendrerit augue
vitae praesent sed non, lorem aenean quis praesent pede.">
<Label.Triggers>
<DataTrigger TargetType="Label"
Value="true">
<Setter Property="FontAttributes"
<Setter Property="FontSize"
Value="18" />
</DataTrigger>
</Label.Triggers>
</Label>
In this example, the Label uses a binding expression in a data trigger to monitor the
IsChecked property of the CheckBox. When this property becomes true , the
FontAttributes and FontSize properties of the Label change. When the IsChecked
property returns to false , the FontAttributes and FontSize properties of the Label are
reset to their initial state.
The following screenshot shows the Label formatting when the CheckBox is checked:
Disable a Checkbox
Sometimes an application enters a state where a CheckBox being checked is not a valid
operation. In such cases, the CheckBox can be disabled by setting its IsEnabled property
to false .
CheckBox appearance
In addition to the properties that CheckBox inherits from the View class, CheckBox also
defines a Color property that sets its color to a Color:
XAML
The following screenshot shows a series of checked CheckBox objects, where each
object has its Color property set to a different Color:
The following XAML example shows how to define a visual state for the IsChecked state:
XAML
<CheckBox ...>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Color"
Value="Red" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="IsChecked">
<VisualState.Setters>
<Setter Property="Color"
Value="Green" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</CheckBox>
In this example, the IsChecked VisualState specifies that when the CheckBox is checked,
its Color property will be set to green. The Normal VisualState specifies that when the
CheckBox is in a normal state, its Color property will be set to red. Therefore, the overall
effect is that the CheckBox is red when it's empty, and green when it's checked.
The .NET Multi-platform App UI (.NET MAUI) DatePicker invokes the platform's date-
picker control and allows you to select a date.
MinimumDate of type DateTime, which defaults to the first day of the year 1900.
MaximumDate of type DateTime , which defaults to the last day of the year 2100.
Date of type DateTime , the selected date, which defaults to the value
DateTime.Today.
Format of type string , a standard or custom .NET formatting string, which
defaults to "D", the long date pattern.
TextColor of type Color, the color used to display the selected date.
DatePicker text.
All eight properties are backed by BindableProperty objects, which means that they can
be styled, and the properties can be targets of data bindings. The Date property has a
default binding mode of BindingMode.TwoWay , which means that it can be a target of a
data binding in an application that uses the Model-View-ViewModel (MVVM) pattern.
2 Warning
When setting MinimumDate and MaximumDate , make sure that MinimumDate is always
less than or equal to MaximumDate . Otherwise, DatePicker will raise an exception.
The DatePicker ensures that Date is between MinimumDate and MaximumDate , inclusive. If
MinimumDate or MaximumDate is set so that Date is not between them, DatePicker will
The DatePicker fires a DateSelected event when the user selects a date.
Create a DatePicker
When a DateTime value is specified in XAML, the XAML parser uses the DateTime.Parse
method with a CultureInfo.InvariantCulture argument to convert the string to a
DateTime value. The dates must be specified in a precise format: two-digit months, two-
XAML
<DatePicker MinimumDate="01/01/2022"
MaximumDate="12/31/2022"
Date="06/21/2022" />
XAML
MaximumDate="{Binding MaxDate}"
In this example, all three properties are initialized to the corresponding properties in the
viewmodel. Because the Date property has a binding mode of TwoWay , any new date
that the user selects is automatically reflected in the viewmodel.
If the DatePicker does not contain a binding on its Date property, your app should
attach a handler to the DateSelected event to be informed when the user selects a new
date.
In code, you can initialize the MinimumDate , MaximumDate , and Date properties to values
of type DateTime :
C#
};
XAML
<DatePicker ···
HorizontalOptions="Center" />
However, this is not recommended. Depending on the setting of the Format property,
selected dates might require different display widths. For example, the "D" format string
causes DateTime to display dates in a long format, and "Wednesday, September 12,
2018" requires a greater display width than "Friday, May 4, 2018". Depending on the
platform, this difference might cause the DateTime view to change width in layout, or for
the display to be truncated.
Tip
It's best to use the default HorizontalOptions setting of Fill with DatePicker, and
not to use a width of Auto when putting DatePicker in a Grid cell.
Slider
Article • 04/03/2023 • 6 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Slider is a horizontal bar that you can
manipulate to select a double value from a continuous range.
Minimum , of type double , is the minimum of the range, with a default value of 0.
Maximum , of type double , is the maximum of the range, with a default value of 1.
Value , of type double , is the slider's value, which can range between Minimum and
Maximum and has a default value of 0.
MinimumTrackColor , of type Color, is the bar color on the left side of the thumb.
MaximumTrackColor , of type Color, is the bar color on the right side of the thumb.
ThumbImageSource , of type ImageSource , is the image to use for the thumb, of type
ImageSource .
action.
These properties are backed by BindableProperty objects. The Value property has a
default binding mode of BindingMode.TwoWay , which means that it's suitable as a binding
source in an application that uses the Model-View-ViewModel (MVVM) pattern.
7 Note
The Slider coerces the Value property so that it is between Minimum and Maximum ,
inclusive. If the Minimum property is set to a value greater than the Value property, the
Slider sets the Value property to Minimum . Similarly, if Maximum is set to a value less than
Value , then Slider sets the Value property to Maximum . Internally, the Slider ensures that
Minimum is less than Maximum . If Minimum or Maximum are ever set so that Minimum is not
less than Maximum , an exception is raised. For more information on setting the Minimum
and Maximum properties, see Precautions.
Slider defines a ValueChanged event that's raised when the Value changes, either
through user manipulation of the Slider or when the program sets the Value property
directly. A ValueChanged event is also raised when the Value property is coerced as
described in the previous paragraph. The ValueChangedEventArgs object that
accompanies the ValueChanged event has OldValue and NewValue properties, of type
double . At the time the event is raised, the value of NewValue is the same as the Value
Slider also defines DragStarted and DragCompleted events, that are raised at the
beginning and end of the drag action. Unlike the ValueChanged event, the DragStarted
and DragCompleted events are only raised through user manipulation of the Slider. When
the DragStarted event fires, the DragStartedCommand , of type ICommand , is executed.
Similarly, when the DragCompleted event fires, the DragCompletedCommand , of type
ICommand , is executed.
2 Warning
Do not use unconstrained horizontal layout options of Center , Start , or End with
Slider. Keep the default HorizontalOptions setting of Fill , and don't use a width
of Auto when putting Slider in a Grid layout.
Create a Slider
The following example shows how to create a Slider, with two Label objects:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SliderDemos.BasicSliderXamlPage"
Padding="10, 0">
<StackLayout>
<Label x:Name="rotatingLabel"
Text="ROTATING TEXT"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Slider Maximum="360"
ValueChanged="OnSliderValueChanged" />
<Label x:Name="displayLabel"
Text="(uninitialized)"
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
In this example, the Slider is initialized to have a Maximum property of 360. The second
Label displays the text "(uninitialized)" until the Slider is manipulated, which causes the
first ValueChanged event to be raised.
The code-behind file contains the handler for the ValueChanged event:
C#
public BasicSliderXamlPage()
InitializeComponent();
rotatingLabel.Rotation = value;
The ValueChanged handler of the Slider uses the Value property of the slider object to
set the Rotation property of the first Label and uses the String.Format method with the
NewValue property of the event arguments to set the Text property of the second Label:
It's also possible for the event handler to obtain the Slider that is firing the event
through the sender argument. The Value property contains the current value:
C#
If the Slider object were given a name in the XAML file with an x:Name attribute (for
example, "slider"), then the event handler could reference that object directly:
C#
C#
Maximum = 360
};
rotationLabel.Rotation = slider.Value;
};
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SliderDemos.BasicSliderBindingsPage"
Padding="10, 0">
<StackLayout>
Path=Value}"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Slider x:Name="slider"
Maximum="360" />
<Label x:Name="displayLabel"
Path=Value,
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
In this example, the Rotation property of the first Label is bound to the Value property
of the Slider, as is the Text property of the second Label with a StringFormat
specification. When the page first appears, the second Label displays the text string with
the value. To display text without data binding, you'd need to specifically initialize the
Text property of the Label or simulate a firing of the ValueChanged event by calling the
Precautions
The value of the Minimum property must always be less than the value of the Maximum
property. The following example causes the Slider to raise an exception:
C#
// Throws an exception!
Minimum = 10,
Maximum = 20
};
The C# compiler generates code that sets these two properties in sequence, and when
the Minimum property is set to 10, it is greater than the default Maximum value of 1. You
can avoid the exception in this case by setting the Maximum property first:
C#
Maximum = 20,
Minimum = 10
};
In this example, setting Maximum to 20 is not a problem because it is greater than the
default Minimum value of 0. When Minimum is set, the value is less than the Maximum value
of 20.
The same problem exists in XAML. The properties must be set in an order that ensures
that Maximum is always greater than Minimum :
XAML
<Slider Maximum="20"
You can set the Minimum and Maximum values to negative numbers, but only in an order
where Minimum is always less than Maximum :
XAML
<Slider Minimum="-20"
The Value property is always greater than or equal to the Minimum value and less than
or equal to Maximum . If Value is set to a value outside that range, the value will be
coerced to lie within the range, but no exception is raised. For example, the following
example won't raise an exception:
C#
Value = 10
};
C#
Maximum = 20,
Minimum = 10
};
XAML
<Slider ValueChanged="OnSliderValueChanged"
Maximum="20"
Minimum="10" />
When Minimum is set to 10, Value is also set to 10, and the ValueChanged event is raised.
This might occur before the rest of the page has been constructed, and the handler
might attempt to reference other elements on the page that have not yet been created.
You might want to add some code to the ValueChanged handler that checks for null
values of other elements on the page. Or, you can set the ValueChanged event handler
after the Slider values have been initialized.
Stepper
Article • 12/23/2022 • 6 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Stepper enables a numeric value to be
selected from a range of values. It consists of two buttons labeled with minus and plus
signs. These buttons can be manipulated by the user to incrementally select a double
value from a range of values.
Increment is the amount to change the selected value by, with a default value of 1.
Minimum is the minimum of the range, with a default value of 0.
All of these properties are backed by BindableProperty objects. The Value property has
a default binding mode of BindingMode.TwoWay , which means that it's suitable as a
binding source in an application that uses the Model-View-ViewModel (MVVM) pattern.
The Stepper coerces the Value property so that it is between Minimum and Maximum ,
inclusive. If the Minimum property is set to a value greater than the Value property, the
Stepper sets the Value property to Minimum . Similarly, if Maximum is set to a value less
than Value , then Stepper sets the Value property to Maximum . Internally, the Stepper
ensures that Minimum is less than Maximum . If Minimum or Maximum are ever set so that
Minimum is not less than Maximum , an exception is raised. For more information on setting
Stepper defines a ValueChanged event that's raised when the Value changes, either
through user manipulation of the Stepper or when the application sets the Value
property directly. A ValueChanged event is also raised when the Value property is
coerced as previously described. The ValueChangedEventArgs object that accompanies
the ValueChanged event has OldValue and NewValue , of type double . At the time the
event is raised, the value of NewValue is the same as the Value property of the Stepper
object.
Create a Stepper
The following example shows how to create a Stepper, with two Label objects:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StepperDemo.BasicStepperXAMLPage"
<StackLayout Margin="20">
<Label x:Name="_rotatingLabel"
Text="ROTATING TEXT"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Stepper Maximum="360"
Increment="30"
HorizontalOptions="Center"
ValueChanged="OnStepperValueChanged" />
<Label x:Name="_displayLabel"
Text="(uninitialized)"
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
In this example, the Stepper is initialized to have a Maximum property of 360, and an
Increment property of 30. Manipulating the Stepper changes the selected value
incrementally between Minimum to Maximum based on the value of the Increment
property. The second Label displays the text "(uninitialized)" until the Stepper is
manipulated, which causes the first ValueChanged event to be raised.
The code-behind file contains the handler for the ValueChanged event:
C#
public BasicStepperXAMLPage()
InitializeComponent();
_rotatingLabel.Rotation = value;
The ValueChanged handler of the Stepper uses the Value property of the stepper object
to set the Rotation property of the first Label and uses the string.Format method with
the NewValue property of the event arguments to set the Text property of the second
Label:
It's also possible for the event handler to obtain the Stepper that is firing the event
through the sender argument. The Value property contains the current value:
C#
If the Stepper object were given a name in the XAML file with an x:Name attribute (for
example, "stepper"), then the event handler could reference that object directly:
C#
C#
Maximum = 360,
Increment = 30,
HorizontalOptions = LayoutOptions.Center
};
rotationLabel.Rotation = stepper.Value;
};
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StepperDemo.BasicStepperBindingsPage"
<StackLayout Margin="20">
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Stepper x:Name="_stepper"
Maximum="360"
Increment="30"
HorizontalOptions="Center" />
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
In this example, the Rotation property of the first Label is bound to the Value property
of the Stepper, as is the Text property of the second Label with a StringFormat
specification. When the page first appears, the second Label displays the text string with
the value. To display text without data binding, you'd need to specifically initialize the
Text property of the Label or simulate a firing of the ValueChanged event by calling the
event handler from the class constructor.
Precautions
The value of the Minimum property must always be less than the value of the Maximum
property. The following code example causes the Stepper to raise an exception:
C#
// Throws an exception!
Minimum = 180,
Maximum = 360
};
The C# compiler generates code that sets these two properties in sequence, and when
the Minimum property is set to 180, it is greater than the default Maximum value of 100.
You can avoid the exception in this case by setting the Maximum property first:
C#
Maximum = 360,
Minimum = 180
};
In this example, setting Maximum to 360 is not a problem because it is greater than the
default Minimum value of 0. When Minimum is set, the value is less than the Maximum value
of 360.
The same problem exists in XAML. Set the properties in an order that ensures that
Maximum is always greater than Minimum :
XAML
<Stepper Maximum="360"
You can set the Minimum and Maximum values to negative numbers, but only in an order
where Minimum is always less than Maximum :
XAML
<Stepper Minimum="-360"
The Value property is always greater than or equal to the Minimum value and less than
or equal to Maximum . If Value is set to a value outside that range, the value will be
coerced to lie within the range, but no exception is raised. For example, this code won't
raise an exception:
C#
Value = 180
};
C#
Maximum = 360,
Minimum = 180
};
If a ValueChanged event handler has been attached at the time that the Value property
is coerced to something other than its default value of 0, then a ValueChanged event is
raised:
XAML
<Stepper ValueChanged="OnStepperValueChanged"
Maximum="360"
Minimum="180" />
When Minimum is set to 180, Value is also set to 180, and the ValueChanged event is
raised. This might occur before the rest of the page has been constructed, and the
handler might attempt to reference other elements on the page that have not yet been
created. You might want to add some code to the ValueChanged handler that checks for
null values of other elements on the page. Or, you can set the ValueChanged event
The .NET Multi-platform App UI (.NET MAUI) Switch control is a horizontal toggle button
that can be manipulated by the user to toggle between on and off states, which are
represented by a boolean value.
The following screenshot shows a Switch control in its on and off toggle states:
IsToggled is a boolean value that indicates whether the Switch is on. The default
value of this property is false .
OnColor is a Color that affects how the Switch is rendered in the toggled, or on
state.
ThumbColor is the Color of the switch thumb.
These properties are backed by BindableProperty objects, which means they can be
styled and be the target of data bindings.
The Switch control defines a Toggled event that's raised when the IsToggled property
changes, either through user manipulation or when an application sets the IsToggled
property. The ToggledEventArgs object that accompanies the Toggled event has a single
property named Value , of type bool . When the event is raised, the value of the Value
property reflects the new value of the IsToggled property.
Create a Switch
A Switch can be instantiated in XAML. Its IsToggled property can be set to toggle the
Switch. By default, the IsToggled property is false . The following example shows how
to instantiate a Switch in XAML with the optional IsToggled property set:
XAML
<Switch IsToggled="true"/>
C#
Switch appearance
In addition to the properties that Switch inherits from the View class, Switch also defines
OnColor and ThumbColor properties. The OnColor property can be set to define the
Switch color when it is toggled to its on state, and the ThumbColor property can be set
to define the Color of the switch thumb. The following example shows how to
instantiate a Switch in XAML with these properties set:
XAML
<Switch OnColor="Orange"
ThumbColor="Green" />
C#
The following screenshot shows the Switch in its on and off toggle states, with the
OnColor and ThumbColor properties set:
XAML
<Switch Toggled="OnToggled" />
The code-behind file contains the handler for the Toggled event:
C#
The sender argument in the event handler is the Switch responsible for firing this event.
You can use the sender property to access the Switch object, or to distinguish between
multiple Switch objects sharing the same Toggled event handler.
C#
};
XAML
<Label Text="Lorem ipsum dolor sit amet, elit rutrum, enim hendrerit augue
vitae praesent sed non, lorem aenean quis praesent pede.">
<Label.Triggers>
<DataTrigger TargetType="Label"
Value="true">
<Setter Property="FontAttributes"
<Setter Property="FontSize"
Value="18" />
</DataTrigger>
</Label.Triggers>
</Label>
In this example, the Label uses a binding expression in a DataTrigger to monitor the
IsToggled property of the Switch named styleSwitch . When this property becomes
true , the FontAttributes and FontSize properties of the Label are changed. When the
The following XAML example shows how to define visual states for the On and Off
states:
XAML
<Switch IsToggled="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="On">
<VisualState.Setters>
<Setter Property="ThumbColor"
Value="MediumSpringGreen" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Off">
<VisualState.Setters>
<Setter Property="ThumbColor"
Value="Red" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Switch>
In this example, the On VisualState specifies that when the IsToggled property is true ,
the ThumbColor property will be set to medium spring green. The Off VisualState
specifies that when the IsToggled property is false , the ThumbColor property will be set
to red. Therefore, the overall effect is that when the Switch is in an off position its thumb
is red, and its thumb is medium spring green when the Switch is in an on position:
Disable a Switch
An app may enter a state where the Switch being toggled is not a valid operation. In
such cases, the Switch can be disabled by setting its IsEnabled property to false . This
will prevent users from being able to manipulate the Switch.
TimePicker
Article • 03/27/2023 • 3 minutes to read
The .NET Multi-platform App UI (.NET MAUI) TimePicker invokes the platform's time-
picker control and allows you to select a time.
Time of type TimeSpan , the selected time, which defaults to a TimeSpan of 0. The
TimePicker text.
All of these properties are backed by BindableProperty objects, which means that they
can be styled, and the properties can be targets of data bindings. The Time property has
a default binding mode of BindingMode.TwoWay , which means that it can be a target of a
data binding in an application that uses the Model-View-ViewModel (MVVM) pattern.
7 Note
The TimePicker doesn't include an event to indicate a new selected Time value. If
you need to be notified of this, you can add an event handler for the
PropertyChanged event.
Create a TimePicker
When the Time property is specified in XAML, the value is converted to a TimeSpan and
validated to ensure that the number of milliseconds is greater than or equal to 0, and
that the number of hours is less than 24. The time components should be separated by
colons:
XAML
<TimePicker Time="4:15:26" />
XAML
In this example, the Time property is initialized to the SelectedTime property in the
viewmodel. Because the Time property has a binding mode of TwoWay , any new time
that the user selects is automatically propagated to the viewmodel.
In code, you can initialize the Time property to a value of type TimeSpan :
C#
};
XAML
<TimePicker ···
HorizontalOptions="Center" />
However, this is not recommended. Depending on the setting of the Format property,
selected times might require different display widths. For example, the "T" format string
causes the TimePicker view to display times in a long format, and "4:15:26 AM" requires
a greater display width than the short time format ("t") of "4:15 AM". Depending on the
platform, this difference might cause the TimePicker view to change width in layout, or
for the display to be truncated.
Tip
It's best to use the default HorizontalOptions setting of Fill with TimePicker, and
not to use a width of Auto when putting TimePicker in a Grid cell.
Platform differences
This section describes the platform-specific differences with the TimePicker control.
Windows
On Windows, the Format property only affects whether the hour is formatted for
12-hours or 24-hours. Other settings from the Format property are ignored. When
the picker is shown by pressing on the control, only the hour, minute, and time of
day can be changed. For more information about the Windows control, see Time
picker - Windows apps.
Editor
Article • 04/03/2023 • 7 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Editor allows you to enter and edit multiple
lines of text.
size to accommodate user input. By default, the editor doesn't auto size.
CharacterSpacing , of type double , sets the spacing between characters in the
entered text.
CursorPosition , of type int , defines the position of the cursor within the editor.
FontAttributes , of type FontAttributes , determines text style.
FontAutoScalingEnabled , of type bool , defines whether the text will reflect scaling
preferences set in the operating system. The default value of this property is true .
FontFamily , of type string , defines the font family.
of the text.
IsTextPredictionEnabled , of type bool , controls whether text prediction and
automatic text correction is enabled.
Placeholder , of type string , defines the text that's displayed when the control is
empty.
PlaceholderColor , of type Color, defines the color of the placeholder text.
SelectionLength , of type int , represents the length of selected text within the
editor.
Text , of type string , defines the text entered into the editor.
TextColor , of type Color, defines the color of the entered text.
the text.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
In addition, Editor defines a Completed event, which is raised when the user finalizes text
in the Editor with the return key.
Editor derives from the InputView class, from which it inherits the following properties:
IsReadOnly , of type bool , defines whether the user should be prevented from
Keyboard , of type Keyboard , specifies the virtual keyboard that's displayed when
entering text.
MaxLength , of type int , defines the maximum input length.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
In addition, InputView defines a TextChanged event, which is raised when the text in the
Editor changes. The TextChangedEventArgs object that accompanies the TextChanged
event has NewTextValue and OldTextValue properties, which specify the new and old
text, respectively.
Create an Editor
The following example shows how to create an Editor:
XAML
<Editor x:Name="editor"
HeightRequest="250"
TextChanged="OnEditorTextChanged"
Completed="OnEditorCompleted" />
C#
editor.TextChanged += OnEditorTextChanged;
editor.Completed += OnEditorCompleted;
The TextChanged event is raised when the text in the Editor changes, and the
TextChangedEventArgs provide the text before and after the change via the OldTextValue
C#
The Completed event is raised when the user has ended input by pressing the return key
on the keyboard, or by pressing the Tab key on Windows. The handler for the event is a
generic event handler:
C#
XAML
<Editor ...
CharacterSpacing="10" />
The result is that characters in the text displayed by the Editor are spaced
CharacterSpacing device-independent units apart.
7 Note
The CharacterSpacing property value is applied to the text displayed by the Text
and Placeholder properties.
XAML
A MaxLength property value of 0 indicates that no input will be allowed, and a value of
int.MaxValue , which is the default value for an Editor, indicates that there is no effective
Auto-size an Editor
An Editor can be made to auto-size to its content by setting the Editor.AutoSize
property to TextChanges , which is a value of the EditorAutoSizeOption enumeration.
This enumeration has two values:
Disabled indicates that automatic resizing is disabled, and is the default value.
TextChanges indicates that automatic resizing is enabled.
XAML
AutoSize="TextChanges" />
When auto-resizing is enabled, the height of the Editor will increase when the user fills it
with text, and the height will decrease as the user deletes text.
7 Note
An Editor will not auto-size if the HeightRequest property has been set.
Transform text
An Editor can transform the casing of its text, stored in the Text property, by setting the
TextTransform property to a value of the TextTransform enumeration. This enumeration
Default indicates that the default behavior for the platform will be used. This is
XAML
TextTransform="Uppercase" />
Chat – used for texting and places where emoji are useful.
The Keyboard class also has a Create factory method that can be used to customize a
keyboard by specifying capitalization, spellcheck, and suggestion behavior.
KeyboardFlags enumeration values are specified as arguments to the method, with a
customized Keyboard being returned. The KeyboardFlags enumeration contains the
following values:
CapitalizeWord – indicates that the first letter of each word will be automatically
capitalized.
CapitalizeCharacter – indicates that every character will be automatically
capitalized.
CapitalizeNone – indicates that no automatic capitalization will occur.
All – indicates that spellcheck, word completions, and sentence capitalization will
The following XAML code example shows how to customize the default Keyboard to
offer word completions and capitalize every entered character:
XAML
<Editor>
<Editor.Keyboard>
<Keyboard x:FactoryMethod="Create">
<x:Arguments>
<KeyboardFlags>Suggestions,CapitalizeCharacter</KeyboardFlags>
</x:Arguments>
</Keyboard>
</Editor.Keyboard>
</Editor>
C#
Editor editor = new Editor();
editor.Keyboard = Keyboard.Create(KeyboardFlags.Suggestions |
KeyboardFlags.CapitalizeCharacter);
However, for some text entry scenarios, such as entering a username, spell checking
provides a negative experience and so should be disabled by setting the
IsSpellCheckEnabled property to false :
XAML
7 Note
However, for some text entry scenarios, such as entering a username, text prediction
and automatic text correction provides a negative experience and should be disabled by
setting the IsTextPredictionEnabled property to false :
XAML
7 Note
used to enable text prediction for a Keyboard that explicitly disables it.
XAML
IsReadOnly="true" />
7 Note
The IsReadonly property does not alter the visual appearance of an Editor, unlike
the IsEnabled property that also changes the visual appearance of the Editor to
gray.
Entry
Article • 04/03/2023 • 9 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Entry allows you to enter and edit a single
line of text. In addition, the Entry can be used as a password field.
entered text.
ClearButtonVisibility , of type ClearButtonVisibility , controls whether a clear
button is displayed, which enables the user to clear the text. The default value of
this property ensures that a clear button isn't displayed.
CursorPosition , of type int , defines the position of the cursor within the entry.
FontAutoScalingEnabled , of type bool , defines whether the text will reflect scaling
preferences set in the operating system. The default value of this property is true .
FontFamily , of type string , defines the font family.
FontSize , of type double , defines the font size.
Keyboard , of type Keyboard , specifies the virtual keyboard that's displayed when
entering text.
HorizontalTextAlignment , of type TextAlignment , defines the horizontal alignment
of the text.
IsPassword , of type bool , specifies whether the entry should visually obscure
typed text.
IsTextPredictionEnabled , of type bool , controls whether text prediction and
automatic text correction is enabled.
Placeholder , of type string , defines the text that's displayed when the control is
empty.
PlaceholderColor , of type Color, defines the color of the placeholder text.
SelectionLength , of type int , represents the length of selected text within the
entry.
Text , of type string , defines the text entered into the entry.
TextColor , of type Color, defines the color of the entered text.
VerticalTextAlignment , of type TextAlignment , defines the vertical alignment of
the text.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
In addition, Entry defines a Completed event, which is raised when the user finalizes text
in the Entry with the return key.
Entry derives from the InputView class, from which it inherits the following properties:
IsReadOnly , of type bool , defines whether the user should be prevented from
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
In addition, InputView defines a TextChanged event, which is raised when the text in the
Entry changes. The TextChangedEventArgs object that accompanies the TextChanged
event has NewTextValue and OldTextValue properties, which specify the new and old
text, respectively.
Create an Entry
The following example shows how to create an Entry:
XAML
<Entry x:Name="entry"
Placeholder="Enter text"
TextChanged="OnEntryTextChanged"
Completed="OnEntryCompleted" />
C#
entry.TextChanged += OnEntryTextChanged;
entry.Completed += OnEntryCompleted;
Entered text can be accessed by reading the Text property, and the TextChanged and
Completed events signal that the text has changed or been completed.
The TextChanged event is raised when the text in the Entry changes, and the
TextChangedEventArgs provide the text before and after the change via the OldTextValue
C#
The Completed event is raised when the user has ended input by pressing the return key
on the keyboard, or by pressing the Tab key on Windows. The handler for the event is a
generic event handler:
C#
After the Completed event fires, any ICommand specified by the ReturnCommand property is
executed, with the object specified by the ReturnCommandParameter property being
passed to the ReturnCommand .
7 Note
The VisualElement class, which is in the Entry inheritance hierarchy, also has
Focused and Unfocused events.
Set character spacing
Character spacing can be applied to an Entry by setting the CharacterSpacing property
to a double value:
XAML
<Entry ...
CharacterSpacing="10" />
The result is that characters in the text displayed by the Entry are spaced
CharacterSpacing device-independent units apart.
7 Note
The CharacterSpacing property value is applied to the text displayed by the Text
and Placeholder properties.
XAML
<Entry ...
MaxLength="10" />
A MaxLength property value of 0 indicates that no input will be allowed, and a value of
int.MaxValue , which is the default value for an Entry, indicates that there is no effective
limit on the number of characters that may be entered.
XAML
<Entry Text="Cursor position set"
CursorPosition="5" />
The default value of the CursorPosition property is 0, which indicates that text will be
inserted at the start of the Entry.
In addition, the SelectionLength property can be used to return or set the length of text
selection within the Entry:
XAML
CursorPosition="2"
SelectionLength="10" />
The default value of the SelectionLength property is 0, which indicates that no text is
selected.
Never indicates that a clear button will never be displayed. This is the default value
for the ClearButtonVisibility property.
WhileEditing indicates that a clear button will be displayed in the Entry, while it
XAML
ClearButtonVisibility="WhileEditing" />
The following screenshot shows an Entry on Android with the clear button enabled:
Transform text
An Entry can transform the casing of its text, stored in the Text property, by setting the
TextTransform property to a value of the TextTransform enumeration. This enumeration
Default indicates that the default behavior for the platform will be used. This is
the default value of the TextTransform property.
Lowercase indicates that the text will be transformed to lowercase.
XAML
TextTransform="Uppercase" />
XAML
The following screenshot shows an Entry whose input has been obscured:
Chat – used for texting and places where emoji are useful.
Default – the default keyboard.
XAML
The Keyboard class also has a Create factory method that can be used to customize a
keyboard by specifying capitalization, spellcheck, and suggestion behavior.
KeyboardFlags enumeration values are specified as arguments to the method, with a
CapitalizeWord – indicates that the first letter of each word will be automatically
capitalized.
CapitalizeCharacter – indicates that every character will be automatically
capitalized.
CapitalizeNone – indicates that no automatic capitalization will occur.
All – indicates that spellcheck, word completions, and sentence capitalization will
occur on entered text.
The following XAML code example shows how to customize the default Keyboard to
offer word completions and capitalize every entered character:
XAML
<Entry.Keyboard>
<Keyboard x:FactoryMethod="Create">
<x:Arguments>
<KeyboardFlags>Suggestions,CapitalizeCharacter</KeyboardFlags>
</x:Arguments>
</Keyboard>
</Entry.Keyboard>
</Entry>
C#
entry.Keyboard = Keyboard.Create(KeyboardFlags.Suggestions |
KeyboardFlags.CapitalizeCharacter);
Default – indicates that no specific return key is required and that the platform
The following XAML example shows how to set the return key:
XAML
7 Note
The exact appearance of the return key is dependent upon the platform. On iOS,
the return key is a text-based button. However, on Android and Windows, the
return key is a icon-based button.
When the return key is pressed, the Completed event fires and any ICommand specified by
the ReturnCommand property is executed. In addition, any object specified by the
ReturnCommandParameter property will be passed to the ICommand as a parameter. For
However, for some text entry scenarios, such as entering a username, spell checking
provides a negative experience and should be disabled by setting the
IsSpellCheckEnabled property to false :
XAML
7 Note
However, for some text entry scenarios, such as entering a username, text prediction
and automatic text correction provides a negative experience and should be disabled by
setting the IsTextPredictionEnabled property to false :
XAML
7 Note
used to enable text prediction for a Keyboard that explicitly disables it.
XAML
IsReadOnly="true" />
7 Note
The IsReadonly property does not alter the visual appearance of an Entry, unlike
the IsEnabled property that also changes the visual appearance of the Entry to
gray.
ActivityIndicator
Article • 04/03/2023 • 2 minutes to read
visible and animating, or hidden. The default value of this property is false , which
indicates that the ActivityIndicator isn't visible.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
Create an ActivityIndicator
To indicate a lengthy activity, create an ActivityIndicator object and sets its properties to
define its appearance.
XAML
C#
ActivityIndicator activityIndicator = new ActivityIndicator { IsRunning =
true };
The following XAML example shows how to change the color of an ActivityIndicator:
XAML
<ActivityIndicator IsRunning="true"
Color="Orange" />
C#
IsRunning = true,
Color = Colors.Orange
};
ProgressBar
Article • 04/03/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) ProgressBar indicates to users that the app
is progressing through a lengthy activity. The progress bar is a horizontal bar that is
filled to a percentage represented by a double value.
Progress is a double value that represents the current progress as a value from 0
to 1. Progress values less than 0 will be clamped to 0, values greater than 1 will be
clamped to 1. The default value of this property is 0.
ProgressColor is a Color values that defines the color of the ProgressBar.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
ProgressBar also defines a ProgressTo method that animates the bar from its current
value to a specified value. For more information, see Animate a ProgressBar.
Create a ProgressBar
To indicate progress through a lengthy activity, create a ProgressBar object and set its
properties to define its appearance.
XAML
2 Warning
Do not use unconstrained horizontal layout options such as Center , Start , or End
with ProgressBar. Keep the default HorizontalOptions value of Fill .
The following XAML example shows how to change the color of a ProgressBar:
XAML
<ProgressBar Progress="0.5"
ProgressColor="Orange" />
C#
Progress = 0.5,
ProgressColor = Colors.Orange
};
Animate a ProgressBar
The ProgressTo method animates the ProgressBar from its current Progress value to a
provided value over time. The method accepts a double progress value, a uint duration
in milliseconds, an Easing enum value and returns a Task<bool> . The following example
demonstrates how to animate a ProgressBar:
C#
For more information about the Easing enumeration, see Easing functions.
CarouselView
Article • 12/23/2022 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CarouselView is a view for presenting data
in a scrollable layout, where users can swipe to move through a collection of items.
By default, CarouselView will display its items in a horizontal orientation. A single item
will be displayed on screen, with swipe gestures resulting in forwards and backwards
navigation through the collection of items. In addition, indicators can be displayed that
represent each item in the CarouselView:
CarouselView shares much of its implementation with CollectionView. However, the two
controls have different use cases. CollectionView is typically used to present lists of data
of any length, whereas CarouselView is typically used to highlight information in a list of
limited length. For more information about CollectionView, see CollectionView.
Populate a CarouselView with data
Article • 04/03/2023 • 9 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CarouselView includes the following
properties that define the data to be displayed, and its appearance:
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
CarouselView supports incremental data virtualization as the user scrolls. For more
information, see Load data incrementally.
) Important
CarouselView can be populated with data by using data binding to bind its ItemsSource
property to an IEnumerable collection. In XAML, this is achieved with the Binding
markup extension:
XAML
<CarouselView ItemsSource="{Binding Monkeys}" />
C#
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
In this example, the ItemsSource property data binds to the Monkeys property of the
connected viewmodel.
7 Note
XAML
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<StackLayout>
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
Aspect="AspectFill"
HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center" />
FontAttributes="Italic"
HorizontalOptions="Center"
MaxLines="5"
LineBreakMode="TailTruncation" />
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
C#
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
nameLabel.SetBinding(Label.TextProperty, "Name");
image.SetBinding(Image.SourceProperty, "ImageUrl");
locationLabel.SetBinding(Label.TextProperty, "Location");
detailsLabel.SetBinding(Label.TextProperty, "Details");
stackLayout.Add(nameLabel);
stackLayout.Add(image);
stackLayout.Add(locationLabel);
stackLayout.Add(detailsLabel);
rootStackLayout.Add(frame);
return rootStackLayout;
});
The elements specified in the DataTemplate define the appearance of each item in the
CarouselView. In the example, layout within the DataTemplate is managed by a
StackLayout, and the data is displayed with an Image object, and three Label objects,
that all bind to properties of the Monkey class:
C#
XAML
<ContentPage ...
xmlns:controls="clr-namespace:CarouselViewDemos.Controls"
x:Class="CarouselViewDemos.Views.HorizontalLayoutDataTemplateSelectorPage">
<ContentPage.Resources>
<DataTemplate x:Key="AmericanMonkeyTemplate">
...
</DataTemplate>
<DataTemplate x:Key="OtherMonkeyTemplate">
...
</DataTemplate>
<controls:MonkeyDataTemplateSelector x:Key="MonkeySelector"
AmericanMonkey="{StaticResource
AmericanMonkeyTemplate}"
OtherMonkey="{StaticResource
OtherMonkeyTemplate}" />
</ContentPage.Resources>
</ContentPage>
C#
};
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
C#
For more information about data template selectors, see Create a DataTemplateSelector.
) Important
When using CarouselView, never set the root element of your DataTemplate
objects to a ViewCell. This will result in an exception being thrown because
CarouselView has no concept of cells.
Display indicators
Indicators, that represent the number of items and current position in a CarouselView,
can be displayed next to the CarouselView. This can be accomplished with the
IndicatorView control:
XAML
<StackLayout>
IndicatorView="indicatorView">
<CarouselView.ItemTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
HorizontalOptions="Center" />
</StackLayout>
) Important
Context menus
CarouselView supports context menus for items of data through the SwipeView, which
reveals the context menu with a swipe gesture. The SwipeView is a container control
that wraps around an item of content, and provides context menu items for that item of
content. Therefore, context menus are implemented for a CarouselView by creating a
SwipeView that defines the content that the SwipeView wraps around, and the context
menu items that are revealed by the swipe gesture. This is achieved by adding a
SwipeView to the DataTemplate that defines the appearance of each item of data in the
CarouselView:
XAML
<CarouselView x:Name="carouselView"
ItemsSource="{Binding Monkeys}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<SwipeView>
<SwipeView.TopItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Command="{Binding Source=
{x:Reference carouselView}, Path=BindingContext.FavoriteCommand}"
CommandParameter="{Binding}"
/>
</SwipeItems>
</SwipeView.TopItems>
<SwipeView.BottomItems>
<SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Command="{Binding Source=
{x:Reference carouselView}, Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding}"
/>
</SwipeItems>
</SwipeView.BottomItems>
<StackLayout>
</StackLayout>
</SwipeView>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
C#
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
Text = "Favorite",
IconImageSource = "favorite.png",
BackgroundColor = Colors.LightGreen
};
favoriteSwipeItem.SetBinding(MenuItem.CommandProperty, new
Binding("BindingContext.FavoriteCommand", source: carouselView));
favoriteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");
Text = "Delete",
IconImageSource = "delete.png",
BackgroundColor = Colors.LightPink
};
deleteSwipeItem.SetBinding(MenuItem.CommandProperty, new
Binding("BindingContext.DeleteCommand", source: carouselView));
deleteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");
swipeView.Content = swipeViewStackLayout;
frame.Content = swipeView;
stackLayout.Add(frame);
return stackLayout;
});
In this example, the SwipeView content is a StackLayout that defines the appearance of
each item that's surrounded by a Frame in the CarouselView. The swipe items are used
to perform actions on the SwipeView content, and are revealed when the control is
swiped from the bottom and from the top:
SwipeView supports four different swipe directions, with the swipe direction being
defined by the directional SwipeItems collection the SwipeItems objects are added to.
By default, a swipe item is executed when it's tapped by the user. In addition, once a
swipe item has been executed the swipe items are hidden and the SwipeView content is
re-displayed. However, these behaviors can be changed.
Pull to refresh
CarouselView supports pull to refresh functionality through the RefreshView, which
enables the data being displayed to be refreshed by pulling down on the items. The
RefreshView is a container control that provides pull to refresh functionality to its child,
provided that the child supports scrollable content. Therefore, pull to refresh is
implemented for a CarouselView by setting it as the child of a RefreshView:
XAML
Command="{Binding RefreshCommand}">
...
</CarouselView>
</RefreshView>
C#
// IsRefreshing is true
refreshView.IsRefreshing = false;
});
refreshView.Command = refreshCommand;
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
refreshView.Content = carouselView;
// ...
When the user initiates a refresh, the ICommand defined by the Command property is
executed, which should refresh the items being displayed. A refresh visualization is
shown while the refresh occurs, which consists of an animated progress circle:
The value of the RefreshView.IsRefreshing property indicates the current state of the
RefreshView. When a refresh is triggered by the user, this property will automatically
transition to true . Once the refresh completes, you should reset the property to false .
The default value of the RemainingItemsThreshold property is -1, which indicates that the
RemainingItemsThresholdReached event will never be fired. When the property value is 0,
the RemainingItemsThresholdReached event will be fired when the final item in the
ItemsSource is displayed. For values greater than 0, the RemainingItemsThresholdReached
event will be fired when the ItemsSource contains that number of items not yet scrolled
to.
7 Note
The following XAML example shows a CarouselView that loads data incrementally:
XAML
RemainingItemsThreshold="2"
RemainingItemsThresholdReached="OnCarouselViewRemainingItemsThresholdReached
"
RemainingItemsThresholdReachedCommand="{Binding
LoadMoreDataCommand}">
...
</CarouselView>
RemainingItemsThreshold = 2
};
carouselView.RemainingItemsThresholdReached +=
OnCollectionViewRemainingItemsThresholdReached;
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
In this code example, the RemainingItemsThresholdReached event fires when there are 2
items not yet scrolled to, and in response executes the
OnCollectionViewRemainingItemsThresholdReached event handler:
C#
7 Note
viewmodel.
Specify CarouselView layout
Article • 04/03/2023 • 4 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CarouselView defines the following
properties that control layout:
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
By default, a CarouselView will display its items in a horizontal orientation. A single item
will be displayed on screen, with swipe gestures resulting in forwards and backwards
navigation through the collection of items. However, a vertical orientation is also
possible. This is because the ItemsLayout property is of type LinearItemsLayout , which
inherits from the ItemsLayout class. The ItemsLayout class defines the following
properties:
when scrolling.
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings. For more information about snap points, see
Snap points in Control scrolling in a CarouselView guide.
Vertical indicates that the CarouselView will expand vertically as items are added.
Horizontal indicates that the CarouselView will expand horizontally as items are
added.
The LinearItemsLayout class inherits from the ItemsLayout class, and defines an
ItemSpacing property, of type double , that represents the empty space around each
item. The default value of this property is 0, and its value must always be greater than or
equal to 0. The LinearItemsLayout class also defines static Vertical and Horizontal
members. These members can be used to create vertical or horizontal lists, respectively.
Alternatively, a LinearItemsLayout object can be created, specifying an
ItemsLayoutOrientation enumeration member as an argument.
7 Note
Horizontal layout
By default, CarouselView will display its items horizontally. Therefore, it's not necessary
to set the ItemsLayout property to use this layout:
XAML
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<StackLayout>
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
Aspect="AspectFill"
HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center" />
FontAttributes="Italic"
HorizontalOptions="Center"
MaxLines="5"
LineBreakMode="TailTruncation" />
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
Alternatively, this layout can also be accomplished by setting the ItemsLayout property
to a LinearItemsLayout object, specifying the Horizontal ItemsLayoutOrientation
enumeration member as the Orientation property value:
XAML
</CarouselView.ItemsLayout>
...
</CarouselView>
C#
...
ItemsLayout = LinearItemsLayout.Horizontal
};
This results in a layout that grows horizontally as new items are added.
Vertical layout
CarouselView can display its items vertically by setting the ItemsLayout property to a
LinearItemsLayout object, specifying the Vertical ItemsLayoutOrientation
enumeration member as the Orientation property value:
XAML
</CarouselView.ItemsLayout>
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<StackLayout>
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
Aspect="AspectFill"
HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center" />
FontAttributes="Italic"
HorizontalOptions="Center"
MaxLines="5"
LineBreakMode="TailTruncation" />
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
C#
...
ItemsLayout = LinearItemsLayout.Vertical
};
This results in a layout that grows vertically as new items are added.
PeekAreaInsets="100">
...
</CarouselView>
C#
...
};
Item spacing
By default, there is no space between each item in a CarouselView. This behavior can be
changed by setting the ItemSpacing property on the items layout used by the
CarouselView.
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="20" />
</CarouselView.ItemsLayout>
...
</CarouselView>
7 Note
C#
...
ItemSpacing = 20
};
This code results in a vertical layout that has a spacing of 20 between items.
C#
image.HeightRequest = image.WidthRequest =
image.HeightRequest.Equals(150) ? 200 : 150;
Right-to-left layout
CarouselView can layout its content in a right-to-left flow direction by setting its
FlowDirection property to RightToLeft . However, the FlowDirection property should
ideally be set on a page or root layout, which causes all the elements within the page, or
root layout, to respond to the flow direction:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CarouselViewDemos.Views.HorizontalTemplateLayoutRTLPage"
FlowDirection="RightToLeft">
...
</CarouselView>
</ContentPage>
The default FlowDirection for an element with a parent is MatchParent . Therefore, the
CarouselView inherits the FlowDirection property value from the ContentPage.
Configure CarouselView interaction
Article • 04/03/2023 • 8 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CarouselView defines the following
properties that control user interaction:
CurrentItem , of type object , the current item being displayed. This property has a
default binding mode of TwoWay , and has a null value when there isn't any data to
display.
CurrentItemChangedCommand , of type ICommand , which is executed when the current
item changes.
CurrentItemChangedCommandParameter , of type object , which is the parameter that's
This property has a default binding mode of TwoWay , and has a 0 value when there
isn't any data to display.
PositionChangedCommand , of type ICommand , which is executed when the position
changes.
PositionChangedCommandParameter , of type object , which is the parameter that's
that contains the objects for the items that are currently visible.
All of these properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
CarouselView also defines a PositionChanged event that's fired when the Position
property changes, either due to user scrolling, or when an application sets the property.
The PositionChangedEventArgs object that accompanies the PositionChanged event has
two properties, both of type int :
fires.
) Important
The Position property changes when the CurrentItem property changes. This will
result in the PositionChangedCommand being executed, and the PositionChanged
event firing.
Event
The following XAML example shows a CarouselView that uses an event handler to
respond to the current item changing:
XAML
CurrentItemChanged="OnCurrentItemChanged">
...
</CarouselView>
C#
CarouselView carouselView = new CarouselView();
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.CurrentItemChanged += OnCurrentItemChanged;
C#
In this example, the OnCurrentItemChanged event handler exposes the previous and
current items:
Command
The following XAML example shows a CarouselView that uses a command to respond to
the current item changing:
XAML
CurrentItemChangedCommand="{Binding ItemChangedCommand}"
CurrentItemChangedCommandParameter="{Binding Source=
{RelativeSource Self}, Path=CurrentItem}">
...
</CarouselView>
C#
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.SetBinding(CarouselView.CurrentItemChangedCommandProperty,
"ItemChangedCommand");
carouselView.SetBinding(CarouselView.CurrentItemChangedCommandParameterPrope
rty, new Binding("CurrentItem", source: RelativeBindingSource.Self));
argument. The ItemChangedCommand can then respond to the current item changing, as
required:
C#
PreviousMonkey = CurrentMonkey;
CurrentMonkey = item;
});
In this example, the ItemChangedCommand updates objects that store the previous and
current items.
event then fires. If the Position property has been programmatically changed, the
CarouselView will be scrolled to the item that corresponds to the Position value.
7 Note
Setting the Position property to 0 will result in the first item in the underlying
collection being displayed.
Event
The following XAML example shows a CarouselView that uses an event handler to
respond to the Position property changing:
XAML
PositionChanged="OnPositionChanged">
...
</CarouselView>
C#
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.PositionChanged += OnPositionChanged;
C#
In this example, the OnCurrentItemChanged event handler exposes the previous and
current positions:
Command
The following XAML example shows a CarouselView that uses a command to respond to
the Position property changing:
XAML
PositionChangedCommand="{Binding PositionChangedCommand}"
PositionChangedCommandParameter="{Binding Source=
{RelativeSource Self}, Path=Position}">
...
</CarouselView>
C#
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.SetBinding(CarouselView.PositionChangedCommandProperty,
"PositionChangedCommand");
carouselView.SetBinding(CarouselView.PositionChangedCommandParameterProperty
, new Binding("Position", source: RelativeBindingSource.Self));
C#
PreviousPosition = CurrentPosition;
CurrentPosition = position;
});
In this example, the PositionChangedCommand updates objects that store the previous and
current positions.
XAML
CurrentItem="{Binding CurrentItem}">
...
</CarouselView>
C#
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.SetBinding(CarouselView.CurrentItemProperty, "CurrentItem");
7 Note
C#
// ...
public MonkeysViewModel()
// ...
CurrentItem = Monkeys.Skip(3).FirstOrDefault();
OnPropertyChanged("CurrentItem");
In this example, the CurrentItem property is set to the fourth item in the Monkeys
collection:
XAML
Position="{Binding Position}">
...
</CarouselView>
C#
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
carouselView.SetBinding(CarouselView.PositionProperty, "Position");
7 Note
C#
// ...
public MonkeysViewModel()
// ...
Position = 3;
OnPropertyChanged("Position");
In this example, the Position property is set to the fourth item in the Monkeys
collection:
CurrentItem represents the visual state for the currently displayed item.
PreviousItem represents the visual state for the previously displayed item.
DefaultItem represents the visual state for the remainder of the items.
These visual states can be used to initiate visual changes to the items displayed by the
CarouselView.
The following XAML example shows how to define the CurrentItem , PreviousItem ,
NextItem , and DefaultItem visual states:
XAML
PeekAreaInsets="100">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="CurrentItem">
<VisualState.Setters>
<Setter Property="Scale"
Value="1.1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PreviousItem">
<VisualState.Setters>
<Setter Property="Opacity"
Value="0.5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NextItem">
<VisualState.Setters>
<Setter Property="Opacity"
Value="0.5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="DefaultItem">
<VisualState.Setters>
<Setter Property="Opacity"
Value="0.25" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Frame HasShadow="true">
...
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
In this example, the CurrentItem visual state specifies that the current item displayed by
the CarouselView will have its Scale property changed from its default value of 1 to 1.1.
The PreviousItem and NextItem visual states specify that the items surrounding the
current item will be displayed with an Opacity value of 0.5. The DefaultItem visual state
specifies that the remainder of the items displayed by the CarouselView will be
displayed with an Opacity value of 0.25.
7 Note
Alternatively, the visual states can be defined in a Style that has a TargetType
property value that's the type of the root element of the DataTemplate, which is set
as the ItemTemplate property value.
The following screenshot shows the CurrentItem , PreviousItem , and NextItem visual
states:
Disable bounce
By default, CarouselView bounces items at content boundaries. This can be disabled by
setting the IsBounceEnabled property to false .
Disable loop
By default, CarouselView provides looped access to its collection of items. Therefore,
swiping backwards from the first item in the collection will display the last item in the
collection. Similarly, swiping forwards from the last item in the collection will return to
the first item in the collection. This behavior can be disabled by setting the Loop
property to false .
Disable swipe interaction
By default, CarouselView allows users to move through items using a swipe gesture. This
swipe interaction can be disabled by setting the IsSwipeEnabled property to false .
Define an EmptyView for a
CarouselView
Article • 04/03/2023 • 6 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CarouselView defines the following
properties that can be used to provide user feedback when there's no data to display:
EmptyView , of type object , the string, binding, or view that will be displayed when
the ItemsSource property is null , or when the collection specified by the
ItemsSource property is null or empty. The default value is null .
EmptyViewTemplate , of type DataTemplate, the template to use to format the
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
The main usage scenarios for setting the EmptyView property are displaying user
feedback when a filtering operation on a CarouselView yields no data, and displaying
user feedback while data is being retrieved from a web service.
7 Note
The EmptyView property can be set to a view that includes interactive content if
required.
property is null or empty. The following XAML shows an example of this scenario:
XAML
C#
};
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "EmptyMonkeys");
The result is that, because the data bound collection is null , the string set as the
EmptyView property value is displayed.
XAML
<StackLayout Margin="20">
SearchCommandParameter="{Binding Source={RelativeSource
Self}, Path=Text}"
Placeholder="Filter" />
<CarouselView.EmptyView>
<ContentView>
<StackLayout HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
FontAttributes="Italic"
FontSize="12"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</CarouselView.EmptyView>
<CarouselView.ItemTemplate>
...
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
In this example, what looks like a redundant has been added as the root element of the
EmptyView . This is because internally, the EmptyView is added to a native container that
doesn't provide any context for .NET MAUI layout. Therefore, to position the views that
comprise your EmptyView , you must add a root layout, whose child is a layout that can
position itself within the root layout.
C#
Content = stackLayout
};
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
When the SearchBar executes the FilterCommand , the collection displayed by the
CarouselView is filtered for the search term stored in the SearchBar.Text property. If the
filtering operation yields no data, the StackLayout set as the EmptyView property value is
displayed.
<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={RelativeSource
Self}, Path=Text}"
Placeholder="Filter" />
<CarouselView.EmptyView>
</CarouselView.EmptyView>
<CarouselView.EmptyViewTemplate>
<DataTemplate>
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</DataTemplate>
</CarouselView.EmptyViewTemplate>
<CarouselView.ItemTemplate>
...
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
C#
})
};
C#
The EmptyView property is set to a FilterData object, and the Filter property data
binds to the SearchBar.Text property. When the SearchBar executes the FilterCommand ,
the collection displayed by the CarouselView is filtered for the search term stored in the
Filter property. If the filtering operation yields no data, the Label defined in the
DataTemplate, that's set as the EmptyViewTemplate property value, is displayed.
7 Note
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:CarouselViewDemos.ViewModels"
x:Class="CarouselViewDemos.Views.EmptyViewSwapPage"
Title="EmptyView (swap)">
<ContentPage.BindingContext>
<viewmodels:MonkeysViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ContentView x:Key="BasicEmptyView">
<StackLayout>
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="AdvancedEmptyView">
<StackLayout>
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
FontAttributes="Italic"
FontSize="12"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout Margin="20">
SearchCommandParameter="{Binding Source={RelativeSource
Self}, Path=Text}"
Placeholder="Filter" />
<StackLayout Orientation="Horizontal">
<Label Text="Toggle EmptyViews" />
<Switch Toggled="OnEmptyViewSwitchToggled" />
</StackLayout>
<CarouselView x:Name="carouselView"
ItemsSource="{Binding Monkeys}">
<CarouselView.ItemTemplate>
...
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
</ContentPage>
This XAML defines two ContentView objects in the page-level ResourceDictionary, with
the Switch object controlling which ContentView object will be set as the EmptyView
property value. When the Switch is toggled, the OnEmptyViewSwitchToggled event
handler executes the ToggleEmptyView method:
C#
The ToggleEmptyView method sets the EmptyView property of the CarouselView object to
one of the two ContentView objects stored in the ResourceDictionary, based on the
value of the Switch.IsToggled property. When the SearchBar executes the
FilterCommand , the collection displayed by the CarouselView is filtered for the search
term stored in the SearchBar.Text property. If the filtering operation yields no data, the
ContentView object set as the EmptyView property is displayed.
XAML
<ContentPage ...
xmlns:controls="clr-namespace:CarouselViewDemos.Controls">
<ContentPage.Resources>
<DataTemplate x:Key="AdvancedTemplate">
...
</DataTemplate>
<DataTemplate x:Key="BasicTemplate">
...
</DataTemplate>
<controls:SearchTermDataTemplateSelector x:Key="SearchSelector"
DefaultTemplate="
{StaticResource AdvancedTemplate}"
OtherTemplate="
{StaticResource BasicTemplate}" />
</ContentPage.Resources>
<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={RelativeSource
Self}, Path=Text}"
Placeholder="Filter" />
EmptyViewTemplate="{StaticResource SearchSelector}">
<CarouselView.ItemTemplate>
...
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
</ContentPage>
C#
EmptyView = searchBar.Text,
carouselView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
When the SearchBar executes the FilterCommand , the collection displayed by the
CarouselView is filtered for the search term stored in the SearchBar.Text property. If the
filtering operation yields no data, the DataTemplate chosen by the
SearchTermDataTemplateSelector object is set as the EmptyViewTemplate property and
displayed.
C#
user.
For more information about data template selectors, see Create a DataTemplateSelector.
Control scrolling in a CarouselView
Article • 12/23/2022 • 8 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CarouselView defines the following scroll
related properties:
scrolling behavior of the CarouselView when new items are added to it.
VerticalScrollBarVisibility , of type ScrollBarVisibility , which specifies when
All of these properties are backed by BindableProperty objects, which means that they
can be targets of data bindings.
CarouselView also defines two ScrollTo methods, that scroll items into view. One of the
overloads scrolls the item at the specified index into view, while the other scrolls the
specified item into view. Both overloads have additional arguments that can be specified
to indicate the exact position of the item after the scroll has completed, and whether to
animate the scroll.
CarouselView defines a ScrollToRequested event that is fired when one of the ScrollTo
methods is invoked. The ScrollToRequestedEventArgs object that accompanies the
ScrollToRequested event has many properties, including IsAnimated , Index , Item , and
ScrollToPosition . These properties are set from the arguments specified in the
In addition, CarouselView defines a Scrolled event that is fired to indicate that scrolling
occurred. The ItemsViewScrolledEventArgs object that accompanies the Scrolled event
has many properties. For more information, see Detect scrolling.
When a user swipes to initiate a scroll, the end position of the scroll can be controlled so
that items are fully displayed. This feature is known as snapping, because items snap to
position when scrolling stops. For more information, see Snap points.
CarouselView can also load data incrementally as the user scrolls. For more information,
see Load data incrementally.
Detect scrolling
The IsDragging property can be examined to determine whether the CarouselView is
currently scrolling through items.
The following XAML example shows a CarouselView that sets an event handler for the
Scrolled event:
XAML
<CarouselView Scrolled="OnCollectionViewScrolled">
...
</CarouselView>
C#
carouselView.Scrolled += OnCarouselViewScrolled;
In this code example, the OnCarouselViewScrolled event handler is executed when the
Scrolled event fires:
C#
In this example, the OnCarouselViewScrolled event handler outputs the values of the
ItemsViewScrolledEventArgs object that accompanies the event.
) Important
The Scrolled event is fired for user initiated scrolls, and for programmatic scrolls.
C#
carouselView.ScrollTo(6);
7 Note
C#
carouselView.ScrollTo(monkey);
7 Note
Alternatively, the animate argument of the ScrollTo method can be set to false to
disable the scrolling animation on programmatic scrolls:
C#
MakeVisible
The ScrollToPosition.MakeVisible member indicates that the item should be scrolled
until it's visible in the view:
C#
This example code results in the minimal scrolling required to scroll the item into view.
7 Note
Start
The ScrollToPosition.Start member indicates that the item should be scrolled to the
start of the view:
C#
This example code results in the item being scrolled to the start of the view.
Center
The ScrollToPosition.Center member indicates that the item should be scrolled to the
center of the view:
C#
This example code results in the item being scrolled to the center of the view.
End
The ScrollToPosition.End member indicates that the item should be scrolled to the end
of the view:
C#
This example code results in the item being scrolled to the end of the view.
KeepItemsInView keeps the first item in the list displayed when new items are
added.
KeepScrollOffset ensures that the current scroll position is maintained when new
XAML
<CarouselView ItemsUpdatingScrollMode="KeepLastItemInView">
...
</CarouselView>
C#
ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepLastItemInView
};
Default indicates the default scroll bar behavior for the platform, and is the
default value for the HorizontalScrollBarVisibility and
VerticalScrollBarVisibility properties.
Always indicates that scroll bars will be visible, even when the content fits in the
view.
Never indicates that scroll bars will not be visible, even if the content doesn't fit in
the view.
Snap points
When a user swipes to initiate a scroll, the end position of the scroll can be controlled so
that items are fully displayed. This feature is known as snapping, because items snap to
position when scrolling stops, and is controlled by the following properties from the
ItemsLayout class:
when scrolling.
SnapPointsAlignment , of type SnapPointsAlignment , specifies how snap points are
aligned with items.
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
7 Note
When snapping occurs, it will occur in the direction that produces the least amount
of motion.
Mandatory indicates that content always snaps to the closest snap point to where
scrolling would naturally stop, along the direction of inertia.
MandatorySingle indicates the same behavior as Mandatory , but only scrolls one
item at a time.
) Important
Start
The SnapPointsAlignment.Start member indicates that snap points are aligned with the
leading edge of items. The following XAML example shows how to set this enumeration
member:
XAML
PeekAreaInsets="100">
<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Start" />
</CarouselView.ItemsLayout>
...
</CarouselView>
C#
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.Start
},
// ...
};
When a user swipes to initiate a scroll in a horizontally scrolling CarouselView, the left
item will be aligned with the left of the view:
Center
The SnapPointsAlignment.Center member indicates that snap points are aligned with the
center of items.
XAML
PeekAreaInsets="100">
<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Center" />
</CarouselView.ItemsLayout>
...
</CarouselView>
C#
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.Center
},
// ...
};
When a user swipes to initiate a scroll in a horizontally scrolling CarouselView, the center
item will be aligned with the center of the view:
End
The SnapPointsAlignment.End member indicates that snap points are aligned with the
trailing edge of items. The following XAML example shows how to set this enumeration
member:
XAML
PeekAreaInsets="100">
<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="End" />
</CarouselView.ItemsLayout>
...
</CarouselView>
C#
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.End
},
// ...
};
When a user swipes to initiate a scroll in a horizontally scrolling CarouselView, the right
item will be aligned with the right of the view.
CollectionView
Article • 04/03/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CollectionView is a view for presenting lists
of data using different layout specifications. It aims to provide a more flexible, and
performant alternative to ListView.
The following screenshot shows a CollectionView that uses a two-column vertical grid
and allows multiple selections:
CollectionView should be used for presenting lists of data that require scrolling or
selection. A bindable layout can be used when the data to be displayed doesn't require
scrolling or selection. For more information, see BindableLayout.
CollectionView and ListView differences
While the CollectionView and ListView APIs are similar, there are some notable
differences:
Headers Header , HeaderElement , CollectionView can present a header and footer that
and footers HeaderTemplate , Footer , scroll with the items in the list, via the Header , Footer ,
FooterElement , HeaderTemplate , and FooterTemplate properties. For
FooterTemplate more information, see Headers and footers.
The .NET Multi-platform App UI (.NET MAUI) CollectionView includes the following
properties that define the data to be displayed, and its appearance:
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
CollectionView supports incremental data virtualization as the user scrolls. For more
information, see Load data incrementally.
) Important
ObservableCollection .
CollectionView can be populated with data by using data binding to bind its
ItemsSource property to an IEnumerable collection. In XAML, this is achieved with the
Binding markup extension:
XAML
C#
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
In this example, the ItemsSource property data binds to the Monkeys property of the
connected viewmodel.
7 Note
For information on how to change the CollectionView layout, see Specify CollectionView
layout. For information on how to define the appearance of each item in the
CollectionView, see Define item appearance. For more information about data binding,
see Data binding.
2 Warning
XAML
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
...
</CollectionView>
C#
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
image.SetBinding(Image.SourceProperty, "ImageUrl");
nameLabel.SetBinding(Label.TextProperty, "Name");
locationLabel.SetBinding(Label.TextProperty, "Location");
Grid.SetRowSpan(image, 2);
grid.Add(image);
grid.Add(nameLabel, 1, 0);
grid.Add(locationLabel, 1, 1);
return grid;
});
The elements specified in the DataTemplate define the appearance of each item in the
list. In the example, layout within the DataTemplate is managed by a Grid. The Grid
contains an Image object, and two Label objects, that all bind to properties of the
Monkey class:
C#
The following screenshot shows the result of templating each item in the list:
For more information about data templates, see Data templates.
XAML
<ContentPage ...
xmlns:controls="clr-namespace:CollectionViewDemos.Controls">
<ContentPage.Resources>
<DataTemplate x:Key="AmericanMonkeyTemplate">
...
</DataTemplate>
<DataTemplate x:Key="OtherMonkeyTemplate">
...
</DataTemplate>
<controls:MonkeyDataTemplateSelector x:Key="MonkeySelector"
AmericanMonkey="{StaticResource
AmericanMonkeyTemplate}"
OtherMonkey="{StaticResource
OtherMonkeyTemplate}" />
</ContentPage.Resources>
</ContentPage>
C#
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
C#
) Important
When using CollectionView, never set the root element of your DataTemplate
objects to a ViewCell. This will result in an exception being thrown because
CollectionView has no concept of cells.
Context menus
CollectionView supports context menus for items of data through the SwipeView, which
reveals the context menu with a swipe gesture. The SwipeView is a container control
that wraps around an item of content, and provides context menu items for that item of
content. Therefore, context menus are implemented for a CollectionView by creating a
SwipeView that defines the content that the SwipeView wraps around, and the context
menu items that are revealed by the swipe gesture. This is achieved by setting the
SwipeView as the root view in the DataTemplate that defines the appearance of each
item of data in the CollectionView:
XAML
<CollectionView x:Name="collectionView"
ItemsSource="{Binding Monkeys}">
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Favorite"
IconImageSource="favorite.png"
BackgroundColor="LightGreen"
Command="{Binding Source={x:Reference
collectionView}, Path=BindingContext.FavoriteCommand}"
CommandParameter="{Binding}" />
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Command="{Binding Source={x:Reference
collectionView}, Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding}" />
</SwipeItems>
</SwipeView.LeftItems>
<Grid BackgroundColor="White"
Padding="10">
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
C#
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
// ...
Text = "Favorite",
IconImageSource = "favorite.png",
BackgroundColor = Colors.LightGreen
};
favoriteSwipeItem.SetBinding(MenuItem.CommandProperty, new
Binding("BindingContext.FavoriteCommand", source: collectionView));
favoriteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");
Text = "Delete",
IconImageSource = "delete.png",
BackgroundColor = Colors.LightPink
};
deleteSwipeItem.SetBinding(MenuItem.CommandProperty, new
Binding("BindingContext.DeleteCommand", source: collectionView));
deleteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");
swipeView.Content = grid;
return swipeView;
});
In this example, the SwipeView content is a Grid that defines the appearance of each
item in the CollectionView. The swipe items are used to perform actions on the
SwipeView content, and are revealed when the control is swiped from the left side:
SwipeView supports four different swipe directions, with the swipe direction being
defined by the directional SwipeItems collection the SwipeItems objects are added to.
By default, a swipe item is executed when it's tapped by the user. In addition, once a
swipe item has been executed the swipe items are hidden and the SwipeView content is
re-displayed. However, these behaviors can be changed.
Pull to refresh
CollectionView supports pull to refresh functionality through the RefreshView, which
enables the data being displayed to be refreshed by pulling down on the list of items.
The RefreshView is a container control that provides pull to refresh functionality to its
child, provided that the child supports scrollable content. Therefore, pull to refresh is
implemented for a CollectionView by setting it as the child of a RefreshView:
XAML
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
...
</CollectionView>
</RefreshView>
C#
// IsRefreshing is true
refreshView.IsRefreshing = false;
});
refreshView.Command = refreshCommand;
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
refreshView.Content = collectionView;
// ...
When the user initiates a refresh, the ICommand defined by the Command property is
executed, which should refresh the items being displayed. A refresh visualization is
shown while the refresh occurs, which consists of an animated progress circle:
The value of the RefreshView.IsRefreshing property indicates the current state of the
RefreshView. When a refresh is triggered by the user, this property will automatically
transition to true . Once the refresh completes, you should reset the property to false .
RemainingItemsThreshold , of type int , the threshold of items not yet visible in the
The default value of the RemainingItemsThreshold property is -1, which indicates that the
RemainingItemsThresholdReached event will never be fired. When the property value is 0,
the RemainingItemsThresholdReached event will be fired when the final item in the
ItemsSource is displayed. For values greater than 0, the RemainingItemsThresholdReached
event will be fired when the ItemsSource contains that number of items not yet scrolled
to.
7 Note
The following XAML example shows a CollectionView that loads data incrementally:
XAML
RemainingItemsThreshold="5"
RemainingItemsThresholdReached="OnCollectionViewRemainingItemsThresholdReach
ed">
...
</CollectionView>
C#
RemainingItemsThreshold = 5
};
collectionView.RemainingItemsThresholdReached +=
OnCollectionViewRemainingItemsThresholdReached;
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
In this code example, the RemainingItemsThresholdReached event fires when there are 5
items not yet scrolled to, and in response executes the
OnCollectionViewRemainingItemsThresholdReached event handler:
C#
7 Note
The .NET Multi-platform App UI (.NET MAUI) CollectionView defines the following
properties that control layout:
strategy to be used.
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
By default, a CollectionView will display its items in a vertical list. However, any of the
following layouts can be used:
Vertical list – a single column list that grows vertically as new items are added.
Horizontal list – a single row list that grows horizontally as new items are added.
Vertical grid – a multi-column grid that grows vertically as new items are added.
Horizontal grid – a multi-row grid that grows horizontally as new items are added.
These layouts can be specified by setting the ItemsLayout property to class that derives
from the ItemsLayout class. This class defines the following properties:
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings. For more information about snap points, see
Snap points in Control scrolling in a CollectionView.
Vertical indicates that the CollectionView will expand vertically as items are
added.
Horizontal indicates that the CollectionView will expand horizontally as items are
added.
The LinearItemsLayout class inherits from the ItemsLayout class, and defines an
ItemSpacing property, of type double , that represents the empty space around each
item. The default value of this property is 0, and its value must always be greater than or
equal to 0. The LinearItemsLayout class also defines static Vertical and Horizontal
members. These members can be used to create vertical or horizontal lists, respectively.
Alternatively, a LinearItemsLayout object can be created, specifying an
ItemsLayoutOrientation enumeration member as an argument.
The GridItemsLayout class inherits from the ItemsLayout class, and defines the following
properties:
around each item. The default value of this property is 0, and its value must always
be greater than or equal to 0.
Span , of type int , that represents the number of columns or rows to display in the
grid. The default value of this property is 1, and its value must always be greater
than or equal to 1.
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
7 Note
Vertical list
By default, CollectionView will display its items in a vertical list layout. Therefore, it's not
necessary to set the ItemsLayout property to use this layout:
XAML
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
However, for completeness, in XAML a CollectionView can be set to display its items in a
vertical list by setting its ItemsLayout property to VerticalList :
XAML
ItemsLayout="VerticalList">
...
</CollectionView>
XAML
<CollectionView.ItemsLayout>
</CollectionView.ItemsLayout>
...
</CollectionView>
C#
...
ItemsLayout = LinearItemsLayout.Vertical
};
This results in a single column list, which grows vertically as new items are added:
Horizontal list
In XAML, a CollectionView can display its items in a horizontal list by setting its
ItemsLayout property to HorizontalList :
XAML
<CollectionView ItemsSource="{Binding Monkeys}"
ItemsLayout="HorizontalList">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold"
LineBreakMode="TailTruncation" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
LineBreakMode="TailTruncation"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Alternatively, this layout can also be accomplished by setting the ItemsLayout property
to a LinearItemsLayout object, specifying the Horizontal ItemsLayoutOrientation
enumeration member as the Orientation property value:
XAML
<CollectionView.ItemsLayout>
</CollectionView.ItemsLayout>
...
</CollectionView>
C#
CollectionView collectionView = new CollectionView
...
ItemsLayout = LinearItemsLayout.Horizontal
};
This results in a single row list, which grows horizontally as new items are added:
Vertical grid
In XAML, a CollectionView can display its items in a vertical grid by setting its
ItemsLayout property to VerticalGrid :
XAML
ItemsLayout="VerticalGrid, 2">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold"
LineBreakMode="TailTruncation" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
LineBreakMode="TailTruncation"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Alternatively, this layout can also be accomplished by setting the ItemsLayout property
to a GridItemsLayout object whose Orientation property is set to Vertical :
XAML
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
...
</CollectionView>
C#
...
};
By default, a vertical GridItemsLayout will display items in a single column. However, this
example sets the GridItemsLayout.Span property to 2. This results in a two-column grid,
which grows vertically as new items are added:
Horizontal grid
In XAML, a CollectionView can display its items in a horizontal grid by setting its
ItemsLayout property to HorizontalGrid :
XAML
ItemsLayout="HorizontalGrid, 4">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold"
LineBreakMode="TailTruncation" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
LineBreakMode="TailTruncation"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Alternatively, this layout can also be accomplished by setting the ItemsLayout property
to a GridItemsLayout object whose Orientation property is set to Horizontal :
XAML
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Horizontal"
Span="4" />
</CollectionView.ItemsLayout>
...
</CollectionView>
C#
...
};
By default, a horizontal GridItemsLayout will display items in a single row. However, this
example sets the GridItemsLayout.Span property to 4. This results in a four-row grid,
which grows horizontally as new items are added:
Headers and footers
CollectionView can present a header and footer that scroll with the items in the list. The
header and footer can be strings, views, or DataTemplate objects.
CollectionView defines the following properties for specifying the header and footer:
Header , of type object , specifies the string, binding, or view that will be displayed
at the start of the list.
HeaderTemplate , of type DataTemplate, specifies the DataTemplate to use to format
the Header .
Footer , of type object , specifies the string, binding, or view that will be displayed
the Footer .
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
When a header is added to a layout that grows horizontally, from left to right, the
header is displayed to the left of the list. Similarly, when a footer is added to a layout
that grows horizontally, from left to right, the footer is displayed to the right of the list.
XAML
Header="Monkeys"
Footer="2019">
...
</CollectionView>
C#
Header = "Monkeys",
Footer = "2019"
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
This code results in the following screenshots, with the header shown in the iOS
screenshot, and the footer shown in the Android screenshot:
Display views in the header and footer
The Header and Footer properties can each be set to a view. This can be a single view,
or a view that contains multiple child views. The following example shows the Header
and Footer properties each set to a StackLayout object that contains a Label object:
XAML
<CollectionView.Header>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="12"
FontAttributes="Bold" />
</StackLayout>
</CollectionView.Header>
<CollectionView.Footer>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
FontSize="12"
FontAttributes="Bold" />
</StackLayout>
</CollectionView.Footer>
...
</CollectionView>
C#
Header = headerStackLayout,
Footer = footerStackLayout
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
This code results in the following screenshots, with the header shown in the iOS
screenshot, and the footer shown in the Android screenshot:
Display a templated header and footer
The HeaderTemplate and FooterTemplate properties can be set to DataTemplate objects
that are used to format the header and footer. In this scenario, the Header and Footer
properties must bind to the current source for the templates to be applied, as shown in
the following example:
XAML
Header="{Binding .}"
Footer="{Binding .}">
<CollectionView.HeaderTemplate>
<DataTemplate>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="12"
FontAttributes="Bold" />
</StackLayout>
</DataTemplate>
</CollectionView.HeaderTemplate>
<CollectionView.FooterTemplate>
<DataTemplate>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
FontSize="12"
FontAttributes="Bold" />
</StackLayout>
</DataTemplate>
</CollectionView.FooterTemplate>
...
</CollectionView>
C#
}),
})
};
collectionView.SetBinding(ItemsView.HeaderProperty, ".");
collectionView.SetBinding(ItemsView.FooterProperty, ".");
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
This code results in the following screenshots, with the header shown in the iOS
screenshot, and the footer shown in the Android screenshot:
Item spacing
By default, there is no space between each item in a CollectionView. This behavior can
be changed by setting properties on the items layout used by the CollectionView.
XAML
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="20" />
</CollectionView.ItemsLayout>
...
</CollectionView>
7 Note
The LinearItemsLayout.ItemSpacing property has a validation callback set, which
ensures that the value of the property is always greater than or equal to 0.
C#
...
ItemSpacing = 20
};
This code results in a vertical single column list that has a spacing of 20 between items:
When a CollectionView sets its ItemsLayout property to a GridItemsLayout object, the
GridItemsLayout.VerticalItemSpacing and GridItemsLayout.HorizontalItemSpacing
properties can be set to double values that represent the empty space vertically and
horizontally between items:
XAML
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2"
VerticalItemSpacing="20"
HorizontalItemSpacing="30" />
</CollectionView.ItemsLayout>
...
</CollectionView>
7 Note
which ensures that the values of the properties are always greater than or equal to
0.
C#
...
VerticalItemSpacing = 20,
HorizontalItemSpacing = 30
};
This code results in a vertical two-column grid that has a vertical spacing of 20 between
items and a horizontal spacing of 30 between items:
Item sizing
By default, each item in a CollectionView is individually measured and sized, provided
that the UI elements in the DataTemplate don't specify fixed sizes. This behavior, which
can be changed, is specified by the CollectionView.ItemSizingStrategy property value.
This property value can be set to one of the ItemSizingStrategy enumeration members:
MeasureFirstItem – only the first item is measured, with all subsequent items
) Important
XAML
<CollectionView ...
ItemSizingStrategy="MeasureFirstItem">
...
</CollectionView>
C#
...
ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem
};
C#
image.HeightRequest = image.WidthRequest =
image.HeightRequest.Equals(60) ? 100 : 60;
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CollectionViewDemos.Views.VerticalListFlowDirectionPage"
FlowDirection="RightToLeft">
<StackLayout Margin="20">
...
</CollectionView>
</StackLayout>
</ContentPage>
The default FlowDirection for an element with a parent is MatchParent . Therefore, the
CollectionView inherits the FlowDirection property value from the StackLayout, which in
turn inherits the FlowDirection property value from the ContentPage. This results in the
right-to-left layout shown in the following screenshot:
Configure CollectionView item selection
Article • 04/03/2023 • 6 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CollectionView defines the following
properties that control item selection:
SelectedItem , of type object , the selected item in the list. This property has a
default binding mode of TwoWay , and has a null value when no item is selected.
SelectedItems , of type IList<object> , the selected items in the list. This property
has a default binding mode of OneWay , and has a null value when no items are
selected.
SelectionChangedCommand , of type ICommand , which is executed when the selected
item changes.
SelectionChangedCommandParameter , of type object , which is the parameter that's
All of these properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
None – indicates that items cannot be selected. This is the default value.
Single – indicates that a single item can be selected, with the selected item being
highlighted.
Multiple – indicates that multiple items can be selected, with the selected items
being highlighted.
PreviousSelection – the list of items that were selected, before the selection
changed.
CurrentSelection – the list of items that are selected, after the selection change.
In addition, CollectionView has a UpdateSelectedItems method that updates the
SelectedItems property with a list of selected items, while only firing a single change
notification.
Single selection
When the SelectionMode property is set to Single , a single item in the CollectionView
can be selected. When an item is selected, the SelectedItem property will be set to the
value of the selected item. When this property changes, the SelectionChangedCommand is
executed (with the value of the SelectionChangedCommandParameter being passed to the
ICommand ), and the SelectionChanged event fires.
The following XAML example shows a CollectionView that can respond to single item
selection:
XAML
SelectionMode="Single"
SelectionChanged="OnCollectionViewSelectionChanged">
...
</CollectionView>
C#
SelectionMode = SelectionMode.Single
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SelectionChanged += OnCollectionViewSelectionChanged;
C#
...
) Important
Multiple selection
When the SelectionMode property is set to Multiple , multiple items in the
CollectionView can be selected. When items are selected, the SelectedItems property
will be set to the selected items. When this property changes, the
SelectionChangedCommand is executed (with the value of the
SelectionChangedCommandParameter being passed to the ICommand , and the
The following XAML example shows a CollectionView that can respond to multiple item
selection:
XAML
SelectionMode="Multiple"
SelectionChanged="OnCollectionViewSelectionChanged">
...
</CollectionView>
C#
SelectionMode = SelectionMode.Multiple
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SelectionChanged += OnCollectionViewSelectionChanged;
C#
...
) Important
XAML
SelectionMode="Single"
SelectedItem="{Binding SelectedMonkey}">
...
</CollectionView>
C#
SelectionMode = SelectionMode.Single
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SetBinding(SelectableItemsView.SelectedItemProperty,
"SelectedMonkey");
7 Note
The SelectedItem property data binds to the SelectedMonkey property of the connected
view model, which is of type Monkey . By default, a TwoWay binding is used so that if the
user changes the selected item, the value of the SelectedMonkey property will be set to
the selected Monkey object. The SelectedMonkey property is defined in the
MonkeysViewModel class, and is set to the fourth item of the Monkeys collection:
C#
...
Monkey selectedMonkey;
get
return selectedMonkey;
set
if (selectedMonkey != value)
selectedMonkey = value;
public MonkeysViewModel()
...
selectedMonkey = Monkeys.Skip(3).FirstOrDefault();
...
Therefore, when the CollectionView appears, the fourth item in the list is pre-selected:
Multiple pre-selection
When the SelectionMode property is set to Multiple , multiple items in the
CollectionView can be pre-selected. The following XAML example shows a
CollectionView that will enable the pre-selection of multiple items:
XAML
<CollectionView x:Name="collectionView"
ItemsSource="{Binding Monkeys}"
SelectionMode="Multiple"
SelectedItems="{Binding SelectedMonkeys}">
...
</CollectionView>
C#
CollectionView collectionView = new CollectionView
SelectionMode = SelectionMode.Multiple
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SetBinding(SelectableItemsView.SelectedItemsProperty,
"SelectedMonkeys");
7 Note
C#
namespace CollectionViewDemos.ViewModels
...
ObservableCollection<object> selectedMonkeys;
get
return selectedMonkeys;
set
if (selectedMonkeys != value)
selectedMonkeys = value;
public MonkeysViewModel()
...
};
...
Therefore, when the CollectionView appears, the second, fourth, and fifth items in the
list are pre-selected:
Clear selections
The SelectedItem and SelectedItems properties can be cleared by setting them, or the
objects they bind to, to null . When either of these properties are cleared, the
SelectionChanged event will be raised with an empty CurrentSelection property, and
XAML
<ContentPage ...>
<ContentPage.Resources>
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="LightSkyBlue" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
<StackLayout Margin="20">
SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
...
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
) Important
The Style that contains the Selected VisualState must have a TargetType property
value that's the type of the root element of the DataTemplate, which is set as the
ItemTemplate property value.
In this example, the Style.TargetType property value is set to Grid because the root
element of the ItemTemplate is a Grid. The Selected VisualState specifies that when an
item in the CollectionView is selected, the BackgroundColor of the item will be set to
LightSkyBlue :
For more information about visual states, see Visual states.
Disable selection
CollectionView selection is disabled by default. However, if a CollectionView has
selection enabled, it can be disabled by setting the SelectionMode property to None :
XAML
<CollectionView ...
SelectionMode="None" />
C#
...
SelectionMode = SelectionMode.None
};
When the SelectionMode property is set to None , items in the CollectionView cannot be
selected, the SelectedItem property will remain null , and the SelectionChanged event
will not be fired.
7 Note
When an item has been selected and the SelectionMode property is changed from
Single to None , the SelectedItem property will be set to null and the
The .NET Multi-platform App UI (.NET MAUI) CollectionView defines the following
properties that can be used to provide user feedback when there's no data to display:
EmptyView , of type object , the string, binding, or view that will be displayed when
the ItemsSource property is null , or when the collection specified by the
ItemsSource property is null or empty. The default value is null .
EmptyViewTemplate , of type DataTemplate, the template to use to format the
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
The main usage scenarios for setting the EmptyView property are displaying user
feedback when a filtering operation on a CollectionView yields no data, and displaying
user feedback while data is being retrieved from a web service.
7 Note
The EmptyView property can be set to a view that includes interactive content if
required.
property is null or empty. The following XAML shows an example of this scenario:
XAML
C#
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "EmptyMonkeys");
The result is that, because the data bound collection is null , the string set as the
EmptyView property value is displayed:
property is null or empty. This can be a single view, or a view that contains multiple
child views. The following XAML example shows the EmptyView property set to a view
that contains multiple child views:
XAML
<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={x:Reference
searchBar}, Path=Text}"
Placeholder="Filter" />
<CollectionView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.EmptyView>
<ContentView>
<StackLayout HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
FontAttributes="Italic"
FontSize="12"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</CollectionView.EmptyView>
</CollectionView>
</StackLayout>
In this example, what looks like a redundant has been added as the root element of the
EmptyView . This is because internally, the EmptyView is added to a native container that
doesn't provide any context for .NET MAUI layout. Therefore, to position the views that
comprise your EmptyView , you must add a root layout, whose child is a layout that can
position itself within the root layout.
C#
Content = stackLayout
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
When the SearchBar executes the FilterCommand , the collection displayed by the
CollectionView is filtered for the search term stored in the SearchBar.Text property. If
the filtering operation yields no data, the StackLayout set as the EmptyView property
value is displayed:
XAML
<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={x:Reference
searchBar}, Path=Text}"
Placeholder="Filter" />
<CollectionView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.EmptyView>
</CollectionView.EmptyView>
<CollectionView.EmptyViewTemplate>
<DataTemplate>
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</DataTemplate>
</CollectionView.EmptyViewTemplate>
</CollectionView>
</StackLayout>
C#
})
};
C#
The EmptyView property is set to a FilterData object, and the Filter property data
binds to the SearchBar.Text property. When the SearchBar executes the FilterCommand ,
the collection displayed by the CollectionView is filtered for the search term stored in
the Filter property. If the filtering operation yields no data, the Label defined in the
DataTemplate, that's set as the EmptyViewTemplate property value, is displayed:
7 Note
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CollectionViewDemos.Views.EmptyViewSwapPage"
Title="EmptyView (swap)">
<ContentPage.Resources>
<ContentView x:Key="BasicEmptyView">
<StackLayout>
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="AdvancedEmptyView">
<StackLayout>
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
FontAttributes="Italic"
FontSize="12"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={x:Reference
searchBar}, Path=Text}"
Placeholder="Filter" />
<StackLayout Orientation="Horizontal">
<Label Text="Toggle EmptyViews" />
<Switch Toggled="OnEmptyViewSwitchToggled" />
</StackLayout>
<CollectionView x:Name="collectionView"
ItemsSource="{Binding Monkeys}">
<CollectionView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
This XAML defines two ContentView objects in the page-level ResourceDictionary, with
the Switch object controlling which ContentView object will be set as the EmptyView
property value. When the Switch is toggled, the OnEmptyViewSwitchToggled event
handler executes the ToggleEmptyView method:
C#
The ToggleEmptyView method sets the EmptyView property of the CollectionView object
to one of the two ContentView objects stored in the ResourceDictionary, based on the
value of the Switch.IsToggled property. When the SearchBar executes the
FilterCommand , the collection displayed by the CollectionView is filtered for the search
term stored in the SearchBar.Text property. If the filtering operation yields no data, the
ContentView object set as the EmptyView property is displayed:
XAML
<ContentPage ...
xmlns:controls="clr-namespace:CollectionViewDemos.Controls">
<ContentPage.Resources>
<DataTemplate x:Key="AdvancedTemplate">
...
</DataTemplate>
<DataTemplate x:Key="BasicTemplate">
...
</DataTemplate>
<controls:SearchTermDataTemplateSelector x:Key="SearchSelector"
DefaultTemplate="
{StaticResource AdvancedTemplate}"
OtherTemplate="
{StaticResource BasicTemplate}" />
</ContentPage.Resources>
<StackLayout Margin="20">
<SearchBar x:Name="searchBar"
SearchCommand="{Binding FilterCommand}"
SearchCommandParameter="{Binding Source={x:Reference
searchBar}, Path=Text}"
Placeholder="Filter" />
EmptyViewTemplate="{StaticResource SearchSelector}"
/>
</StackLayout>
</ContentPage>
C#
EmptyView = searchBar.Text,
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
When the SearchBar executes the FilterCommand , the collection displayed by the
CollectionView is filtered for the search term stored in the SearchBar.Text property. If
the filtering operation yields no data, the DataTemplate chosen by the
SearchTermDataTemplateSelector object is set as the EmptyViewTemplate property and
displayed.
C#
user:
For more information about data template selectors, see Create a DataTemplateSelector.
Control scrolling in a CollectionView
Article • 12/23/2022 • 8 minutes to read
The .NET Multi-platform App UI (.NET MAUI) CollectionView defines two ScrollTo
methods, that scroll items into view. One of the overloads scrolls the item at the
specified index into view, while the other scrolls the specified item into view. Both
overloads have additional arguments that can be specified to indicate the group the
item belongs to, the exact position of the item after the scroll has completed, and
whether to animate the scroll.
the ScrollToRequested event has many properties, including IsAnimated , Index , Item ,
and ScrollToPosition . These properties are set from the arguments specified in the
ScrollTo method calls.
When a user swipes to initiate a scroll, the end position of the scroll can be controlled so
that items are fully displayed. This feature is known as snapping, because items snap to
position when scrolling stops. For more information, see Snap points.
CollectionView can also load data incrementally as the user scrolls. For more
information, see Load data incrementally.
Detect scrolling
CollectionView defines a Scrolled event which is fired to indicate that scrolling
occurred. The ItemsViewScrolledEventArgs class, which represents the object that
accompanies the Scrolled event, defines the following properties:
list.
LastVisibleItemIndex , of type int , is the index of the last item that's visible in the
list.
The following XAML example shows a CollectionView that sets an event handler for the
Scrolled event:
XAML
<CollectionView Scrolled="OnCollectionViewScrolled">
...
</CollectionView>
C#
collectionView.Scrolled += OnCollectionViewScrolled;
In this code example, the OnCollectionViewScrolled event handler is executed when the
Scrolled event fires:
C#
// Custom logic
) Important
The Scrolled event is fired for user initiated scrolls, and for programmatic scrolls.
C#
collectionView.ScrollTo(12);
Alternatively, an item in grouped data can be scrolled into view by specifying the item
and group indexes. The following example shows how to scroll the third item in the
second group into view:
C#
collectionView.ScrollTo(2, 1);
7 Note
C#
collectionView.ScrollTo(monkey);
Alternatively, an item in grouped data can be scrolled into view by specifying the item
and the group. The following example shows how to scroll the Proboscis Monkey item
in the Monkeys group into view:
C#
collectionView.ScrollTo(monkey, group);
7 Note
C#
MakeVisible
The ScrollToPosition.MakeVisible member indicates that the item should be scrolled
until it's visible in the view:
C#
This example code results in the minimal scrolling required to scroll the item into view:
7 Note
Start
The ScrollToPosition.Start member indicates that the item should be scrolled to the
start of the view:
C#
This example code results in the item being scrolled to the start of the view:
Center
The ScrollToPosition.Center member indicates that the item should be scrolled to the
center of the view:
C#
This example code results in the item being scrolled to the center of the view:
End
The ScrollToPosition.End member indicates that the item should be scrolled to the end
of the view:
C#
This example code results in the item being scrolled to the end of the view:
Control scroll position when new items are
added
CollectionView defines a ItemsUpdatingScrollMode property, which is backed by a
bindable property. This property gets or sets a ItemsUpdatingScrollMode enumeration
value that represents the scrolling behavior of the CollectionView when new items are
added to it. The ItemsUpdatingScrollMode enumeration defines the following members:
KeepItemsInView keeps the first item in the list displayed when new items are
added.
KeepScrollOffset ensures that the current scroll position is maintained when new
XAML
<CollectionView ItemsUpdatingScrollMode="KeepLastItemInView">
...
</CollectionView>
C#
ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepLastItemInView
};
Default indicates the default scroll bar behavior for the platform, and is the
Always indicates that scroll bars will be visible, even when the content fits in the
view.
Never indicates that scroll bars will not be visible, even if the content doesn't fit in
the view.
Snap points
When a user swipes to initiate a scroll, the end position of the scroll can be controlled so
that items are fully displayed. This feature is known as snapping, because items snap to
position when scrolling stops, and is controlled by the following properties from the
ItemsLayout class:
SnapPointsType , of type SnapPointsType , specifies the behavior of snap points
when scrolling.
SnapPointsAlignment , of type SnapPointsAlignment , specifies how snap points are
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
7 Note
When snapping occurs, it will occur in the direction that produces the least amount
of motion.
) Important
Start
The SnapPointsAlignment.Start member indicates that snap points are aligned with the
leading edge of items.
XAML
<CollectionView ItemsSource="{Binding Monkeys}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Start" />
</CollectionView.ItemsLayout>
...
</CollectionView>
C#
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.Start
},
// ...
};
When a user swipes to initiate a scroll, the top item will be aligned with the top of the
view:
Center
The SnapPointsAlignment.Center member indicates that snap points are aligned with the
center of items. The following XAML example shows how to set this enumeration
member:
XAML
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Center" />
</CollectionView.ItemsLayout>
...
</CollectionView>
C#
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.Center
},
// ...
};
When a user swipes to initiate a scroll, the top item will be center aligned at the top of
the view:
End
The SnapPointsAlignment.End member indicates that snap points are aligned with the
trailing edge of items. The following XAML example shows how to set this enumeration
member:
XAML
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="End" />
</CollectionView.ItemsLayout>
...
</CollectionView>
C#
SnapPointsType = SnapPointsType.MandatorySingle,
SnapPointsAlignment = SnapPointsAlignment.End
},
// ...
};
When a user swipes to initiate a scroll, the bottom item will be aligned with the bottom
of the view:
Display grouped data in a
CollectionView
Article • 04/03/2023 • 5 minutes to read
Large data sets can often become unwieldy when presented in a continually scrolling
list. In this scenario, organizing the data into groups can improve the user experience by
making it easier to navigate the data.
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
Group data
Data must be grouped before it can be displayed. This can be accomplished by creating
a list of groups, where each group is a list of items. The list of groups should be an
IEnumerable<T> collection, where T defines two pieces of data:
A group name.
An IEnumerable collection that defines the items belonging to the group.
Example
When grouping data, the first step is to create a type that models a single item. The
following example shows the Animal class from the sample application:
C#
The Animal class models a single item. A type that models a group of items can then be
created. The following example shows the AnimalGroup class from the sample
application:
C#
Name = name;
The AnimalGroup class inherits from the List<T> class and adds a Name property that
represents the group name.
C#
This code defines a collection named Animals , where each item in the collection is an
AnimalGroup object. Each AnimalGroup object comprises a name, and a List<Animal>
collection that defines the Animal objects in the group.
C#
new Animal
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/0/08/01_Schwarzbär.jpg"
},
new Animal
Location = "Asia",
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Ursus_thibetanus_
3_%28Wroclaw_zoo%29.JPG/180px-Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG"
},
// ...
}));
new Animal
Name = "Baboon",
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28S
erengeti%2C_2009%29.jpg/200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg"
},
new Animal
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Ri
ca.jpg/200px-Capuchin_Costa_Rica.jpg"
},
new Animal
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonkey.jpg/22
0px-BlueMonkey.jpg"
},
// ...
}));
This code creates two groups in the Animals collection. The first AnimalGroup is named
Bears , and contains a List<Animal> collection of bear details. The second AnimalGroup
is named Monkeys , and contains a List<Animal> collection of monkey details.
XAML
IsGrouped="true">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
...
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
C#
IsGrouped = true
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
// ...
7 Note
By default, CollectionView will display the group name in the group header and
footer. This behavior can be changed by customizing the group header and group
footer.
XAML
IsGrouped="true">
...
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
BackgroundColor="LightGray"
FontSize="18"
FontAttributes="Bold" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
</CollectionView>
In this example, each group header is set to a Label that displays the group name, and
that has other appearance properties set. The following screenshot shows the
customized group header:
Customize the group footer
The appearance of each group footer can be customized by setting the
CollectionView.GroupFooterTemplate property to a DataTemplate:
XAML
IsGrouped="true">
...
<CollectionView.GroupFooterTemplate>
<DataTemplate>
Margin="0,0,0,10" />
</DataTemplate>
</CollectionView.GroupFooterTemplate>
</CollectionView>
In this example, each group footer is set to a Label that displays the number of items in
the group. The following screenshot shows the customized group footer:
Empty groups
When a CollectionView displays grouped data, it will display any groups that are empty.
Such groups will be displayed with a group header and footer, indicating that the group
is empty. The following screenshot shows an empty group:
7 Note
On iOS 10, group headers and footers for empty groups may all be displayed at the
top of the CollectionView.
XAML
IsGrouped="true" />
In this scenario, meaningful data can be displayed by overriding the ToString method in
the type that models a single item, and the type that models a single group of items.
IndicatorView
Article • 04/03/2023 • 3 minutes to read
The .NET Multi-platform App UI (.NET MAUI) IndicatorView is a control that displays
indicators that represent the number of items, and current position, in a CarouselView:
IndicatorSize , of type double , the size of the indicators. The default value is 6.0.
IndicatorLayout , of type Layout<View> , defines the layout class used to render the
IndicatorView. This property is set by .NET MAUI, and does not typically need to be
set by developers.
IndicatorTemplate , of type DataTemplate, the template that defines the
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
Create an IndicatorView
To add indicators to a page, create an IndicatorView object and set its IndicatorColor
and SelectedIndicatorColor properties. In addition, set the CarouselView.IndicatorView
property to the name of the IndicatorView object.
XAML
<StackLayout>
IndicatorView="indicatorView">
<CarouselView.ItemTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
HorizontalOptions="Center" />
</StackLayout>
) Important
Circle specifies that the indicator shapes will be circular. This is the default value
XAML
<IndicatorView x:Name="indicatorView"
IndicatorsShape="Square"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray" />
XAML
<IndicatorView x:Name="indicatorView"
IndicatorSize="18" />
XAML
<IndicatorView x:Name="indicatorView"
MaximumVisible="6" />
XAML
<StackLayout>
IndicatorView="indicatorView">
<CarouselView.ItemTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
Margin="0,0,0,40"
IndicatorColor="Transparent"
SelectedIndicatorColor="Transparent"
HorizontalOptions="Center">
<IndicatorView.IndicatorTemplate>
<DataTemplate>
<Label Text=""
FontFamily="ionicons"
FontSize="12" />
</DataTemplate>
</IndicatorView.IndicatorTemplate>
</IndicatorView>
</StackLayout>
The elements specified in the DataTemplate define the appearance of each indicator. In
this example, each indicator is a Label that displays a font icon.
XAML
<ContentPage ...>
<ContentPage.Resources>
<Style x:Key="IndicatorLabelStyle"
TargetType="Label">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="TextColor"
Value="LightGray" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="TextColor"
Value="Black" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
<StackLayout>
...
<IndicatorView x:Name="indicatorView"
Margin="0,0,0,40"
IndicatorColor="Transparent"
SelectedIndicatorColor="Transparent"
HorizontalOptions="Center">
<IndicatorView.IndicatorTemplate>
<DataTemplate>
<Label Text=""
FontFamily="ionicons"
FontSize="12"
</DataTemplate>
</IndicatorView.IndicatorTemplate>
</IndicatorView>
</StackLayout>
</ContentPage>
In this example, the Selected visual state specifies that the indicator that represents the
current position will have its TextColor set to black. Otherwise the TextColor of the
indicator will be light gray:
The .NET Multi-platform App UI (.NET MAUI) ListView displays a scrollable vertical list of
selectable data items. While ListView manages the appearance of the list, the
appearance of each item in the list is defined by a DataTemplate that uses a Cell to
display items. .NET MAUI includes cell types to display combinations of text and images,
and you can also define custom cells that display any content you want. ListView also
includes support for displaying headers and footers, grouped data, pull-to-refresh, and
context menu items.
The ListView class derives from the ItemsView<Cell> class, from which it inherits the
following properties:
Footer , of type object , specifies the string or view that will be displayed at the
end of the list.
FooterTemplate , of type DataTemplate, specifies the DataTemplate to use to format
the Footer .
GroupHeaderTemplate , of type DataTemplate, defines the DataTemplate used to
define the appearance of the header of each group. This property is mutually
exclusive with the GroupDisplayBinding property. Therefore, setting this property
will set GroupDisplayBinding to null .
HasUnevenRows , of type bool , indicates whether items in the list can have rows of
to cause the ListView to refresh its data. The default value of this property is false .
IsRefreshing , of type bool , indicates whether the ListView is currently refreshing.
false .
SelectedItem , of type object , represents the currently selected item in the
ListView.
SelectionMode , of type ListViewSelectionMode , indicates whether items can be
selected in the ListView or not. The default value of this property is Single .
SeparatorColor , of type Color, defines the color of the bar that separates items in
the list.
SeparatorVisibility , of type SeparatorVisibility , defines whether separators are
visible between items.
VerticalScrollBarVisibility , of type ScrollBarVisibility , indicates when the
vertical scroll bar will be visible.
All of these properties are backed by BindableProperty objects, which means that they
can be targets of data bindings, and styled.
ItemSelected , which is raised when a new item in the list is selected. The
ItemIndex properties.
) Important
ListView can be populated with data by using data binding to bind its ItemsSource
property to an IEnumerable collection. In XAML, this is achieved with the Binding
markup extension:
XAML
listView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
In this example, the ItemsSource property data binds to the Monkeys property of the
connected viewmodel.
7 Note
XAML
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The elements specified in the DataTemplate define the appearance of each item in the
list, and the child of the DataTemplate must be a Cell object. In the example, layout
within the DataTemplate is managed by a Grid. The Grid contains an Image object, and
two Label objects, that all bind to properties of the Monkey class:
C#
The following screenshot shows the result of templating each item in the list:
Cells
The appearance of each item in a ListView is defined by a DataTemplate, and the
DataTemplate must reference a Cell class to display items. Each cell represents an item
of data in the ListView. .NET MAUI includes the following built-in cells:
Typically, SwitchCell and EntryCell will only be used in a TableView and won't be used in
a ListView. For more information about SwitchCell and EntryCell, see TableView.
Text cell
A TextCell displays primary and secondary text on separate lines. TextCell defines the
following properties:
Command , of type ICommand , defines the command that's executed when the cell is
tapped.
CommandParameter , of type object , represents the parameter that's passed to the
command.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The following example shows using a TextCell to define the appearance of items in a
ListView:
XAML
<ListView.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Image cell
An ImageCell displays an image with primary and secondary text on separate lines.
ImageCell inherits the properties from TextCell, and defines the ImageSource property, of
type ImageSource , which specifies the image to be displayed in the cell. This property is
backed by a BindableProperty object, which means it can be the target of data bindings,
and be styled.
The following example shows using an ImageCell to define the appearance of items in a
ListView:
XAML
<ListView.ItemTemplate>
<DataTemplate>
Text="{Binding Name}"
</ListView.ItemTemplate>
</ListView>
7 Note
The View property is the content property of the ViewCell class, and therefore does
not need to be explicitly set from XAML.
The following example shows using a ViewCell to define the appearance of items in a
ListView:
XAML
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Inside the ViewCell, layout can be managed by any .NET MAUI layout. In this example,
layout is managed by a Grid. The Grid contains an Image object, and two Label objects,
that all bind to properties of the Monkey class.
The following screenshot shows the result of templating each item in the list:
XAML
<ContentPage ...
xmlns:templates="clr-namespace:ListViewDemos.Templates">
<ContentPage.Resources>
<DataTemplate x:Key="AmericanMonkeyTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="OtherMonkeyTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<templates:MonkeyDataTemplateSelector x:Key="MonkeySelector"
AmericanMonkey="{StaticResource
AmericanMonkeyTemplate}"
OtherMonkey="{StaticResource
OtherMonkeyTemplate}" />
</ContentPage.Resources>
<StackLayout Margin="20">
</StackLayout>
</ContentPage>
C#
Single – indicates that a single item can be selected, with the selected item being
ListView defines an ItemSelected event that's raised when the SelectedItem property
changes, either due to the user selecting an item from the list, or when an app sets the
property. The SelectedItemChangedEventArgs object that accompanies this event has
SelectedItem and SelectedItemIndex properties.
When the SelectionMode property is set to Single , a single item in the ListView can be
selected. When an item is selected, the SelectedItem property will be set to the value of
the selected item. When this property changes, the ItemSelected event is raised.
The following example shows a ListView that can respond to single item selection:
XAML
ItemSelected="OnItemSelected">
...
</ListView>
In this example, the OnItemSelected event handler is executed when the ItemSelected
event fires, with the event handler retrieving the selected item:
C#
Disable selection
ListView selection is enabled by default. However, it can be disabled by setting the
SelectionMode property to None :
XAML
<ListView ...
SelectionMode="None" />
When the SelectionMode property is set to None , items in the ListView cannot be
selected, the SelectedItem property will remain null , and the ItemSelected event will
not be fired.
Cache data
ListView is a powerful view for displaying data, but it has some limitations. Scrolling
performance can suffer when using custom cells, especially when they contain deeply
nested view hierarchies or use certain layouts that require complex measurement.
Fortunately, there are techniques you can use to avoid poor performance.
A ListView is often used to display much more data than fits onscreen. For example, a
music app might have a library of songs with thousands of entries. Creating an item for
every entry would waste valuable memory and perform poorly. Creating and destroying
rows constantly would require the app to instantiate and cleanup objects constantly,
which would also perform poorly.
To conserve memory, the native ListView equivalents for each platform have built-in
features for reusing rows. Only the cells visible on screen are loaded in memory and the
content is loaded into existing cells. This pattern prevents the app from instantiating
thousands of objects, saving time and memory.
RetainElement , specifies that the ListView will generate a cell for each item in the
list.
RecycleElement , specifies that the ListView will attempt to minimize its memory
Retain elements
The RetainElement caching strategy specifies that the ListView will generate a cell for
each item in the list, and is the default ListView behavior. It should be used in the
following circumstances:
Recycle elements
The RecycleElement caching strategy specifies that the ListView will attempt to minimize
its memory footprint and execution speed by recycling list cells. This mode doesn't
always offer a performance improvement, and testing should be performed to
determine any improvements. However, it's the preferred choice, and should be used in
the following circumstances:
During virtualization the cell will have its binding context updated, and so if an app uses
this mode it must ensure that binding context updates are handled appropriately. All
data about the cell must come from the binding context or consistency errors may
occur. This problem can be avoided by using data binding to display cell data.
Alternatively, cell data should be set in the OnBindingContextChanged override, rather
than in the custom cell's constructor, as shown in the following example:
C#
public CustomCell()
View = image;
base.OnBindingContextChanged();
if (item != null)
image.Source = item.ImageUrl;
7 Note
7 Note
XAML
<ListView CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C#
C#
...
XAML
<local:CustomListView>
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
</local:CustomListView>
ListView defines the following properties for specifying the header and footer:
Header , of type object , specifies the string, binding, or view that will be displayed
the Header .
Footer , of type object , specifies the string, binding, or view that will be displayed
at the end of the list.
FooterTemplate , of type DataTemplate, specifies the DataTemplate to use to format
the Footer .
These properties are backed by BindableProperty objects, which means that the
properties can be targets of data bindings.
XAML
Header="Monkeys"
Footer="2022">
...
</ListView>
XAML
<ListView.Header>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="12"
FontAttributes="Bold" />
</StackLayout>
</ListView.Header>
<ListView.Footer>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Friends of Monkey"
FontSize="12"
FontAttributes="Bold" />
</StackLayout>
</ListView.Footer>
...
</ListView>
XAML
Header="{Binding .}"
Footer="{Binding .}">
<ListView.HeaderTemplate>
<DataTemplate>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="12"
FontAttributes="Bold" />
</StackLayout>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.FooterTemplate>
<DataTemplate>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Friends of Monkey"
FontSize="12"
FontAttributes="Bold" />
</StackLayout>
</DataTemplate>
</ListView.FooterTemplate>
...
</ListView>
XAML
<ListView ...
SeparatorVisibility="None" />
In addition, when the separator is enabled, it's color can be set with the SeparatorColor
property:
XAML
<ListView ...
SeparatorColor="Blue" />
Size items
By default, all items in a ListView have the same height, which is derived from the
contents of the DataTemplate that defines the appearance of each item. However, this
behavior can be changed with the HasUnevenRows and RowHeight properties. By default,
the HasUnevenRows property is false .
The RowHeight property can be set to an int that represents the height of each item in
the ListView, provided that HasUnevenRows is false . When HasUnevenRows is set to true ,
each item in the ListView can have a different height. The height of each item will be
derived from the contents of the item's DataTemplate, and so each item will be sized to
its content.
C#
viewCell.ForceUpdateSize();
}
2 Warning
Right-to-left layout
ListView can layout its content in a right-to-left flow direction by setting its
FlowDirection property to RightToLeft . However, the FlowDirection property should
ideally be set on a page or root layout, which causes all the elements within the page, or
root layout, to respond to the flow direction:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ListViewDemos.RightToLeftListPage"
FlowDirection="RightToLeft">
<StackLayout Margin="20">
...
</ListView>
</StackLayout>
</ContentPage>
The default FlowDirection for an element with a parent is MatchParent . Therefore, the
ListView inherits the FlowDirection property value from the StackLayout, which in turn
inherits the FlowDirection property value from the ContentPage.
Data must be grouped before it can be displayed. This can be accomplished by creating
a list of groups, where each group is a list of items. The list of groups should be an
IEnumerable<T> collection, where T defines two pieces of data:
A group name.
An IEnumerable collection that defines the items belonging to the group.
Example
When grouping data, the first step is to create a type that models a single item. The
following example shows the Animal class:
C#
The Animal class models a single item. A type that models a group of items can then be
created. The following example shows the AnimalGroup class:
C#
Name = name;
The AnimalGroup class inherits from the List<T> class and adds a Name property that
represents the group name.
C#
This code defines a collection named Animals , where each item in the collection is an
AnimalGroup object. Each AnimalGroup object comprises a name, and a List<Animal>
C#
Animals.Add(new AnimalGroup("Bears", new List<Animal>
new Animal
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/0/08/01_Schwarzbär.jpg"
},
new Animal
Location = "Asia",
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Ursus_thibetanus_
3_%28Wroclaw_zoo%29.JPG/180px-Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG"
},
// ...
}));
new Animal
Name = "Baboon",
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28S
erengeti%2C_2009%29.jpg/200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg"
},
new Animal
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Ri
ca.jpg/200px-Capuchin_Costa_Rica.jpg"
},
new Animal
ImageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonkey.jpg/22
0px-BlueMonkey.jpg"
},
// ...
}));
This code creates two groups in the Animals collection. The first AnimalGroup is named
Bears , and contains a List<Animal> collection of bear details. The second AnimalGroup
is named Monkeys , and contains a List<Animal> collection of monkey details.
ListView will display grouped data, provided that the data has been grouped correctly,
by setting the IsGroupingEnabled property to true :
XAML
IsGroupingEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10">
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C#
IsGroupingEnabled = true
};
listView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
// ...
The appearance of each item in the ListView is defined by setting its ItemTemplate
property to a DataTemplate. For more information, see Define item appearance.
7 Note
By default, ListView will display the group name in the group header. This behavior
can be changed by customizing the group header.
XAML
IsGroupingEnabled="True">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
BackgroundColor="LightGray"
FontSize="18"
FontAttributes="Bold" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
...
</ListView>
In this example, each group header is set to a Label that displays the group name, and
that has other appearance properties set. The following screenshot shows the
customized group header:
) Important
XAML
IsGroupingEnabled="true" />
In this scenario, meaningful data can be displayed by overriding the ToString method in
the type that models a single item, and the type that models a single group of items.
Control scrolling
ListView defines two ScrollTo methods, that scroll items into view. One of the overloads
scrolls the specified item into view, while the other scrolls the specified item in the
specified group into view. Both overloads have additional arguments that allow the
exact position of the item after the scroll has completed to be specified, and whether to
animate the scroll.
ListView defines a ScrollToRequested event that is fired when one of the ScrollTo
methods is invoked. The ScrollToRequestedEventArgs object that accompanies the
ScrollToRequested event has many properties, including ShouldAnimate , Element, Mode ,
and Position . Some of these properties are set from the arguments specified in the
ScrollTo method calls.
In addition, ListView defines a Scrolled event that is fired to indicate that scrolling
occurred. The ScrolledEventArgs object that accompanies the Scrolled event has
ScrollX and ScrollY properties.
Detect scrolling
ListView defines a Scrolled event which is fired to indicate that scrolling occurred. The
ItemsViewScrolledEventArgs class, which represents the object that accompanies the
The following XAML example shows a ListView that sets an event handler for the
Scrolled event:
XAML
<ListView Scrolled="OnListViewScrolled">
...
</ListView>
C#
listView.Scrolled += OnListViewScrolled;
In this code example, the OnListViewScrolled event handler is executed when the
Scrolled event fires:
C#
// Custom logic
) Important
The Scrolled event is fired for user initiated scrolls, and for programmatic scrolls.
C#
Alternatively, an item in grouped data can be scrolled into view by specifying the item
and the group. The following example shows how to scroll the Proboscis Monkey item
in the Monkeys group into view:
C#
7 Note
C#
MakeVisible
C#
Start
The ScrollToPosition.Start member indicates that the item should be scrolled to the
start of the view:
C#
Center
The ScrollToPosition.Center member indicates that the item should be scrolled to the
center of the view:
C#
End
The ScrollToPosition.End member indicates that the item should be scrolled to the end
of the view:
C#
Default indicates the default scroll bar behavior for the platform, and is the
default value for the HorizontalScrollBarVisibility and
VerticalScrollBarVisibility properties.
Always indicates that scroll bars will be visible, even when the content fits in the
view.
Never indicates that scroll bars will not be visible, even if the content doesn't fit in
the view.
XAML
<ListView x:Name="listView"
ItemsSource="{Binding Monkeys}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Favorite"
Command="{Binding Source={x:Reference
listView}, Path=BindingContext.FavoriteCommand}"
CommandParameter="{Binding}" />
<MenuItem Text="Delete"
Command="{Binding Source={x:Reference
listView}, Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding}" />
</ViewCell.ContextActions>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The MenuItem objects are revealed when an item in the ListView is right-clicked:
Pull to refresh
ListView supports pull to refresh functionality, which enables the data being displayed to
be refreshed by pulling down on the list of items.
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding RefreshCommand}"
IsRefreshing="{Binding IsRefreshing}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In this example, when the user initiates a refresh, the ICommand defined by the
RefreshCommand property is executed, which should refresh the items being displayed. A
refresh visualization is shown while the refresh occurs, which consists of an animated
progress circle. The value of the IsRefreshing property indicates the current state of the
refresh operation. When a refresh is triggered, this property will automatically transition
to true . Once the refresh completes, you should reset the property to false .
Picker
Article • 04/03/2023 • 7 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Picker displays a short list of items, from
which the user can select an item.
scaling preferences set in the operating system. The default value of this property
is true .
FontFamily of type string , which defaults to null .
null .
SelectedIndex of type int , the index of the selected item, which defaults to -1.
SelectedItem of type object , the selected item, which defaults to null .
casing of text.
Title of type string , which defaults to null .
TitleColor of type Color, the color used to display the Title text.
All of the properties are backed by BindableProperty objects, which means that they can
be styled, and the properties can be targets of data bindings. The SelectedIndex and
SelectedItem properties have a default binding mode of BindingMode.TwoWay , which
means that they can be targets of data bindings in an application that uses the Model-
View-ViewModel (MVVM) pattern. For information about setting font properties, see
Fonts.
A Picker doesn't show any data when it's first displayed. Instead, the value of its Title
property is shown as a placeholder, as shown in the following iOS screenshot:
When the Picker gains focus, its data is displayed and the user can select an item:
The Picker fires a SelectedIndexChanged event when the user selects an item. Following
selection, the selected item is displayed by the Picker:
XAML
<Picker x:Name="picker"
Title="Select a monkey">
<Picker.ItemsSource>
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
7 Note
The x:Array element requires a Type attribute indicating the type of the items in
the array.
C#
monkeyList.Add("Baboon");
monkeyList.Add("Capuchin Monkey");
monkeyList.Add("Blue Monkey");
monkeyList.Add("Squirrel Monkey");
monkeyList.Add("Howler Monkey");
monkeyList.Add("Japanese Macaque");
picker.ItemsSource = monkeyList;
The following XAML example shows how to retrieve the SelectedItem property value
from the Picker:
XAML
<Label Text="{Binding Source={x:Reference picker}, Path=SelectedItem}" />
C#
C#
if (selectedIndex != -1)
monkeyNameLabel.Text = (string)picker.ItemsSource[selectedIndex];
In this example, the event handler obtains the SelectedIndex property value, and uses
the value to retrieve the selected item from the ItemsSource collection. This is
functionally equivalent to retrieving the selected item from the SelectedItem property.
Each item in the ItemsSource collection is of type object , and so must be cast to a
string for display.
7 Note
XAML
ItemsSource="{Binding Monkeys}"
C#
picker.SetBinding(Picker.ItemsSourceProperty, "Monkeys");
In this example, the ItemsSource property data binds to the Monkeys property of the
binding context, which returns an IList<Monkey> collection. The following code example
shows the Monkey class, which contains four properties:
C#
When binding to a list of objects, the Picker must be told which property to display from
each object. This is achieved by setting the ItemDisplayBinding property to the required
property from each object. In the code examples above, the Picker is set to display each
Monkey.Name property value.
Data binding can be used to set an object to the SelectedItem property value when it
changes:
XAML
ItemsSource="{Binding Monkeys}"
ItemDisplayBinding="{Binding Name}"
C#
picker.SetBinding(Picker.ItemsSourceProperty, "Monkeys");
picker.SetBinding(Picker.SelectedItemProperty, "SelectedMonkey");
nameLabel.SetBinding(Label.TextProperty, "SelectedMonkey.Name");
locationLabel.SetBinding(Label.TextProperty, "SelectedMonkey.Location");
image.SetBinding(Image.SourceProperty, "SelectedMonkey.ImageUrl");
detailsLabel.SetBinding(Label.TextProperty, "SelectedMonkey.Details");
The SelectedItem property data binds to the SelectedMonkey property of the binding
context, which is of type Monkey . Therefore, when the user selects an item in the Picker,
the SelectedMonkey property will be set to the selected Monkey object. The
SelectedMonkey object data is displayed in the user interface by Label and Image views.
7 Note
<Picker.Items>
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</Picker.Items>
</Picker>
C#
picker.Items.Add("Baboon");
picker.Items.Add("Capuchin Monkey");
picker.Items.Add("Blue Monkey");
picker.Items.Add("Squirrel Monkey");
picker.Items.Add("Howler Monkey");
picker.Items.Add("Japanese Macaque");
In addition to adding data using the Items.Add method, data can also be inserted into
the collection by using the Items.Insert method.
C#
if (selectedIndex != -1)
monkeyNameLabel.Text = picker.Items[selectedIndex];
This method obtains the SelectedIndex property value, and uses the value to retrieve
the selected item from the Items collection. Because each item in the Items collection is
a string , they can be displayed by a Label without requiring a cast.
7 Note
The .NET Multi-platform App UI (.NET MAUI) TableView displays a table of scrollable
items that can be grouped into sections. A TableView is typically used for displaying
items where each row has a different appearance, such as presenting a table of settings.
While TableView manages the appearance of the table, the appearance of each item in
the table is defined by a Cell. .NET MAUI includes five cell types that are used to display
different combinations of data, and you can also define custom cells that display any
content you want.
HasUnevenRows , of type bool , indicates whether items in the table can have rows of
different heights. The default value of this property is false .
Root , of type TableRoot , defines the child of the TableView.
RowHeight , of type int , determines the height of each row when HasUnevenRows is
false .
The value of the Intent property helps to define the TableView appearance on iOS only.
This property should be set to a value of the TableIntent enumeration, which defines
the following members:
Create a TableView
To create a table, create a TableView object and set its Intent property to a TableIntent
member. The child of a TableView must be a TableRoot object, which is parent to one or
more TableSection objects. Each TableSection consists of an optional title whose color
can also be set, and one or more Cell objects.
XAML
<TableView Intent="Menu">
<TableRoot>
<TableSection Title="Chapters">
</TableSection>
</TableRoot>
</TableView>
7 Note
Each TextCell can execute a command when tapped, provided that the Command
property is set to a valid ICommand implementation.
Define cell appearance
Each item in a TableView is defined by a Cell object, and the Cell type used defines the
appearance of the cell's data. .NET MAUI includes the following built-in cells:
Text cell
A TextCell displays primary and secondary text on separate lines. TextCell defines the
following properties:
Command , of type ICommand , defines the command that's executed when the cell is
tapped.
CommandParameter , of type object , represents the parameter that's passed to the
command.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
The following example shows using a TextCell to define the appearance of items in a
TableView:
XAML
<TableView Intent="Menu">
<TableRoot>
<TableSection Title="Chapters">
</TableSection>
</TableRoot>
</TableView>
Image cell
An ImageCell displays an image with primary and secondary text on separate lines.
ImageCell inherits the properties from TextCell, and defines the ImageSource property, of
type ImageSource , which specifies the image to be displayed in the cell. This property is
backed by a BindableProperty object, which means it can be the target of data bindings,
and be styled.
The following example shows using an ImageCell to define the appearance of items in a
TableView:
XAML
<TableView Intent="Menu">
<TableRoot>
ImageSource="xbox.png" />
ImageSource="xbox.png" />
ImageSource="xbox.png" />
ImageSource="xbox.png" />
</TableSection>
</TableRoot>
</TableView>
Switch cell
A SwitchCell displays text and a switch that can be switched on or off. SwitchCell defines
the following properties:
Text , of type string , defines the text to display next to the switch.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
SwitchCell also defines an OnChanged event that's raised when the switch changes state.
The ToggledEventArgs object that accompanies this event defines a Value property, that
indicates whether the switch is on or off.
The following example shows using a SwitchCell to define the appearance of items in a
TableView:
XAML
<TableView Intent="Settings">
<TableRoot>
<TableSection>
On="False" />
<SwitchCell Text="Notifications"
On="True" />
</TableSection>
</TableRoot>
</TableView>
Entry cell
An EntryCell displays a label and text data that's editable. EntryCell defines the following
properties:
property is empty.
Text , of type string , defines the text that's editable.
the text.
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
EntryCell also defines a Completed event that's raised when the user hits the return key,
to indicate that editing is complete.
The following example shows using an EntryCell to define the appearance of items in a
TableView:
XAML
<TableView Intent="Settings">
<TableRoot>
<TableSection>
<EntryCell Label="Login"
Placeholder="username" />
<EntryCell Label="Password"
Placeholder="password" />
</TableSection>
</TableRoot>
</TableView>
View cell
A ViewCell is a custom cell whose appearance is defined by a View. ViewCell defines a
View property, of type View, which defines the view that represents the content of the
cell. This property is backed by a BindableProperty object, which means it can be the
target of data bindings, and be styled.
7 Note
The View property is the content property of the ViewCell class, and therefore does
not need to be explicitly set from XAML.
The following example shows using a ViewCell to define the appearance of an item in a
TableView:
XAML
<TableView Intent="Settings">
<TableRoot>
<TableSection Title="Silent">
<ViewCell>
<Grid RowDefinitions="Auto,Auto"
ColumnDefinitions="0.5*,0.5*">
<Label Text="Vibrate"
Margin="10,10,0,0"/>
<Switch Grid.Column="1"
HorizontalOptions="End" />
<Slider Grid.Row="1"
Grid.ColumnSpan="2"
Margin="10"
Minimum="0"
Maximum="10"
Value="3" />
</Grid>
</ViewCell>
</TableSection>
</TableRoot>
</TableView>
Inside the ViewCell, layout can be managed by any .NET MAUI layout. The following
screenshot shows the resulting cell appearance:
Size items
By default, all cells of the same type in a TableView have the same height. However, this
behavior can be changed with the HasUnevenRows and RowHeight properties. By default,
the HasUnevenRows property is false .
The RowHeight property can be set to an int that represents the height of each item in
the TableView, provided that HasUnevenRows is false . When HasUnevenRows is set to
true , each item in the TableView can have a different height. The height of each item
will be derived from the contents of each cell, and so each item will be sized to its
content.
C#
label.IsVisible = !label.IsVisible;
viewCell.ForceUpdateSize();
In this example, the OnViewCellTapped event handler is executed in response to the cell
being tapped. The event handler updates the visibility of the Label object and the
Cell.ForceUpdateSize method updates the cell's size. If the Label has been made visible
the cell's height will increase. If the Label has been made invisible the cell's height will
decrease.
2 Warning
Right-to-left layout
TableView can layout its content in a right-to-left flow direction by setting its
FlowDirection property to RightToLeft . However, the FlowDirection property should
ideally be set on a page or root layout, which causes all the elements within the page, or
root layout, to respond to the flow direction:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TableViewDemos.RightToLeftTablePage"
FlowDirection="RightToLeft">
<TableView Intent="Settings">
...
</TableView>
</ContentPage>
The default FlowDirection for an element with a parent is MatchParent . Therefore, the
TableView inherits the FlowDirection property value from the ContentPage.
ContentView
Article • 04/03/2023 • 4 minutes to read
The .NET Multi-platform App UI (.NET MAUI) ContentView is a control that enables the
creation of custom, reusable controls.
The ContentView class defines a Content property, of type View, which represents the
content of the ContentView. This property is backed by a BindableProperty object, which
means that it can be the target of data bindings, and styled.
The ContentView class derives from the TemplatedView class, which defines the
ControlTemplate bindable property, of type ControlTemplate, which defines the
appearance of the control. For more information about the ControlTemplate property,
see Customize appearance with a ControlTemplate.
7 Note
This article demonstrates how to create a CardView control, which is a UI element that
displays an image, title, and description in a card-like layout.
CardTitle , of type string , which represents the title shown on the card.
CardDescription , of type string , which represents the description shown on the
card.
IconImageSource , of type ImageSource , which represents the image shown on the
card.
IconBackgroundColor , of type Color, which represents the background color for the
image shown on the card.
BorderColor , of type Color, which represents the color of the card border, image
border, and divider line.
CardColor , of type Color, which represents the background color of the card.
The following example shows the CardTitle bindable property in the code-behind file
for the CardView class:
C#
// ...
public CardView()
InitializeComponent();
XAML
<ContentView ...
x:Name="this"
x:Class="CardViewDemo.Controls.CardView">
BackgroundColor="{Binding CardColor}"
BorderColor="{Binding BorderColor}"
...>
<Grid>
...
BackgroundColor="{Binding IconBackgroundColor,
FallbackValue='Grey'}"
...>
<Image Source="{Binding IconImageSource}"
.. />
</Frame>
... />
... />
... />
</Grid>
</Frame>
</ContentView>
The ContentView element sets the x:Name property to this , which can be used to
access the object bound to the CardView instance. Elements in the layout set bindings
on their properties to values defined on the bound object. For more information about
data binding, see Data binding.
7 Note
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:CardViewDemo.Controls"
x:Class="CardViewDemo.CardViewXamlPage">
<ScrollView>
<StackLayout>
<controls:CardView BorderColor="DarkGray"
CardTitle="Slavko Vlasic"
IconBackgroundColor="SlateGray"
IconImageSource="user.png" />
</StackLayout>
</ScrollView>
</ContentPage>
For example, a CardView layout might occupy too much space for some use cases. A
ControlTemplate can be used to override the CardView layout to provide a more
compact view, suitable for a condensed list:
XAML
<ContentPage.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="CardViewCompressed">
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
BackgroundColor="{TemplateBinding
IconBackgroundColor}"
WidthRequest="100"
HeightRequest="100"
Aspect="AspectFill"
HorizontalOptions="Center"
VerticalOptions="Center" />
<StackLayout Grid.Column="1">
FontAttributes="Bold" />
</StackLayout>
</Grid>
</ControlTemplate>
</ResourceDictionary>
</ContentPage.Resources>
XAML
<controls:CardView ControlTemplate="{StaticResource CardViewCompressed}" />
The following screenshot shows a standard CardView instance, and multiple CardView
instances whose control templates have been overridden:
The TwoPaneView class represents a container with two views that size and position
content in the available space, either side-by-side or top-to-bottom. TwoPaneView
inherits from Grid so the easiest way to think about these properties is as if they are
being applied to a grid.
) Important
The TwoPaneView control only adapts to Android foldable devices that support the
Jetpack Window Manager API provided by Google (such as Microsoft Surface Duo).
On all other platforms and devices (i.e. other Android devices, iOS, macOS,
Windows) it acts like a configurable and responsive split view that can dynamically
show one or two panes, proportionally sized on the screen.
4. Add the UseFoldable() initialization method (and namespace) call to the project's
MauiApp class, in the CreateMauiApp method:
C#
...
...
return builder.Build();
C#
ConfigurationChanges = ConfigChanges.Orientation |
ConfigChanges.ScreenSize
| ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize |
ConfigChanges.UiMode
These values are required so that configuration changes and span state can be
more reliably reported for reliable dual-screen support.
Set up TwoPaneView
To add the TwoPaneView layout to your page:
XAML
xmlns:foldable="clr-
namespace:Microsoft.Maui.Controls.Foldable;assembly=Microsoft.Maui.Cont
rols.Foldable"
2. Add the TwoPaneView as the root element on the page, and add controls to Pane1
and Pane2 :
XAML
<foldable:TwoPaneView x:Name="twoPaneView">
<foldable:TwoPaneView.Pane1
BackgroundColor="#dddddd">
<Label
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center" />
</foldable:TwoPaneView.Pane1>
<foldable:TwoPaneView.Pane2>
</StackLayout>
</foldable:TwoPaneView.Pane2>
</foldable:TwoPaneView>
Wide the two panes are laid out horizontally. One pane is on the left and the other
is on the right. When on two screens this is the mode when the device is portrait.
Tall the two panes are laid out vertically. One pane is on top and the other is on
bottom. When on two screens this is the mode when the device is landscape.
Tall mode.
MinWideModeWidth indicates the minimum width the control must be to enter Wide
mode.
Pane1Length sets the width of Pane1 in Wide mode, the height of Pane1 in Tall
mode, and has no effect in SinglePane mode.
Pane2Length sets the width of Pane2 in Wide mode, the height of Pane2 in Tall
) Important
Troubleshooting
If the TwoPaneView layout isn't working as expected, double-check the set-up
instructions on this page. Omitting or misconfiguring the UseFoldable() method or the
ConfigurationChanges attribute values are common causes of errors.
Display pop-ups
Article • 01/09/2023 • 3 minutes to read
Display an alert
All .NET MAUI-supported platforms have a modal pop-up to alert the user or ask simple
questions of them. To display alerts, use the DisplayAlert method on any Page. The
following example shows a simple message to the user:
C#
The alert is displayed modally, and once dismissed the user continues interacting with
the app:
The DisplayAlert method can also be used to capture a user's response by presenting
two buttons and returning a bool . To get a response from an alert, supply text for both
buttons and await the method:
C#
After the user selects one of the options the response will be returned as a bool .
The DisplayAlert method also has overloads that accept a FlowDirection argument that
specifies the direction in which UI elements flow within the alert.
C#
After the user taps one of the buttons, the button label will be returned as a string .
Action sheets also support a destroy button, which is a button that represents
destructive behavior. The destroy button can be specified as the third string argument
to the DisplayActionSheet method, or can be left null . The following example specifies
a destroy button:
C#
async void OnActionSheetCancelDeleteClicked(object sender, EventArgs e)
7 Note
On iOS, the destroy button is rendered differently to the other buttons in the action
sheet.
Display a prompt
To display a prompt, call the DisplayPromptAsync on any Page, passing a title and
message as string arguments:
C#
accept , of type string , is the text for the accept button. This is an optional
which can be edited. This is an optional argument, whose default value is an empty
string .
C#
This code displays a predefined response of 10, limits the number of characters that can
be input to 2, and displays the numeric keyboard for user input:
Display a page as a pop-up
.NET MAUI supports modal page navigation. A modal page encourages users to
complete a self-contained task that can't be navigated away from until the task is
completed or canceled. For example, to display a form as a pop-up that requires users
to enter multiple pieces of data, create a ContentPage that contains the UI for your form
and then push it onto the navigation stack as a modal page. For more information, see
Perform modal navigation.
Display tooltips
Article • 11/08/2022 • 2 minutes to read
A .NET Multi-platform App UI (.NET MAUI) tooltip is a small rectangular popup that
displays a brief description of a view's purpose, when the user rests the pointer on the
view. Tooltips display automatically, typically when the user hovers the pointer over the
associated view:
On Android, tooltips are displayed when users long press the view. Tooltips remain
visible for a few seconds after the long press is released.
On iOS, to show a tooltip your app must be an iPhone or iPad app running on a
Mac with Apple silicon. Providing this criteria is met, tooltips are displayed when
the pointer is positioned over the view for a few seconds, and remain visible until
the pointer moves away from the view. Tooltips on iOS require the use of iOS
15.0+. For more information about using iPhone and iPad apps on a Mac with
Apple silicon, see Use iPhone and iPad apps on Mac with Apple silicon .
On Mac Catalyst, tooltips are displayed when the pointer is positioned over the
view for a few seconds. Tooltips remain visible until the pointer moves away from
the view. Tooltips on Mac Catalyst require the use of Mac Catalyst 15.0+.
On Windows, tooltips are displayed when the pointer hovers over the view.
Tooltips remain visible for a few seconds, or until the pointer stops hovering over
the view.
XAML
<Button Text="Save"
C#
By default, .NET Multi-platform App UI (.NET MAUI) apps use the Open Sans font on
each platform. However, this default can be changed, and additional fonts can be
registered for use in an app.
All controls that display text define properties that can be set to change font
appearance:
members: None , Bold , and Italic . The default value of this property is None .
FontSize , of type double .
text scaling preferences set in the operating system. The default value of this
property is true .
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
All controls that display text automatically use font scaling, which means that an app's
UI reflects text scaling preferences set in the operating system.
Register fonts
True type format (TTF) and open type font (OTF) fonts can be added to your app and
referenced by filename or alias, with registration being performed in the CreateMauiApp
method in the MauiProgram class. This is accomplished by invoking the ConfigureFonts
method on the MauiAppBuilder object. Then, on the IFontCollection object, call the
AddFont method to add the required font to your app:
C#
namespace MyMauiApp
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
fonts.AddFont("Lobster-Regular.ttf", "Lobster");
});
return builder.Build();
In the example above, the first argument to the AddFont method is the font filename,
while the second argument represents an optional alias by which the font can be
referenced when consuming it.
A font can be added to your app project by dragging it into the Resources\Fonts folder
of the project, where its build action will automatically be set to MauiFont. This creates a
corresponding entry in your project file. Alternatively, all fonts in the app can be
registered by using a wildcard in your project file:
XML
<ItemGroup>
</ItemGroup>
Fonts can also be added to other folders of your app project. However, in this scenario
their build action must be manually set to MauiFont in the Properties window.
7 Note
The * wildcard character indicates that all the files within the folder will be treated
as being font files. In addition, if you want to include files from sub-folders too,
then configure it using additional wildcard characters, for example,
Resources\Fonts\**\* .
Consume fonts
Registered fonts can be consumed by setting the FontFamily property of a control that
displays text to the font name, without the file extension:
XAML
<!-- Use font name -->
FontFamily="Lobster-Regular" />
XAML
FontFamily="Lobster" />
C#
FontFamily = "Lobster-Regular"
};
FontFamily = "Lobster"
};
XAML
<Label Text="Italics"
FontAttributes="Italic" />
C#
Text = "Italics",
FontAttributes = FontAttributes.Italic
};
};
XAML
FontSize="24" />
C#
FontSize = 24
};
7 Note
XAML
<Label Text="Scaling disabled"
FontSize="18"
FontAutoScalingEnabled="False" />
This approach is useful when you want to guarantee that text is displayed at a specific
size.
7 Note
Font auto scaling also works with font icons. For more information, see Display font
icons.
XAML
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
</OnPlatform>
</Label.FontFamily>
</Label>
The DeviceInfo.Platform property can be used in code to set font properties per
platform:
C#
};
For more information about providing platform-specific values, see Device information.
For information about the OnPlatform markup extension, see OnPlatform markup
extension.
Glyph – the unicode character value of the font icon, specified as a string .
Size – a double value that indicates the size, in device-independent units, of the
rendered font icon. The default value is 30. In addition, this property can be set to
a named font size.
FontFamily – a string representing the font family to which the font icon belongs.
Color – an optional Color value to be used when displaying the font icon.
This data is used to create a PNG, which can be displayed by any view that can display
an ImageSource . This approach permits font icons, such as emojis, to be displayed by
multiple views, as opposed to limiting font icon display to a single text presenting view,
such as a Label.
) Important
The following XAML example has a single font icon being displayed by an Image view:
XAML
<Image BackgroundColor="#D1D1D1">
<Image.Source>
<FontImageSource Glyph=""
FontFamily="{OnPlatform iOS=Ionicons,
Android=ionicons.ttf#}"
Size="44" />
</Image.Source>
</Image>
This code displays an XBox icon, from the Ionicons font family, in an Image view. Note
that while the unicode character for this icon is \uf30c , it has to be escaped in XAML
and so becomes  . The equivalent C# code is:
C#
Glyph = "\uf30c",
Size = 44
};
Drawing canvas
In .NET MAUI, the GraphicsView enables consumption of the Microsoft.Maui.Graphics
functionality, via a drawing canvas that's exposed as an ICanvas object. For more
information about the GraphicsView, see GraphicsView.
ICanvas defines the following properties that affect the appearance of objects that are
drawn on the canvas:
However, not all elements of the canvas are elements of the graphics state. The graphics
state does not include drawing objects, such as paths, and paint objects, such as
gradients. Typical elements of the graphics state on each platform include stroke and fill
data, and font data.
The graphics state of each ICanvas can be manipulated with the following methods:
7 Note
By default, the last drawn object obscures the objects drawn underneath it:
In this example, the cyan circle is drawn first, followed by the magenta circle, then the
yellow circle. Each circle obscures the circle drawn underneath it. This occurs because
the default blend mode is Normal , which means that the source is drawn over the
destination. However, it's possible to specify a different blend mode for a different
result. For example, if you specify DestinationOver , then in the area where the source
and destination intersect, the destination is drawn over the source.
The 28 members of the BlendMode enumeration can be divided into three categories:
Darken SourceAtop
Lighten DestinationOver
ColorDodge DestinationIn
ColorBurn DestinationOut
SoftLight DestinationAtop
HardLight Xor
Difference PlusDarker
Exclusion PlusLighter
The order that the members are listed in the table above is the same as in the
BlendMode enumeration. The first column lists the 12 separable blend modes, while the
second column lists the non-separable blend modes. Finally, the third column lists the
Porter-Duff blend modes.
In the image above, the destination is a transparent rectangle except for a brown area
that occupies the left and top two-thirds of the display surface. The source is also a
transparent rectangle except for a blue area that occupies the right and bottom two-
thirds of the display surface. Displaying the source on the destination produces the
following result:
The transparent pixels of the source allow the background to show through, while the
blue source pixels obscure the background. This is the normal case, using the default
blend mode of Normal . However, it's possible to specify that in the area where the
source and destination intersect, the destination appears instead of the source, using
the DestinationOver blend mode:
The DestinationIn blend mode displays only the area where the destination and source
intersect, using the destination color:
The Xor blend mode causes nothing to appear where the two areas overlap:
The colored destination and source rectangles effectively divide the display surface into
four unique areas that can be colored in different ways, corresponding to the presence
of the destination and source rectangles:
The upper-right and lower-left rectangles are always blank because both the destination
and source are transparent in those areas. The destination color occupies the upper-left
area, so that area can either be colored with the destination color or not at all. Similarly,
the source color occupies the lower-right area, so that area can be colored with the
source color or not at all.
Clear
Copy Source X
SourceIn Source
SourceOut X
SourceAtop X Source
DestinationOver X Destination X
DestinationIn Destination
DestinationOut X
DestinationAtop Destination X
Xor X X
PlusDarker X Sum X
PlusLighter X Sum X
The Over suffix indicates what is visible in the intersection. Either the source or
destination is drawn over the other.
The In suffix means that only the intersection is colored. The output is restricted to
only the part of the source or destination that is in the other.
The Out suffix means that the intersection isn't colored. The output is only the part
of the source or destination that is out of the intersection.
The Atop suffix is the union of In and Out. It includes the area where the source or
destination is atop of the other.
7 Note
These blend modes are symmetrical. The source and destination can be exchanged,
and all the modes are still available.
The PlusLighter blend mode sums the source and destination. Then, for values above 1,
white is displayed. Similarly, the PlusDarker blend mode sums the source and
destination, but subtracts 1 from the resulting values, with values below 0 becoming
black.
Separable blend modes
The separable blend modes alter the individual red, green, and blue color components
of a graphical object.
The following table shows the separable blend modes, with brief explanations of what
they do. In the table, Dc and Sc refer to the destination and source colors, and the
second column shows the source color that produces no change:
Difference Black Subtracts the darker color from the lighter color: Abs(Dc - Sc)
7 Note
If the source is transparent, then the separable blend modes have no effect.
The following example uses the Multiply blend mode to draw three overlapping circles
of cyan, magenta, and yellow:
C#
canvas.BlendMode = BlendMode.Multiply;
canvas.FillColor = Colors.Cyan;
canvas.FillCircle(center1, radius);
canvas.FillColor = Colors.Magenta;
canvas.FillCircle(center2, radius);
canvas.FillColor = Colors.Yellow;
canvas.FillCircle(center3, radius);
The result is that a combination of any two colors produces red, green, and blue, and a
combination of all three colors produces black.
The hue value represents the dominant wavelength of the color. Hue values range
from 0 to 360, and cycle through the additive and subtractive primary colors. Red
is the value 0, yellow is 60, green is 120, cyan is 180, blue is 240, magenta is 300,
and the cycle returns to red at 360. If there is no dominant color, for example the
color is white or black or a gray shade, then the hue is undefined and usually set to
0.
The saturation value indicates the purity of the color, and can range from 0 to 100.
A saturation value of 100 is the purest color while values lower than 100 cause the
color to become more grayish. A saturation value of 0 results in a shade of gray.
The luminosity value indicates how bright the color is. A luminosity value of 0 is
black regardless of other values. Similarly, a luminosity value of 100 is white.
The HSL value (0,100,50) is the RGB value (255,0,0), which is pure red. The HSL value
(180,100,50) is the RGB value (0, 255, 255), which is pure cyan. As the saturation is
decreased, the dominant color component is decreased and the other components are
increased. At a saturation level of 0, all the components are the same and color is a gray
shade.
1. Convert the source and destination objects from their original color space to the
HSL color space.
2. Create the composited object from a combination of hue, saturation, and
luminosity components, from the source and destination objects.
3. Convert the result back to the original color space.
The following table lists how which HSL components are composited for each non-
separable blend mode:
The Color class, in the Microsoft.Maui.Graphics namespace, lets you specify colors as
Red-Green-Blue (RGB) values, Hue-Saturation-Luminosity (HSL) values, Hue-Saturation-
Value (HSV) values, or with a color name. An Alpha channel is also available to indicate
transparency.
Color objects can be created with Color constructors, which can be used to specify a
gray shade, an RGB value, or an RGB value with transparency. Constructor overloads
accept float values ranging from 0 to 1, byte , and int values.
7 Note
You can also use the following static methods to create Color objects:
A))).
Color.FromUint from a uint value calculated as (B + 256 * (G + 256 * (R + 256 *
A))).
Color.FromArgb from a string -based hexadecimal value in the form
7 Note
In addition to the methods listed above, the Color class also has Parse and
TryParse methods that create Color objects from string arguments.
Once created, a Color object is immutable. The characteristics of the color can be
obtained from the following float fields, that range from 0 to 1:
In addition, the characteristics of the color can be obtained from the following methods:
GetHue, which returns a float that represents the hue channel of the color.
GetSaturation, which returns a float that represents the saturation channel of the
color.
GetLuminosity, which returns a float that represents the luminosity channel of the
color.
Named colors
The Colors class defines 148 public static read-only fields for common colors, such as
AntiqueWhite , MidnightBlue , and YellowGreen .
Modify a color
The following instance methods modify an existing color to create a new color:
Conversions
The following instance methods convert a Color to an alternative representation:
AsPaint returns a SolidPaint object whose Color property is set to the color.
ToHex returns a hexadecimal string representation of a Color.
ToArgbHex returns an ARGB hexadecimal string representation of a Color.
ToRgbaHex returns an RGBA hexadecimal string representation of a Color.
ToInt returns an ARGB int representation of a Color.
ToUint returns an ARGB uint representation of a Color.
ToRgb converts a Color to RGB byte values that are returned as out arguments.
ToRgba converts a Color to RGBA byte values that are returned as out arguments.
ToHsl converts a Color to HSL float values that are passed as out arguments.
Examples
In XAML, colors are typically referenced using their named values, or with hexadecimal:
XAML
TextColor="Aqua" />
<Label Text="RGB"
TextColor="#00FF00" />
TextColor="#CC00FF00" />
TextColor="#0F0" />
TextColor="#C0F0" />
In C#, colors are typically referenced using their named values, or with their static
methods:
C#
The following example uses the OnPlatform markup extension to selectively set the
color of an ActivityIndicator:
XAML
IsRunning="True" />
C#
IsRunning = true
};
The .NET MAUI GraphicsView control provides access to an ICanvas object, on which
properties can be set and methods invoked to draw graphical objects. For more
information about the GraphicsView, see GraphicsView.
) Important
Draw a line
Lines can be drawn on an ICanvas using the DrawLine method, which requires four
float arguments that represent the start and end points of the line.
C#
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 6;
7 Note
There's also a DrawLine overload that takes two PointF arguments.
C#
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 4;
In this example, a red dashed diagonal line is drawn from (10,10) to (90,100):
For more information about dashed lines, see Draw dashed objects.
Draw an ellipse
Ellipses and circles can be drawn on an ICanvas using the DrawEllipse method, which
requires x , y , width , and height arguments, of type float .
C#
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 4;
C#
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 4;
7 Note
For information about drawing a dashed ellipse, see Draw dashed objects.
A filled ellipse can be drawn with the FillEllipse method, which also requires x , y , width ,
and height arguments, of type float :
C#
canvas.FillColor = Colors.Red;
In this example, a red filled ellipse with dimensions 150x50 is drawn at (10,10):
The FillColor property of the ICanvas object must be set to a Color before invoking the
FillEllipse method.
There are DrawEllipse and FillEllipse overloads that take Rect and RectF arguments.
In addition, there are also DrawCircle and FillCircle overloads.
Draw a rectangle
Rectangles and squares can be drawn on an ICanvas using the DrawRectangle method,
which requires x , y , width , and height arguments, of type float .
C#
canvas.StrokeColor = Colors.DarkBlue;
canvas.StrokeSize = 4;
In this example, a dark blue rectangle with dimensions 100x50 is drawn at (10,10):
To draw a square, make the width and height arguments to the DrawRectangle method
equal:
C#
canvas.StrokeColor = Colors.DarkBlue;
canvas.StrokeSize = 4;
In this example, a dark blue square with dimensions 100x100 is drawn at (10,10):
For information about drawing a dashed rectangle, see Draw dashed objects.
A filled rectangle can be drawn with the FillRectangle method, which also requires x , y ,
width , and height arguments, of type float :
C#
canvas.FillColor = Colors.DarkBlue;
In this example, a dark blue filled rectangle with dimensions 100x50 is drawn at (10,10):
The FillColor property of the ICanvas object must be set to a Color before invoking the
FillRectangle method.
7 Note
There are DrawRectangle and FillRectangle overloads that take Rect and RectF
arguments.
C#
canvas.StrokeColor = Colors.Green;
canvas.StrokeSize = 4;
In this example, a green rectangle with rounded corners and dimensions 100x50 is
drawn at (10,10):
For information about drawing a dashed rounded rectangle, see Draw dashed objects.
A filled rounded rectangle can be drawn with the FillRoundedRectangle method, which
also requires x , y , width , height , and cornerRadius arguments, of type float :
C#
canvas.FillColor = Colors.Green;
In this example, a green filled rectangle with rounded corners and dimensions 100x50 is
drawn at (10,10):
The FillColor property of the ICanvas object must be set to a Color before invoking the
FillRoundedRectangle method.
7 Note
closed arguments of type bool . The startAngle argument specifies the angle from the
x-axis to the starting point of the arc. The endAngle argument specifies the angle from
the x-axis to the end point of the arc. The clockwise argument specifies the direction in
which the arc is drawn, and the closed argument specifies whether the end point of the
arc will be connected to the start point.
C#
canvas.StrokeColor = Colors.Teal;
canvas.StrokeSize = 4;
In this example, a teal arc of dimensions 100x100 is drawn at (10,10). The arc is drawn in
a clockwise direction from 0 degrees to 180 degrees, and isn't closed:
For information about drawing a dashed arc, see Draw dashed objects.
A filled arc can be drawn with the FillArc method, which requires x , y , width , height ,
startAngle , and endAngle arguments of type float , and a clockwise argument of type
bool :
C#
canvas.FillColor = Colors.Teal;
In this example, a filled teal arc of dimensions 100x100 is drawn at (10,10). The arc is
drawn in a clockwise direction from 0 degrees to 180 degrees, and is closed
automatically:
The FillColor property of the ICanvas object must be set to a Color before invoking the
FillArc method.
7 Note
There are DrawArc and FillArc overloads that take Rect and RectF arguments.
Draw a path
A path is a collection of one or more contours. Each contour is a collection of connected
straight lines and curves. Contours are not connected to each other but they might
visually overlap. Sometimes a single contour can overlap itself.
Paths are used to draw curves and complex shapes and can be drawn on an ICanvas
using the DrawPath method, which requires a PathF argument.
A contour generally begins with a call to the PathF.MoveTo method, which you can
express either as a PointF value or as separate x and y coordinates. The MoveTo call
establishes a point at the beginning of the contour and an initial current point. You can
then call the following methods to continue the contour with a line or curve from the
current point to a point specified in the method, which then becomes the new current
point:
None of these methods contain all of the data necessary to describe the line or curve.
Instead, each method works with the current point established by the method call
immediately preceding it. For example, the LineTo method adds a straight line to the
contour based on the current point.
A contour ends with another call to MoveTo, which begins a new contour, or a call to
Close, which closes the contour. The Close method automatically appends a straight line
from the current point to the first point of the contour, and marks the path as closed.
The PathF class also defines other methods and properties. The following methods add
entire contours to the path:
C#
path.MoveTo(40, 10);
path.LineTo(70, 80);
path.LineTo(10, 50);
path.Close();
canvas.StrokeColor = Colors.Green;
canvas.StrokeSize = 6;
canvas.DrawPath(path);
A filled path can be drawn with the FillPath, which also requires a PathF argument:
C#
path.MoveTo(40, 10);
path.LineTo(70, 80);
path.LineTo(10, 50);
canvas.FillColor = Colors.SlateBlue;
canvas.FillPath(path);
) Important
Draw an image
Images can be drawn on an ICanvas using the DrawImage method, which requires an
IImage argument, and x , y , width , and height arguments, of type float .
The following example shows how to load an image and draw it to the canvas:
C#
using Microsoft.Maui.Graphics.Platform;
...
IImage image;
image = PlatformImage.FromStream(stream);
if (image != null)
2 Warning
) Important
Loading an image that's embedded in an assembly requires the image to have its
build action set to Embedded Resource rather than MauiImage.
Draw a string
Strings can be drawn on an ICanvas using one of the DrawString overloads. The
appearance of each string can be defined by setting the Font, FontColor, and FontSize
properties. String alignment can be specified by horizontal and vertical alignment
options that perform alignment within the string's bounding box.
7 Note
The bounding box for a string is defined by its x , y , width , and height arguments.
C#
canvas.FontColor = Colors.Blue;
canvas.FontSize = 18;
canvas.Font = Font.Default;
canvas.Font = Font.DefaultBold;
canvas.FontColor = Colors.Black;
In this example, strings with different appearance and alignment options are displayed:
7 Note
The DrawString overloads also enable truncation and line spacing to be specified.
C#
using Microsoft.Maui.Graphics.Text;
...
canvas.FontSize = 18;
canvas.FontColor = Colors.Blue;
IAttributedText attributedText =
MarkdownAttributedTextReader.Read(markdownText); // Requires the
Microsoft.Maui.Graphics.Text.Markdig package
In this example, markdown is converted to attributed text and displayed with the correct
styling:
) Important
The following example draws a filled circle, with a stroke outline, as a path:
C#
canvas.StrokeColor = Colors.Blue;
canvas.StrokeSize = 10;
canvas.FillColor = Colors.Red;
canvas.FillPath(path);
canvas.DrawPath(path);
In this example, the stroke and fill colors for a PathF object are specified. The filled circle
is drawn, then the outline stroke of the circle:
2 Warning
Calling a draw method before a fill method will result in an incorrect z-order. The
fill will be drawn over the stroke, and the stroke won't be visible.
Draw a shadow
Graphical objects drawn on an ICanvas can have a shadow applied using the SetShadow
method, which takes the following arguments:
offset , of type SizeF, specifies an offset for the shadow, which represents the
C#
canvas.FillColor = Colors.Red;
canvas.FillColor = Colors.Green;
canvas.FillColor = Colors.Blue;
In these examples, shadows whose light sources are in different positions are added to
the filled objects, with identical amounts of blur:
The following example shows how to draw a dashed square, using a regular dash:
C#
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 4;
C#
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 4;
ICanvas objects have a StrokeLineCap property, of type LineCap, that describes the start
and end of a line. The LineCap enumeration defines the following members:
Butt , which represents a line with a square end, drawn to extend to the exact
endpoint of the line. This is the default value of the StrokeLineCap property.
Round , which represents a line with a rounded end.
Square , which represents a line with a square end, drawn to extend beyond the
C#
canvas.StrokeSize = 10;
canvas.StrokeColor = Colors.Red;
canvas.StrokeLineCap = LineCap.Round;
In this example, the red line is rounded at the start and end of the line:
Miter , which represents angular vertices that produce a sharp or clipped corner.
7 Note
When the StrokeLineJoin property is set to Miter , the MiterLimit property can be
set to a float to limit the miter length of line joins in the object.
C#
path.MoveTo(10, 10);
path.LineTo(110, 50);
path.LineTo(10, 110);
canvas.StrokeSize = 20;
canvas.StrokeColor = Colors.Blue;
canvas.StrokeLineJoin = LineJoin.Round;
canvas.DrawPath(path);
In this example, the blue PathF object has rounded joins at its vertices:
Clip objects
Graphical objects that are drawn to an ICanvas can be clipped prior to drawing, with the
following methods:
ClipPath clips an object so that only the area that's within the region of a PathF
object will be visible.
ClipRectangle clips an object so that only the area that's within the region of a
rectangle will be visible. The rectangle can be specified using float arguments, or
by a Rect or RectF argument.
SubtractFromClip clips an object so that only the area that's outside the region of a
rectangle will be visible. The rectangle can be specified using float arguments, or
by a Rect or RectF argument.
The following example shows how to use the ClipPath method to clip an image:
C#
using Microsoft.Maui.Graphics.Platform;
...
IImage image;
image = PlatformImage.FromStream(stream);
if (image != null)
2 Warning
The PlatformImage type isn't supported on Windows.
In this example, the image is clipped using a PathF object that defines a circle that's
centered at (100,90) with a radius of 80. The result is that only the part of the image
within the circle is visible:
) Important
The following example shows how to use the SubtractFromClip method to clip an
image:
C#
using Microsoft.Maui.Graphics.Platform;
...
IImage image;
image = PlatformImage.FromStream(streamß);
if (image != null)
2 Warning
In this example, the area defined by the rectangle that's specified by the arguments
supplied to the SubtractFromClip method is clipped from the image. The result is that
only the parts of the image outside the rectangle are visible:
Images
Article • 03/03/2023 • 4 minutes to read
.NET Multi-platform App UI (.NET MAUI) graphics includes functionality to load, save,
resize, and downsize images. Supported image formats are dependent on the
underlying platform.
Images are represented by the IImage type, which defines the following properties:
An optional ImageFormat argument can be specified when loading and saving images.
The ImageFormat enumeration defines Png , Jpeg , Gif , Tiff , and Bmp members.
However, this argument is only used when the image format is supported by the
underlying platform.
7 Note
Load an image
Image loading functionality is provided by the PlatformImage class. Images can be
loaded from a stream by the FromStream method, or from a byte array using the
PlatformImage constructor.
C#
using Microsoft.Maui.Graphics.Platform;
...
IImage image;
image = PlatformImage.FromStream(stream);
if (image != null)
2 Warning
In this example, the image is retrieved from the assembly, loaded as a stream, and
displayed.
) Important
Loading an image that's embedded in an assembly requires the image to have its
build action set to Embedded Resource rather than MauiImage.
Resize an image
Images can be resized using the Resize method, which requires width and height
arguments, of type float , which represent the target dimensions of the image. The
Resize method also accepts two optional arguments:
A ResizeMode argument, that controls how the image will be resized to fit its
target dimensions.
A bool argument that controls whether the source image will be disposed after
performing the resize operation. This argument defaults to false , indicating that
the source image won't be disposed.
The ResizeMode enumeration defines the following members, which specify how to
resize the image to the target size:
Fit , which letterboxes the image so that it fits its target size.
Bleed , which clips the image so that it fits its target size, while preserving its aspect
ratio.
Stretch , which stretches the image so it fills the available space. This can result in
C#
using Microsoft.Maui.Graphics.Platform;
...
IImage image;
image = PlatformImage.FromStream(stream);
if (image != null)
2 Warning
In this example, the image is retrieved from the assembly and loaded as a stream. The
image is resized using the Resize method, with its arguments specifying the new size,
and that it should be stretched to fill the available space. In addition, the source image is
disposed. The resized image is then drawn at actual size at (10,10).
Downsize an image
Images can be downsized by one of the Downsize overloads. The first overload requires
a single float value that represents the maximum width or height of the image, and
downsizes the image while maintaining its aspect ratio. The second overload requires
two float arguments, that represent the maximum width and maximum height of the
image.
The Downsize overloads also accept an optional bool argument that controls whether
the source image should be disposed after performing the downsizing operation. This
argument defaults to false , indicating that the source image won't be disposed.
C#
using Microsoft.Maui.Graphics.Platform;
...
IImage image;
image = PlatformImage.FromStream(stream);
if (image != null)
2 Warning
In this example, the image is retrieved from the assembly and loaded as a stream. The
image is downsized using the Downsize method, with the argument specifying that its
largest dimension should be set to 100 pixels. In addition, the source image is disposed.
The downsized image is then drawn at actual size at (10,10).
Save an image
Images can be saved by the Save and SaveAsync methods. Each method saves the
IImage to a Stream, and enables optional ImageFormat and quality values to be
specified.
C#
using Microsoft.Maui.Graphics.Platform;
...
IImage image;
image = PlatformImage.FromStream(stream);
if (image != null)
newImage.Save(memStream);
}
2 Warning
In this example, the image is retrieved from the assembly and loaded as a stream. The
image is downsized using the Downsize method, with the argument specifying that its
largest dimension should be set to 150 pixels. In addition, the source image is disposed.
The downsized image is then saved to a stream.
Paint graphical objects
Article • 03/03/2023 • 12 minutes to read
.NET Multi-platform App UI (.NET MAUI) graphics includes the ability to paint graphical
objects with solid colors, gradients, repeating images, and patterns.
The Paint class is an abstract class that paints an object with its output. Classes that
derive from Paint describe different ways of painting an object. The following list
describes the different paint types available in .NET MAUI graphics:
SolidPaint, which paints an object with a solid color. For more information, see
Paint a solid color.
ImagePaint, which paints an object with an image. For more information, see Paint
an image.
PatternPaint, which paints an object with a pattern. For more information, see Paint
a pattern.
GradientPaint, which paints an object with a gradient. For more information, see
Paint a gradient.
Instances of these types can be painted on an ICanvas, typically by using the SetFillPaint
method to set the paint as the fill of a graphical object.
The Paint class also defines BackgroundColor, and ForegroundColor properties, of type
Color, that can be used to optionally define background and foreground colors for a
Paint object.
The SolidPaint class defines a Color property, of type Color, which represents the color
of the paint. The class also has an IsTransparent property that returns a bool that
represents whether the color has an alpha value of less than 1.
canvas.SetFillPaint(solidPaint, solidRectangle);
canvas.FillRoundedRectangle(solidRectangle, 12);
The SolidPaint object is specified as the first argument to the SetFillPaint method.
Therefore, a filled rounded rectangle is painted with a silver SolidPaint object:
C#
Color = Colors.Silver
};
canvas.SetFillPaint(solidPaint, solidRectangle);
canvas.FillRoundedRectangle(solidRectangle, 12);
Paint an image
The ImagePaint class, that's derived from the Paint class, is used to paint a graphical
object with an image.
The ImagePaint class defines an Image property, of type IImage, which represents the
image to paint. The class also has an IsTransparent property that returns false .
7 Note
Loading an image that's embedded in an assembly requires the image to have its
build action set to Embedded Resource.
The following example shows how to load an image and fill a rectangle with it:
C#
using Microsoft.Maui.Graphics.Platform;
...
IImage image;
image = PlatformImage.FromStream(stream);
if (image != null)
Image = image.Downsize(100)
};
canvas.SetFillPaint(imagePaint, RectF.Zero);
2 Warning
In this example, the image is retrieved from the assembly and loaded as a stream. The
image is resized using the Downsize method, with the argument specifying that its
largest dimension should be set to 100 pixels. For more information about downsizing
an image, see Downsize an image.
The Image property of the ImagePaint object is set to the downsized version of the
image, and the ImagePaint object is set as the paint to fill an object with. A rectangle is
then drawn that's filled with the paint:
7 Note
An ImagePaint object can also be created from an IImage object by the AsPaint
extension method.
Alternatively, the SetFillImage extension method can be used to simplify the code:
C#
if (image != null)
canvas.SetFillImage(image.Downsize(100));
Paint a pattern
The PatternPaint class, that's derived from the Paint class, is used to paint a graphical
object with a pattern.
The PatternPaint class defines a Pattern property, of type IPattern, which represents the
pattern to paint. The class also has an IsTransparent property that returns a bool that
represents whether the background or foreground color of the paint has an alpha value
of less than 1.
C#
IPattern pattern;
picture.StrokeColor = Colors.Silver;
Pattern = pattern
};
canvas.SetFillPaint(patternPaint, RectF.Zero);
In this example, the pattern is a 10x10 area that contains a diagonal line from (0,0) to
(10,10), and a diagonal line from (0,10) to (10,0). The Pattern property of the PatternPaint
object is set to the pattern, and the PatternPaint object is set as the paint to fill an object
with. A rectangle is then drawn that's filled with the paint:
7 Note
Paint a gradient
The GradientPaint class, that's derived from the Paint class, is an abstract base class that
describes a gradient, which is composed of gradient steps. A GradientPaint paints a
graphical object with multiple colors that blend into each other along an axis. Classes
that derive from GradientPaint describe different ways of interpreting gradients stops,
and .NET MAUI graphics provides the following gradient paints:
Gradient stops
Gradient stops are the building blocks of a gradient, and specify the colors in the
gradient and their location along the gradient axis. Gradient stops are specified using
PaintGradientStop objects.
Color, of type Color, which represents the color of the gradient stop.
Offset, of type float , which represents the location of the gradient stop within the
gradient vector. Valid values are in the range 0.0-1.0. The closer this value is to 0,
the closer the color is to the start of the gradient. Similarly, the closer this value is
to 1, the closer the color is to the end of the gradient.
) Important
The coordinate system used by gradients is relative to a bounding box for the
graphical object. 0 indicates 0 percent of the bounding box, and 1 indicates 100
percent of the bounding box. Therefore, (0.5,0.5) describes a point in the middle of
the bounding box, and (1,1) describes a point at the bottom right of the bounding
box.
Gradient stops can be added to a GradientPaint object with the AddOffset method.
C#
LinearGradientPaint linearGradientPaint = new LinearGradientPaint
StartColor = Colors.Yellow,
EndColor = Colors.Green,
};
linearGradientPaint.AddOffset(0.25f, Colors.Red);
linearGradientPaint.AddOffset(0.75f, Colors.Blue);
canvas.SetFillPaint(linearGradientPaint, linearRectangle);
canvas.FillRoundedRectangle(linearRectangle, 12);
The color of each point between gradient stops is interpolated as a combination of the
color specified by the two bounding gradient stops. The following diagram shows the
gradient stops from the previous example:
In this diagram, the circles mark the position of gradient stops, and the dashed line
shows the gradient axis. The first gradient stop specifies the color yellow at an offset of
0.0. The second gradient stop specifies the color red at an offset of 0.25. The points
between these two gradient stops gradually change from yellow to red as you move
from left to right along the gradient axis. The third gradient stop specifies the color blue
at an offset of 0.75. The points between the second and third gradient stops gradually
change from red to blue. The fourth gradient stop specifies the color lime green at
offset of 1.0. The points between the third and fourth gradient stops gradually change
from blue to lime green.
The StartPoint and EndPoint properties are relative to the graphical object being
painted. (0,0) represents the top-left corner of the object being painted, and (1,1)
represents the bottom-right corner of the object being painted. The following diagram
shows the gradient axis for a diagonal linear gradient brush:
In this diagram, the dashed line shows the gradient axis, which highlights the
interpolation path of the gradient from the start point to the end point.
C#
StartColor = Colors.Yellow,
EndColor = Colors.Green,
};
canvas.SetFillPaint(linearGradientPaint, linearRectangle);
canvas.FillRoundedRectangle(linearRectangle, 12);
In this example, the rounded rectangle is painted with a linear gradient that interpolates
horizontally from yellow to green:
To create a vertical linear gradient, create a LinearGradientPaint object and set its
StartColor and EndColor properties. Then, set its EndPoint to (0,1).
C#
StartColor = Colors.Yellow,
EndColor = Colors.Green,
};
canvas.SetFillPaint(linearGradientPaint, linearRectangle);
canvas.FillRoundedRectangle(linearRectangle, 12);
In this example, the rounded rectangle is painted with a linear gradient that interpolates
vertically from yellow to green:
C#
StartColor = Colors.Yellow,
EndColor = Colors.Green,
};
canvas.SetFillPaint(linearGradientPaint, linearRectangle);
canvas.FillRoundedRectangle(linearRectangle, 12);
In this example, the rounded rectangle is painted with a linear gradient that interpolates
diagonally from yellow to green:
Paint a radial gradient
The RadialGradientPaint class, that's derived from the GradientPaint class, paints a
graphical object with a radial gradient. A radial gradient blends two or more colors
across a circle. PaintGradientStop objects are used to specify the colors in the gradient
and their positions. For more information about PaintGradientStop objects, see Paint a
gradient.
Center, of type Point, which represents the center point of the circle for the radial
gradient. The class constructor initializes this property to (0.5,0.5).
Radius, of type double , which represents the radius of the circle for the radial
gradient. The class constructor initializes this property to 0.5.
To create a radial gradient, create a RadialGradientPaint object and set its StartColor and
EndColor properties. Then, set its Center and Radius properties.
C#
StartColor = Colors.Red,
EndColor = Colors.DarkBlue
};
canvas.SetFillPaint(radialGradientPaint, radialRectangle);
canvas.FillRoundedRectangle(radialRectangle, 12);
In this example, the rounded rectangle is painted with a radial gradient that interpolates
from red to dark blue. The center of the radial gradient is positioned in the center of the
rectangle:
The following example moves the center of the radial gradient to the top-left corner of
the rectangle:
C#
StartColor = Colors.Red,
EndColor = Colors.DarkBlue,
};
canvas.SetFillPaint(radialGradientPaint, radialRectangle);
canvas.FillRoundedRectangle(radialRectangle, 12);
In this example, the rounded rectangle is painted with a radial gradient that interpolates
from red to dark blue. The center of the radial gradient is positioned in the top-left of
the rectangle:
The following example moves the center of the radial gradient to the bottom-right
corner of the rectangle:
C#
StartColor = Colors.Red,
EndColor = Colors.DarkBlue,
};
canvas.SetFillPaint(radialGradientPaint, radialRectangle);
canvas.FillRoundedRectangle(radialRectangle, 12);
In this example, the rounded rectangle is painted with a radial gradient that interpolates
from red to dark blue. The center of the radial gradient is positioned in the bottom-right
of the rectangle:
Transforms
Article • 03/03/2023 • 8 minutes to read
These transforms are known as affine transforms. Affine transforms always preserve
parallel lines and never cause a coordinate or size to become infinite.
The .NET MAUI VisualElement class also supports the following transform properties:
TranslationX, TranslationY, Scale, Rotation, RotationX, and RotationY. However, there are
several differences between Microsoft.Maui.Graphics transforms and VisualElement
transforms:
Translate transform
The translate transform shifts graphical objects in the horizontal and vertical directions.
Translation can be considered unnecessary because the same result can be
accomplished by changing the coordinates of the drawing method you're using.
However, when displaying a path, all the coordinates are encapsulated in the path, and
so it's often easier to apply a translate transform to shift the entire path.
The Translate method requires x and y arguments of type float that cause
subsequently drawn graphic objects to be shifted horizontally and vertically. Negative x
values move an object to the left, while positive values move an object to the right.
Negative y values move up an object, while positive values move down an object.
A common use of the translate transform is for rendering a graphical object that has
been originally created using coordinates that are convenient for drawing. The following
example creates a PathF object for an 11-pointed star:
C#
if (i == 0)
path.MoveTo(point);
else
path.LineTo(point);
canvas.FillColor = Colors.Red;
canvas.Translate(150, 150);
canvas.FillPath(path);
The center of the star is at (0,0), and the points of the star are on a circle surrounding
that point. Each point is a combination of sine and cosine values of an angle that
increases by 5/11ths of 360 degrees. The radius of the circle is set as 100. If the PathF
object is displayed without any transforms, the center of the star will be positioned at
the upper-left corner of the ICanvas, and only a quarter of it will be visible. Therefore, a
translate transform is used to shift the star horizontally and vertically to (150,150):
Scale transform
The scale transform changes the size of a graphical object, and can also often cause
coordinates to move when a graphical object is made larger.
The Scale method requires x and y arguments of type float that let you specify
different values for horizontal and vertical scaling, otherwise known as anisotropic
scaling. The values of x and y have a significant impact on the resulting scaling:
Values between 0 and 1 decrease the width and height of the scaled object.
Values greater than 1 increase the width and height of the scaled object.
Values of 1 indicate that the object is not scaled.
C#
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 4;
canvas.FontColor = Colors.Blue;
canvas.FontSize = 18;
canvas.Scale(2, 2);
In this example, ".NET MAUI" is displayed inside a rounded rectangle stroked with a
dashed line. The same graphical objects drawn after the Scale call increase in size
proportionally:
The text and the rounded rectangle are both subject to the same scaling factors.
7 Note
Anisotropic scaling causes the stroke size to become different for lines aligned with
the horizontal and vertical axes.
Order matters when you combine Translate and Scale calls. If the Translate call comes
after the Scale call, the translation factors are scaled by the scaling factors. If the
Translate call comes before the Scale call, the translation factors aren't scaled.
Rotate transform
The rotate transform rotates a graphical object around a point. Rotation is clockwise for
increasing angles. Negative angles and angles greater than 360 degrees are allowed.
There are two Rotate overloads. The first requires a degrees argument of type float
that defines the rotation angle, and centers the rotation around the upper-left corner of
the canvas (0,0). The following example demonstrates this Rotate method:
C#
canvas.FontColor = Colors.Blue;
canvas.FontSize = 18;
canvas.Rotate(45);
C#
canvas.FontColor = Colors.Blue;
canvas.FontSize = 18;
In this example, the .NET MAUI text is rotated 45 degrees around the center of the
canvas.
Combine transforms
The simplest way to combine transforms is to begin with global transforms, followed by
local transforms. For example, translation, scaling, and rotation can be combined to
draw an analog clock. The clock can be drawn using an arbitrary coordinate system
based on a circle that's centered at (0,0) with a radius of 100. Translation and scaling
expand and center the clock on the canvas, and rotation can then be used to draw the
minute and hour marks of the clock and to rotate the hands:
C#
canvas.StrokeLineCap = LineCap.Round;
canvas.FillColor = Colors.Gray;
canvas.Translate(dirtyRect.Center.X, dirtyRect.Center.Y);
canvas.Scale(scale, scale);
canvas.Rotate(6);
// Hour hand
canvas.StrokeSize = 20;
canvas.SaveState();
canvas.DrawLine(0, 0, 0, -50);
canvas.RestoreState();
// Minute hand
canvas.StrokeSize = 10;
canvas.SaveState();
canvas.DrawLine(0, 0, 0, -70);
canvas.RestoreState();
// Second hand
canvas.StrokeSize = 2;
canvas.SaveState();
canvas.Rotate(6 * now.Second);
canvas.RestoreState();
In this example, the Translate and Scale calls apply globally to the clock, and so are
called before the Rotate method.
There are 60 marks of two different sizes that are drawn in a circle around the clock. The
FillCircle call draws that circle at (0,-90), which relative to the center of the clock
corresponds to 12:00. The Rotate call increments the rotation angle by 6 degrees after
every tick mark. The angle variable is used solely to determine if a large circle or a small
circle is drawn. Finally, the current time is obtained and rotation degrees are calculated
for the hour, minute, and second hands. Each hand is drawn in the 12:00 position so that
the rotation angle is relative to that position:
Concatenate transforms
A transform can be described in terms of a 3x3 affine transformation matrix, which
performs transformations in 2D space. The following table shows the structure of a 3x3
affine transformation matrix:
M11
M12
0.0
M21
M22
0.0
M31
M32
1.0
An affine transformation matrix has its final column equal to (0,0,1), so only members in
the first two columns need to be specified. Therefore, the 3x3 matrix is represented by
the Matrix3x2 struct, from the System.Numerics namespace, which is a collection of
three rows and two columns of float values.
The six cells in the first two columns of the transform matrix represent values that
performing scaling, shearing, and translation:
ScaleX
ShearY
0.0
ShearX
ScaleY
0.0
TranslateX
TranslateY
1.0
For example, if you change the M31 value to 100, you can use it to translate a graphical
object 100 pixels along the x-axis. If you change the M22 value to 3, you can use it to
stretch a graphical object to three times its current height. If you change both values,
you move the graphical object 100 pixels along the x-axis and stretch its height by a
factor of 3.
You can define a new transform matrix with the Matrix3x2 constructor. The advantage of
specifying transforms with a transform matrix is that composite transforms can be
applied as a single transform, which is referred to as concatenation. The Matrix3x2 struct
also defines methods that can be used to manipulate matrix values.
C#
if (i == 0)
path.MoveTo(point);
else
path.LineTo(point);
canvas.ConcatenateTransform(transform);
canvas.FillColor = Colors.Red;
canvas.FillPath(path);
In this example, the PathF object is scaled and sheared on the x-axis, and translated on
the x-axis and the y-axis.
Winding modes
Article • 03/03/2023 • 2 minutes to read
The WindingMode enumeration defines NonZero and EvenOdd members. Each member
represents a different algorithm for determining whether a point is in the fill region of
an enclosed area.
7 Note
NonZero
The NonZero winding mode draws a hypothetical ray from the point to infinity in any
direction and then examines the places where a path contour crosses the ray. The count
starts at zero and is incremented each time a contour crosses the ray from left to right
and decremented each time a contour crosses the ray from right to left. If the count of
crossings is zero, the area isn't filled. Otherwise, the area is filled.
The following example fills a five-pointed star using the NonZero winding mode:
C#
path.Close();
canvas.StrokeSize = 15;
canvas.StrokeLineJoin = LineJoin.Round;
canvas.StrokeColor = Colors.Red;
canvas.FillColor = Colors.Blue;
canvas.DrawPath(path);
In this example, the path is drawn twice. The FillPath method is used to fill the path with
blue, while the DrawPath method outlines the path with a red stroke. The FillPath
overload used omits the WindingMode argument, and instead automatically uses the
NonZero winding mode. This results in all the enclosed areas of the path being filled:
7 Note
For many paths, the NonZero winding mode often fills all the enclosed areas of a
path.
EvenOdd
The EvenOdd winding mode draws a hypothetical ray from the point to infinity in any
direction and counts the number of path contours that the ray crosses. If this number is
odd, then the area is filled. Otherwise, the area isn't filled.
The following example fills a five-pointed star using the EvenOdd winding mode:
C#
path.Close();
canvas.StrokeSize = 15;
canvas.StrokeLineJoin = LineJoin.Round;
canvas.StrokeColor = Colors.Red;
canvas.FillColor = Colors.Blue;
canvas.FillPath(path, WindingMode.EvenOdd);
canvas.DrawPath(path);
In this example, the path is drawn twice. The FillPath method is used to fill the path with
blue, while the DrawPath method outlines the path with a red stroke. The FillPath
overload used specifies that the EvenOdd winding mode is used. This mode results in the
central area of the star not being filled:
Change a .NET MAUI app icon
Article • 03/27/2023 • 9 minutes to read
Every app has a logo icon that represents it, and that icon typically appears in multiple
places. For example, on iOS the app icon appears on the Home screen and throughout
the system, such as in Settings, notifications, and search results, and in the App Store.
On Android, the app icon appears as a launcher icon and throughout the system, such
as on the action bar, notifications, and in the Google Play Store. On Windows, the app
icon appears in the app list in the start menu, the taskbar, the app's tile, and in the
Microsoft Store.
In a .NET Multi-platform App UI (.NET MAUI) app project, an app icon can be specified
in a single location in your app project. At build time, this icon can be automatically
resized to the correct resolution for the target platform and device, and added to your
app package. This avoids having to manually duplicate and name the app icon on a per
platform basis. By default, bitmap (non-vector) image formats aren't automatically
resized by .NET MAUI.
A .NET MAUI app icon can use any of the standard platform image formats, including
Scalable Vector Graphics (SVG) files.
) Important
.NET MAUI converts SVG files to Portable Network Graphic (PNG) files. Therefore,
when adding an SVG file to your .NET MAUI app project, it should be referenced
from XAML or C# with a .png extension. The only reference to the SVG file should
be in your project file.
To comply with Android resource naming rules, app icon filenames must be lowercase,
start and end with a letter character, and contain only alphanumeric characters or
underscores. For more information, see App resources overview on
developer.android.com.
The icon defined by your app can be composed of a single image, by specifying the file
as the Include attribute:
XML
<ItemGroup>
</ItemGroup>
) Important
Only the first <MauiIcon> item defined in the project file is processed by .NET MAUI.
If you want to use a different file as the icon, first delete the existing icon from your
project, and then add the new icon. Next, in the Solution Explorer pane, select the
file, and then in the Properties pane, set the Build Action to MauiIcon. Instead of
adding a new icon file to the project, consider replacing the existing icon file
instead.
After changing the icon file, you may need to clean the project in Visual Studio. To clean
the project, right-click on the project file in the Solution Explorer pane, and select
Clean. You also may need to uninstall the app from the target platform you're testing
with.
U Caution
If you don't clean the project and uninstall the app from the target platform, you
may not see your new icon.
After changing the icon, review the Platform specific configuration information.
Composed icon
Alternatively, the app icon can be composed of two images, one image representing the
background and another representing the foreground. Since icons are transformed into
PNG files, the composed app icon will be first layered with the background image,
typically an image of a pattern or solid color, followed by the foreground image. In this
case, the Include attribute represents the icon background image, and the Foreground
attribute represents the foreground image:
XML
<ItemGroup>
<MauiIcon Include="Resources\AppIcon\appicon.svg"
ForegroundFile="Resources\AppIcon\appiconfg.svg" />
</ItemGroup>
) Important
The background image ( Include attribute) must be specified for the <MauiIcon>
item. The foreground image ( ForegroundFile attribute) is optional.
The base size of your icon represents baseline density of the image, and is effectively
the 1.0 scale factor that all other sizes are derived. If you don't specify the base size for a
bitmap-based app icon, such as a PNG file, the image isn't resized. If you don't specify
the base size for a vector-based app icon, such as an SVG file, the dimensions specified
in the image are used as the base size. To stop a vector image from being resized, set
the Resize attribute to false .
A: The image is added as the .NET MAUI icon and has dimensions of 210x260, and
the base size is set to 424x520.
B: .NET MAUI automatically scales the image to match the base size of 424x520.
C: As different target platforms require different sizes of the image, .NET MAUI
automatically scales the image from the base size to different sizes.
Tip
Use an SVG image as your icon. SVG images can upscale to larger sizes and still
look crisp and clean. Bitmap-based images, such a PNG or JPG image, look blurry
when upscaled.
The base size is specified with the BaseSize="W,H" attribute, where W is the width of the
icon and H is the height of the icon. The value specified as the base size must be
divisible by 8. The following example sets the base size:
XML
<ItemGroup>
</ItemGroup>
And the following example stops the automatic resizing of a vector-based image:
XML
<ItemGroup>
</ItemGroup>
XML
<ItemGroup>
</ItemGroup>
Color values can be specified in hexadecimal, using the format: #RRGGBB or #AARRGGBB .
The value of RR represents the red channel, GG the green channel, BB the blue channel,
and AA the alpha channel. Instead of a hexadecimal value, you may use a named .NET
MAUI color, such as Red or PaleVioletRed .
U Caution
If you don't define a background color for your app icon the background is
considered to be tranparent on iOS and Mac Catalyst. This will cause an error
during App Store Connect verification and you won't be able to upload your app.
XML
<ItemGroup>
<MauiIcon Include="Resources\AppIcon\appicon.png"
ForegroundFile="Resources\AppIcon\appiconfg.svg" TintColor="Yellow" />
</ItemGroup>
Color values can be specified in hexadecimal, using the format: #RRGGBB or #AARRGGBB .
The value of RR represents the red channel, GG the green channel, BB the blue channel,
and AA the alpha channel. Instead of a hexadecimal value, you may use a named .NET
MAUI color, such as Red or PaleVioletRed .
XML
<ItemGroup>
Include="Resources\AppIcon\backicon.png"
ForegroundFile="Resources\AppIcon\appiconfg.svg" TintColor="#40FF00FF" />
<MauiIcon Include="Resources\AppIcon\appicon.png"
ForegroundFile="Resources\AppIcon\appiconfg.svg" TintColor="Yellow" />
</ItemGroup>
You can set the target platform by changing the value compared in the condition to one
of the following values:
'ios'
'maccatalyst'
'android'
'windows'
'android'" .
Platform-specific configuration
While the project file declares which resources the app icon is composed from, you're
still required to update the individual platform configurations with reference to those
app icons. The following information describes these platform-specific settings.
Windows
The app icon defined by project is used to generate your app's icon assets.
Add images to a .NET MAUI app project
Article • 08/03/2022 • 2 minutes to read
Images are a crucial part of app navigation, usability, and branding. However, each
platform has differing image requirements that typically involve creating multiple
versions of each image at different resolutions. Therefore, a single image typically has to
be duplicated multiple times per platform, at different resolutions, with the resulting
images having to use different filename and folder conventions on each platform.
In a .NET Multi-platform App UI (.NET MAUI) app project, images can be specified in a
single location in your app project, and at build time they can be automatically resized
to the correct resolution for the target platform and device, and added to your app
package. This avoids having to manually duplicate and name images on a per platform
basis. By default, bitmap (non-vector) image formats, including animated GIFs, are not
automatically resized by .NET MAUI.
.NET MAUI images can use any of the standard platform image formats, including
Scalable Vector Graphics (SVG) files.
) Important
.NET MAUI converts SVG files to PNG files. Therefore, when adding an SVG file to
your .NET MAUI app project, it should be referenced from XAML or C# with a .png
extension. The only reference to the SVG file should be in your project file.
An image can be added to your app project by dragging it into the Resources\Images
folder of the project, where its build action will automatically be set to MauiImage. This
creates a corresponding entry in your project file:
XML
<ItemGroup>
</ItemGroup>
7 Note
Images can also be added to other folders of your app project. However, in this
scenario their build action must be manually set to MauiImage in the Properties
window.
To comply with Android resource naming rules, image filenames must be lowercase,
start and end with a letter character, and contain only alphanumeric characters or
underscores. For more information, see App resources overview on
developer.android.com.
The base size of the image can be specified by setting the BaseSize attribute to values
that are divisible by 8:
XML
The value of the BaseSize attribute represents the baseline density of the image, and is
effectively the 1.0 scale factor for the image (the size you would typically use in your
code to specify the image size) from which all other density sizes are derived. This value
will be used to ensure that images are correctly resized to different display densities. If
you don't specify a BaseSize for a bitmap image, the image isn't resized. If you don't
specify a BaseSize value for a vector image, the dimensions specified in the SVG are
assumed to be the base size. To stop vector images being resized, set the Resize
attribute to false :
XML
To add a tint to your images, which is useful when you have icons or simple images
you'd like to render in a different color to the source, set the TintColor attribute:
XML
XML
Color values can be specified in hexadecimal, or as a .NET MAUI color. For example,
Color="Red" is valid.
At build time, images can be resized to the correct resolutions for the target platform
and device. The resulting images are then added to your app package.
Add a splash screen to a .NET MAUI app
project
Article • 03/08/2023 • 3 minutes to read
On Android and iOS, .NET Multi-platform App UI (.NET MAUI) apps can display a splash
screen while their initialization process completes. The splash screen is displayed
immediately when an app is launched, providing immediate feedback to users while app
resources are initialized:
Once the app is ready for interaction, its splash screen is dismissed.
7 Note
On Android 12+ (API 31+), the splash screen shows an icon that's centred on
screen. For more information about splash screens on Android 12+, see Splash
screens on developer.android.com.
In a .NET MAUI app project, a splash screen can be specified in a single location in your
app project, and at build time it can be automatically resized to the correct resolution
for the target platform and device, and added to your app package. This avoids having
to manually duplicate and name the splash screen on a per platform basis. By default,
bitmap (non-vector) image formats are not automatically resized by .NET MAUI.
A .NET MAUI splash screen can use any of the standard platform image formats,
including Scalable Vector Graphics (SVG) files.
) Important
.NET MAUI converts SVG files to PNG files. Therefore, when adding an SVG file to
your .NET MAUI app project, it should be referenced from XAML or C# with a .png
extension. The only reference to the SVG file should be in your project file.
A splash screen can be added to your app project by dragging an image into the
Resources\Splash folder of the project, where its build action will automatically be set to
MauiSplashScreen. This creates a corresponding entry in your project file:
XML
<ItemGroup>
</ItemGroup>
7 Note
A splash screen can also be added to other folders of your app project. However, in
this scenario its build action must be manually set to MauiSplashScreen in the
Properties window.
To comply with Android resource naming rules, splash screen files names must be
lowercase, start and end with a letter character, and contain only alphanumeric
characters or underscores. For more information, see App resources overview on
developer.android.com.
The base size of the splash screen can be specified by setting the BaseSize attribute to
values that are divisible by 8:
XML
<MauiSplashScreen Include="Resources\Splash\splashscreen.svg"
BaseSize="128,128" />
The value of the BaseSize attribute represents the baseline density of the splash screen,
and is effectively the 1.0 scale factor for the splash screen from which all other density
sizes are derived. This value will be used to ensure that splash screens are correctly
resized to different display densities. If you don't specify a BaseSize for a bitmap-based
splash screen, the image isn't resized. If you don't specify a BaseSize value for a vector-
based splash screen, the dimensions specified in the SVG are assumed to be the base
size. To stop vector images being resized, set the Resize attribute to false :
XML
To add a tint to your splash screen, which is useful when you have a simple image you'd
like to render in a different color to the source, set the TintColor attribute:
XML
<MauiSplashScreen Include="Resources\Splash\splashscreen.svg"
TintColor="#66B3FF" />
XML
<MauiSplashScreen Include="Resources\Splash\splashscreen.svg"
Color="#512BD4" />
Color values can be specified in hexadecimal, or as a .NET MAUI color. For example,
Color="Red" is valid.
At build time, the splash screen can be resized to the correct resolution for the target
platform and device. The resulting splash screen is then added to your app package.
Android
C#
using Android.App;
using Android.Content.PM;
namespace MyMauiApp
A context menu, often known as a right-click menu, offers contextual commands that
are specific to the control being clicked on. In .NET Multi-platform App UI (.NET MAUI), a
context menu can be added to any control that derives from Element, on Mac Catalyst
and Windows. This includes all pages, layouts, and views.
A context menu is defined with a MenuFlyout , which can consist of the following
children:
MenuItem defines multiple properties that enable the appearance and behavior of a
menu item to be specified. The appearance of a menu item, or sub-item, can be defined
by setting the Text , and IconImageSource properties. The response to a menu item, or
sub-item, click can be defined by setting the Clicked , Command , and CommandParameter
properties.
2 Warning
XAML
<WebView x:Name="webView"
Source="https://learn.microsoft.com/dotnet/maui"
MinimumHeightRequest="400">
<FlyoutBase.ContextFlyout>
<MenuFlyout>
Clicked="OnWebViewGoToRepoClicked"
CommandParameter="docs" />
Clicked="OnWebViewGoToRepoClicked"
CommandParameter="eng" />
</MenuFlyout>
</FlyoutBase.ContextFlyout>
</WebView>
C#
2 Warning
It's not currently possible to add items to, or remove items from, the MenuFlyout at
runtime.
Create sub-menu items
Sub-menu items can be added to a context menu by adding one or more
MenuFlyoutSubItem objects to the MenuFlyout :
XAML
<Label x:Name="label"
<FlyoutBase.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Black"
Clicked="OnLabelClicked"
CommandParameter="Black" />
<MenuFlyoutSubItem Text="Light">
<MenuFlyoutItem Text="Blue"
Clicked="OnLabelClicked"
CommandParameter="LightBlue" />
<MenuFlyoutItem Text="Coral"
Clicked="OnLabelClicked"
CommandParameter="LightCoral" />
<MenuFlyoutItem Text="Cyan"
Clicked="OnLabelClicked"
CommandParameter="LightCyan" />
</MenuFlyoutSubItem>
<MenuFlyoutSubItem Text="Dark">
<MenuFlyoutItem Text="Blue"
Clicked="OnLabelClicked"
CommandParameter="DarkBlue" />
<MenuFlyoutItem Text="Cyan"
Clicked="OnLabelClicked"
CommandParameter="DarkCyan" />
<MenuFlyoutItem Text="Magenta"
Clicked="OnLabelClicked"
CommandParameter="DarkMagenta" />
</MenuFlyoutSubItem>
</MenuFlyout>
</FlyoutBase.ContextFlyout>
</Label>
In this example, the context menu defines a menu item and two sub-menus that each
contain three menu items:
Display icons on menu items
MenuFlyoutItem and MenuFlyoutSubItem inherit the IconImageSource property from
MenuItem , which enables a small icon to be displayed next to the text for a context menu
item. This icon can either be an image, or a font icon.
2 Warning
Mac Catalyst does not support displaying icons on context menu items.
The following example shows a context menu, where the icons for menu items are
defined using font icons:
XAML
WidthRequest="80">
<FlyoutBase.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Pause"
Clicked="OnPauseClicked">
<MenuFlyoutItem.IconImageSource>
<FontImageSource Glyph="⏸"
FontFamily="Arial" />
</MenuFlyoutItem.IconImageSource>
</MenuFlyoutItem>
<MenuFlyoutItem Text="Stop"
Clicked="OnStopClicked">
<MenuFlyoutItem.IconImageSource>
<FontImageSource Glyph="⏹"
FontFamily="Arial" />
</MenuFlyoutItem.IconImageSource>
</MenuFlyoutItem>
</MenuFlyout>
</FlyoutBase.ContextFlyout>
</Button>
In this example, the context menu defines two menu items that display an icon and text
on Windows:
For more information about displaying font icons, see Display font icons. For
information about adding images to .NET MAUI projects, see Add images to a .NET
MAUI app project.
Display a menu bar in a .NET MAUI
desktop app
Article • 12/23/2022 • 3 minutes to read
A .NET Multi-platform App UI (.NET MAUI) menu bar is a container that presents a set of
menus in a horizontal row, at the top of an app on Mac Catalyst and Windows.
Each top-level menu in the menu bar, known as a menu bar item, is represented by a
MenuBarItem object. MenuBarItem defines the following properties:
IsEnabled , of type boolean , specifies whether the menu is enabled. The default
value of this property is true .
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
menu item to be specified. The appearance of a menu item, or sub-item, can be defined
by setting the Text , and IconImageSource properties. The response to a menu item, or
sub-item, click can be defined by setting the Clicked , Command , and CommandParameter
properties.
bar, containing menu items, when they are added to any ContentPage that's hosted in a
NavigationPage or a Shell app.
The following example shows a ContentPage that defines menu bar items:
XAML
<ContentPage ...>
<ContentPage.MenuBarItems>
<MenuBarItem Text="File">
<MenuFlyoutItem Text="Exit"
</MenuBarItem>
<MenuBarItem Text="Locations">
Command="{Binding ChangeLocationCommand}"
CommandParameter="Redmond" />
Command="{Binding ChangeLocationCommand}"
CommandParameter="London" />
Command="{Binding ChangeLocationCommand}"
CommandParameter="Berlin"/>
</MenuFlyoutSubItem>
<MenuFlyoutSeparator />
<MenuBarItem Text="View">
<MenuFlyoutItem Text="Refresh"
</MenuBarItem>
</ContentPage.MenuBarItems>
</ContentPage>
This example defines three top-level menus. Each top-level menu has menu items, and
the second top-level menu has a sub-menu and a separator:
In this example, each MenuFlyoutItem defines a menu item that executes an ICommand
when selected.
2 Warning
The following example shows a menu bar item, where the icons for menu items are
defined using font icons:
XAML
<ContentPage.MenuBarItems>
<MenuBarItem Text="Media">
<MenuFlyoutItem Text="Play">
<MenuFlyoutItem.IconImageSource>
<FontImageSource Glyph="▶"
FontFamily="Arial" />
</MenuFlyoutItem.IconImageSource>
</MenuFlyoutItem>
<MenuFlyoutItem Text="Pause"
Clicked="OnPauseClicked">
<MenuFlyoutItem.IconImageSource>
<FontImageSource Glyph="⏸"
FontFamily="Arial" />
</MenuFlyoutItem.IconImageSource>
</MenuFlyoutItem>
<MenuFlyoutItem Text="Stop"
Clicked="OnStopClicked">
<MenuFlyoutItem.IconImageSource>
<FontImageSource Glyph="⏹"
FontFamily="Arial" />
</MenuFlyoutItem.IconImageSource>
</MenuFlyoutItem>
</MenuBarItem>
</ContentPage.MenuBarItems>
In this example, the menu bar item defines three menu items that display an icon and
text on Windows.
For more information about displaying font icons, see Display font icons. For
information about adding images to .NET MAUI projects, see Add images to a .NET
MAUI app project.
Additional menu items, beyond the 50 limit, can be added to a menu bar by adding the
following code to your AppDelegate class:
C#
[Export("MenuItem50: ")]
uICommand.SendClicked();
Shadow
Article • 02/09/2023 • 2 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Shadow class paints a shadow around a
layout or view. The VisualElement class has a Shadow bindable property, of type Shadow ,
that enables a shadow to be added to any layout or view.
Radius , of type float , defines the radius of the blur used to generate the shadow.
this property is 1.
Brush , of type Brush, represents the brush used to colorize the shadow.
OffSet , of type Point , specifies the offset for the shadow, which represents the
These properties are backed by BindableProperty objects, which means that they can be
targets of data bindings, and styled.
) Important
Create a Shadow
To add a shadow to a control, set the control's Shadow property to a Shadow object
whose properties define its appearance.
XAML
<Image Source="dotnet_bot.png"
WidthRequest="250"
HeightRequest="310">
<Image.Shadow>
<Shadow Brush="Black"
Offset="20,20"
Radius="40"
Opacity="0.8" />
</Image.Shadow>
</Image>
In this example, a black shadow is painted around the outline of the image, with its
offset specifying that it appears at the right and bottom of the image:
Shadows can also be added to clipped objects, as shown in the following example:
XAML
<Image Source=https://aka.ms/campus.jpg
Aspect="AspectFill"
HeightRequest="220"
WidthRequest="220"
HorizontalOptions="Center">
<Image.Clip>
<EllipseGeometry Center="220,250"
RadiusX="220"
RadiusY="220" />
</Image.Clip>
<Image.Shadow>
<Shadow Brush="Black"
Offset="10,10"
Opacity="0.8" />
</Image.Shadow>
</Image>
In this example, a black shadow is painted around the outline of the EllipseGeometry
that clips the image:
For more information about clipping an element, see Clip with a Geometry.
Style apps using XAML
Article • 04/03/2023 • 13 minutes to read
.NET Multi-platform App UI (.NET MAUI) apps often contain multiple controls that have
an identical appearance. For example, an app may have multiple Label instances that
have the same font options and layout options:
XAML
HorizontalOptions="Center"
VerticalOptions="Center"
FontSize="18" />
<Label Text="are not"
HorizontalOptions="Center"
VerticalOptions="Center"
FontSize="18" />
<Label Text="using styles"
HorizontalOptions="Center"
VerticalOptions="Center"
FontSize="18" />
In this example, each Label object has identical property values for controlling the
appearance of the text displayed by the Label. However, setting the appearance of each
individual control can be repetitive and error prone. Instead, a style can be created that
defines the appearance, and then applied to the required controls.
Introduction to styles
An app can be styled by using the Style class to group a collection of property values
into one object that can then be applied to multiple visual elements. This helps to
reduce repetitive markup, and allows an apps appearance to be more easily changed.
Although styles are designed primarily for XAML-based apps, they can also be created
in C#:
Each Style object contains a collection of one or more Setter objects, with each Setter
having a Property and a Value . The Property is the name of the bindable property of
the element the style is applied to, and the Value is the value that is applied to the
property.
When creating a Style, the TargetType property is always required. The following
example shows an explicit style:
XAML
</Style>
To apply a Style, the target object must be a VisualElement that matches the TargetType
property value of the Style:
XAML
Styles lower in the view hierarchy take precedence over those defined higher up. For
example, setting a Style that sets Label.TextColor to Red at the app-level will be
overridden by a page-level style that sets Label.TextColor to Green . Similarly, a page-
level style will be overridden by a control-level style. In addition, if Label.TextColor is
set directly on a control property, this takes precedence over any styles.
Styles do not respond to property changes, and remain unchanged for the duration of
an app. However, apps can respond to style changes dynamically at runtime by using
dynamic resources. For more information, see Dynamic styles.
Explicit styles
To create a Style at the page-level, a ResourceDictionary must be added to the page and
then one or more Style declarations can be included in the ResourceDictionary. A Style is
made explicit by giving its declaration an x:Key attribute, which gives it a descriptive key
in the ResourceDictionary. Explicit styles must then be applied to specific visual elements
by setting their Style properties.
The following example shows explicit styles in a page's ResourceDictionary, and applied
to the page's Label objects:
XAML
<ContentPage ...>
<ContentPage.Resources>
<Style x:Key="labelRedStyle"
TargetType="Label">
</Style>
<Style x:Key="labelGreenStyle"
TargetType="Label">
</Style>
<Style x:Key="labelBlueStyle"
TargetType="Label">
</Style>
</ContentPage.Resources>
<StackLayout>
Style="{StaticResource labelBlueStyle}"
TextColor="Teal" />
</StackLayout>
</ContentPage>
In this example, the ResourceDictionary defines three styles that are explicitly set on the
page's Label objects. Each Style is used to display text in a different color, while also
setting the font size, and horizontal and vertical layout options. Each Style is applied to a
different Label by setting its Style properties using the StaticResource markup extension.
In addition, while the final Label has a Style set on it, it also overrides the TextColor
property to a different Color value.
Implicit styles
To create a Style at the page-level, a ResourceDictionary must be added to the page and
then one or more Style declarations can be included in the ResourceDictionary. A Style is
made implicit by not specifying an x:Key attribute. The style will then be applied to in
scope visual elements that match the TargetType exactly, but not to elements that are
derived from the TargetType value.
The following code example shows an implicit style in a page's ResourceDictionary, and
applied to the page's Entry objects:
XAML
<ContentPage ...>
<ContentPage.Resources>
<Style TargetType="Entry">
</Style>
</ContentPage.Resources>
<StackLayout>
BackgroundColor="Lime"
TextColor="Red" />
</StackLayout>
</ContentPage>
In this example, the ResourceDictionary defines a single implicit style that are implicitly
set on the page's Entry objects. The Style is used to display blue text on a yellow
background, while also setting other appearance options. The Style is added to the
page's ResourceDictionary without specifying an x:Key attribute. Therefore, the Style is
applied to all the Entry objects implicitly as they match the TargetType property of the
Style exactly. However, the Style is not applied to the CustomEntry object, which is a
subclassed Entry. In addition, the fourth Entry overrides the BackgroundColor and
TextColor properties of the style to different Color values.
The following example shows an implicit style that sets the background color of Button
instances to red:
XAML
<Style TargetType="Button"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="Red" />
</Style>
Placing this style in a page-level ResourceDictionary will result in it being applied to all
Button objects on the page, and also to any controls that derive from Button. However,
if the ApplyToDerivedTypes property remained unset, the style would only be applied to
Button objects.
Global styles
Styles can be defined globally by adding them to the app's resource dictionary. These
styles can then be consumed throughout an app, and help to avoid style duplication
across pages and controls.
The following example shows a Style defined at the app-level:
XAML
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Styles"
x:Class="Styles.App">
<Application.Resources>
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="VerticalOptions"
Value="CenterAndExpand" />
<Setter Property="BorderColor"
Value="Lime" />
<Setter Property="CornerRadius"
Value="5" />
<Setter Property="BorderWidth"
Value="5" />
<Setter Property="WidthRequest"
Value="200" />
<Setter Property="TextColor"
Value="Teal" />
</Style>
</Application.Resources>
</Application>
7 Note
The following example shows a page consuming the buttonStyle on the page's Button
objects:
XAML
<ContentPage ...>
<StackLayout>
</StackLayout>
</ContentPage>
Style inheritance
Styles can inherit from other styles to reduce duplication and enable reuse. This is
achieved by setting the Style.BasedOn property to an existing Style. In XAML, this can
be achieved by setting the BasedOn property to a StaticResource markup extension that
references a previously created Style.
Styles that inherit from a base style can include Setter instances for new properties, or
use them to override setters from the base style. In addition, styles that inherit from a
base style must target the same type, or a type that derives from the type targeted by
the base style. For example, if a base style targets View objects, styles that are based on
the base style can target View objects or types that derive from the View class, such as
Label and Button objects.
A style can only inherit from styles at the same level, or above, in the view hierarchy. This
means that:
XAML
<ContentPage ...>
<ContentPage.Resources>
<Style x:Key="baseStyle"
TargetType="View">
</Style>
</ContentPage.Resources>
<StackLayout>
<StackLayout.Resources>
<Style x:Key="labelStyle"
TargetType="Label"
BasedOn="{StaticResource baseStyle}">
<Setter Property="FontSize" Value="18" />
</Style>
<Style x:Key="buttonStyle"
TargetType="Button"
BasedOn="{StaticResource baseStyle}">
<Setter Property="BorderColor" Value="Lime" />
</Style>
</StackLayout.Resources>
</StackLayout>
</ContentPage>
In this example, the baseStyle targets View objects, and sets the HorizontalOptions and
VerticalOptions properties. The baseStyle is not set directly on any controls. Instead,
labelStyle and buttonStyle inherit from it, setting additional bindable property values.
The labelStyle and buttonStyle objects are then set on a Label and Button.
) Important
An implicit style can be derived from an explicit style, but an explicit style can't be
derived from an implicit style.
Dynamic styles
Styles do not respond to property changes, and remain unchanged for the duration of
an app. For example, after assigning a Style to a visual element, if one of the Setter
objects is modified, removed, or a new Setter added, the changes won't be applied to
the visual element. However, apps can respond to style changes dynamically at runtime
by using dynamic resources.
<ContentPage ...>
<ContentPage.Resources>
<Style x:Key="baseStyle"
TargetType="View">
</Style>
<Style x:Key="blueSearchBarStyle"
TargetType="SearchBar"
BasedOn="{StaticResource baseStyle}">
</Style>
<Style x:Key="greenSearchBarStyle"
TargetType="SearchBar">
</Style>
</ContentPage.Resources>
<StackLayout>
</StackLayout>
</ContentPage>
In this example, the SearchBar object use the DynamicResource markup extension to set
a Style named blueSearchBarStyle . The SearchBar can then have its Style definition
updated in code:
C#
Resources["blueSearchBarStyle"] = Resources["greenSearchBarStyle"];
In this example, the blueSearchBarStyle definition is updated to use the values from the
greenSearchBarStyle definition. When this code is executed, the SearchBar will be
updated to use the Setter objects defined in greenSearchBarStyle .
XAML
<ContentPage ...>
<ContentPage.Resources>
<Style x:Key="baseStyle"
TargetType="View">
</Style>
<Style x:Key="blueSearchBarStyle"
TargetType="SearchBar"
BasedOn="{StaticResource baseStyle}">
</Style>
<Style x:Key="greenSearchBarStyle"
TargetType="SearchBar">
</Style>
<Style x:Key="tealSearchBarStyle"
TargetType="SearchBar"
BaseResourceKey="blueSearchBarStyle">
</Style>
</ContentPage.Resources>
<StackLayout>
</StackLayout>
</ContentPage>
In this example, the SearchBar object uses the StaticResource markup extension to
reference a Style named tealSearchBarStyle . This Style sets some additional properties
and uses the BaseResourceKey property to reference blueSearchBarStyle . The
DynamicResource markup extension is not required because tealSearchBarStyle will
not change, except for the Style it derives from. Therefore, tealSearchBarStyle
maintains a link to blueSearchBarStyle and is updated when the base style changes.
C#
Resources["blueSearchBarStyle"] = Resources["greenSearchBarStyle"];
In this example, the blueSearchBarStyle definition is updated to use the values from the
greenSearchBarStyle definition. When this code is executed, the SearchBar will be
A style class can be created by setting the Class property on a Style to a string that
represents the class name. The advantage this offers, over defining an explicit style using
the x:Key attribute, is that multiple style classes can be applied to a VisualElement.
) Important
Multiple styles can share the same class name, provided they target different types.
This enables multiple style classes, that are identically named, to target different
types.
The following example shows three BoxView style classes, and a VisualElement style
class:
XAML
<ContentPage ...>
<ContentPage.Resources>
<Style TargetType="BoxView"
Class="Separator">
<Setter Property="BackgroundColor"
Value="#CCCCCC" />
<Setter Property="HeightRequest"
Value="1" />
</Style>
<Style TargetType="BoxView"
Class="Rounded">
<Setter Property="BackgroundColor"
Value="#1FAECE" />
<Setter Property="HorizontalOptions"
Value="Start" />
<Setter Property="CornerRadius"
Value="10" />
</Style>
<Style TargetType="BoxView"
Class="Circle">
<Setter Property="BackgroundColor"
Value="#1FAECE" />
<Setter Property="WidthRequest"
Value="100" />
<Setter Property="HeightRequest"
Value="100" />
<Setter Property="HorizontalOptions"
Value="Start" />
<Setter Property="CornerRadius"
Value="50" />
</Style>
<Style TargetType="VisualElement"
Class="Rotated"
ApplyToDerivedTypes="true">
<Setter Property="Rotation"
Value="45" />
</Style>
</ContentPage.Resources>
</ContentPage>
In this example, the Separator , Rounded , and Circle style classes each set BoxView
properties to specific values. The Rotated style class has a TargetType of VisualElement,
which means it can only be applied to VisualElement instances. However, its
ApplyToDerivedTypes property is set to true , which ensures that it can be applied to any
controls that derive from VisualElement, such as BoxView. For more information about
applying a style to a derived type, see Apply a style to derived types.
Style classes can be consumed by setting the StyleClass property of the control, which
is of type IList<string> , to a list of style class names. The style classes will be applied,
provided that the type of the control matches the TargetType of the style classes.
The following example shows three BoxView instances, each set to different style classes:
XAML
<ContentPage ...>
<ContentPage.Resources>
...
</ContentPage.Resources>
<StackLayout>
<BoxView WidthRequest="100"
HeightRequest="100"
HorizontalOptions="Center"
<BoxView HorizontalOptions="Center"
StyleClass="Circle" />
</StackLayout>
</ContentPage>
In this example, the first BoxView is styled to be a line separator, while the third BoxView
is circular. The second BoxView has two style classes applied to it, which give it rounded
corners and rotate it 45 degrees:
) Important
Multiple style classes can be applied to a control because the StyleClass property
is of type IList<string> . When this occurs, style classes are applied in ascending
list order. Therefore, when multiple style classes set identical properties, the
property in the style class that's in the highest list position will take precedence.
Style apps using Cascading Style Sheets
Article • 03/03/2023 • 13 minutes to read
.NET Multi-platform App UI (.NET MAUI) apps can be styled using Cascading Style
Sheets (CSS). A style sheet consists of a list of rules, with each rule consisting of one or
more selectors and a declaration block. A declaration block consists of a list of
declarations in braces, with each declaration consisting of a property, a colon, and a
value. When there are multiple declarations in a block, a semi-colon is inserted as a
separator.
css
navigationpage {
-maui-bar-background-color: lightgray;
^contentpage {
background-color: lightgray;
#listView {
background-color: lightgray;
stacklayout {
margin: 20;
-maui-spacing: 6;
grid {
row-gap: 6;
column-gap: 6;
.mainPageTitle {
font-style: bold;
font-size: 14;
.mainPageSubtitle {
margin-top: 15;
.detailPageTitle {
font-style: bold;
font-size: 14;
text-align: center;
.detailPageSubtitle {
text-align: center;
font-style: italic;
listview image {
height: 60;
width: 60;
stacklayout>image {
height: 200;
width: 200;
In .NET MAUI, CSS style sheets are parsed and evaluated at runtime, rather than compile
time, and style sheets are re-parsed on use.
) Important
It's not possible to fully style a .NET MAUI app using CSS. However, XAML styles
can be used to supplement CSS. For more information about XAML styles, see Style
apps using XAML.
1. Add an empty CSS file to your .NET MAUI app project. The CSS file can be placed
in any folder, with the Resources folder being the recommended location.
2. Set the build action of the CSS file to MauiCss.
7 Note
It's not possible to change a style sheet at runtime and have the new style sheet
applied.
XAML
<Application ...>
<Application.Resources>
</Application.Resources>
</Application>
The StyleSheet.Source property specifies the style sheet as a URI relative to the location
of the enclosing XAML file, or relative to the project root if the URI starts with a / .
2 Warning
The CSS file will fail to load if its build action is not set to MauiCss.
Alternatively, a style sheet can be loaded and parsed with the StyleSheet class, before
being added to a ResourceDictionary, by inlining it in a CDATA section:
XAML
<ContentPage ...>
<ContentPage.Resources>
<StyleSheet>
<![CDATA[
^contentpage {
background-color: lightgray;
]]>
</StyleSheet>
</ContentPage.Resources>
...
</ContentPage>
C#
using Microsoft.Maui.Controls.StyleSheets;
public MyPage()
InitializeComponent();
this.Resources.Add(StyleSheet.FromReader(reader));
The argument to the StyleSheet.FromReader method is the TextReader that has read the
style sheet.
CSS uses properties to style a selected element. Each property has a set of possible
values, and some properties can affect any type of element, while others apply to
groups of elements. For more information about supported properties, see Property
reference.
Child stylesheets always override parent stylesheets if they set the same properties.
Therefore, the following precedence rules are followed when applying styles that set the
same properties:
A style defined in the app resources will be overwritten by a style defined in the
page resources, if they set the same properties.
A style defined in page resources will be overwritten by a style defined in the
control resources, if they set the same properties.
A style defined in the app resources will be overwritten by a style defined in the
control resources, if they set the same properties.
7 Note
css
stacklayout {
margin: 20;
This selector identifies any StackLayout elements on pages that consume the style sheet,
and sets their margins to a uniform thickness of 20.
7 Note
The element selector does not identify subclasses of the specified type.
css
^contentpage {
background-color: lightgray;
This selector identifies any ContentPage elements that consume the style sheet, and sets
their background color to lightgray .
7 Note
The ^base selector is specific to .NET MAUI, and isn't part of the CSS specification.
css
#listView {
background-color: lightgray;
This selector identifies the element whose StyleId property is set to listView .
However, if the StyleId property is not set, the selector will fall back to using the
x:Name of the element. Therefore, in the following example, the #listView selector will
identify the ListView whose x:Name attribute is set to listView , and will set it's
background color to lightgray .
XAML
<ContentPage ...>
<ContentPage.Resources>
</ContentPage.Resources>
<StackLayout>
<ListView x:Name="listView">
...
</ListView>
</StackLayout>
</ContentPage>
css
.detailPageTitle {
font-style: bold;
font-size: 14;
text-align: center;
.detailPageSubtitle {
text-align: center;
font-style: italic;
A CSS class can be assigned to a XAML element by setting the StyleClass property of
the element to the CSS class name. Therefore, in the following example, the styles
defined by the .detailPageTitle class are assigned to the first Label, while the styles
defined by the .detailPageSubtitle class are assigned to the second Label.
XAML
<ContentPage ...>
<ContentPage.Resources>
</ContentPage.Resources>
<ScrollView>
<StackLayout>
</StackLayout>
</ScrollView>
</ContentPage>
css
listview image {
height: 60;
width: 60;
This selector identifies any Image elements that are children of ListView elements, and
sets their height and width to 60. Therefore, in the following XAML example, the
listview image selector will identify the Image that's a child of the ListView, and sets its
XAML
<ContentPage ...>
<ContentPage.Resources>
</ContentPage.Resources>
<StackLayout>
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
...
...
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
7 Note
The element element selector does not require the child element to be a direct
child of the parent – the child element may have a different parent. Selection
occurs provided that an ancestor is the specified first element.
css
stacklayout>image {
height: 200;
width: 200;
This selector identifies any Image elements that are direct children of StackLayout
elements, and sets their height and width to 200. Therefore, in the following example,
the stacklayout>image selector will identify the Image that's a direct child of the
StackLayout, and sets its height and width to 200.
XAML
<ContentPage ...>
<ContentPage.Resources>
</ContentPage.Resources>
<ScrollView>
<StackLayout>
...
...
</StackLayout>
</ScrollView>
</ContentPage>
7 Note
The element>element selector requires that the child element is a direct child of the
parent.
Selector reference
The following CSS selectors are supported by .NET MAUI:
#id #email Selects all elements with StyleId set to email . If StyleId
is not set, fallback to x:Name . When using XAML, x:Name
is preferred over StyleId . This selector is case sensitive.
element label Selects all elements of type Label, but not subclasses.
This selector is case insensitive.
^base ^contentpage Selects all elements with ContentPage as the base class,
including ContentPage itself. This selector is case
insensitive and isn't part of the CSS specification.
element,element label,button Selects all Button elements and all Label elements. This
selector is case insensitive.
element element stacklayout label Selects all Label elements inside a StackLayout. This
selector is case insensitive.
element+element label+entry Selects all Entry elements directly after a Label. This
selector is case insensitive.
Tip
[attribute]
@media and @supports
: and ::
7 Note
Property reference
The following CSS properties are supported by .NET MAUI (in the Values column, types
are italic, while string literals are gray ):
7 Note
initial is a valid value for all properties. It clears the value (resets to default) that
was set from another style.
all: initial .
Layout properties (box, or grid).
Shorthand properties, such as font , and border .
In addition, there's no inherit value and so inheritance isn't supported. Therefore you
can't, for example, set the font-size property on a layout and expect all the Label
instances in the layout to inherit the value. The one exception is the direction property,
which has a default value of inherit .
) Important
Color
The following color values are supported:
X11 colors , which match CSS colors and .NET MAUI colors. These color values
are case insensitive.
hex colors: #rgb , #argb , #rrggbb , #aarrggbb
rgb colors: rgb(255,0,0) , rgb(100%,0%,0%) . Values are in the range 0-255, or
0%-100%.
rgba colors: rgba(255, 0, 0, 0.8) , rgba(100%, 0%, 0%, 0.8) . The opacity value is
in the range 0.0-1.0.
hsl colors: hsl(120, 100%, 50%) . The h value is in the range 0-360, while s and l are
in the range 0%-100%.
hsla colors: hsla(120, 100%, 50%, .8) . The opacity value is in the range 0.0-1.0.
Thickness
One, two, three, or four thickness values are supported, each separated by white space:
7 Note
CSS thickness values differ from XAML Thickness values. For example, in XAML a
two-value Thickness indicates horizontal then vertical thickness, while a four-value
Thickness indicates left, then top, then right, then bottom thickness. In addition,
Functions
Linear and radial gradients can be specified using the linear-gradient() and radial-
gradient() CSS functions, respectively. The result of these functions should be assigned
.NET Multi-platform App UI (.NET MAUI) apps can respond to style changes dynamically
at runtime by using the DynamicResource markup extension. This markup extension is
similar to the StaticResource markup extension, in that both use a dictionary key to fetch
a value from a ResourceDictionary. However, while the StaticResource markup extension
performs a single dictionary lookup, the DynamicResource markup extension maintains
a link to the dictionary key. Therefore, if the value associated with the key is replaced,
the change is applied to the VisualElement. This enables runtime theming to be
implemented in .NET MAUI apps.
The process for implementing runtime theming in a .NET MAUI app is as follows:
) Important
Use the StaticResource markup extension if you don't need to change the app
theme at runtime.
The following screenshot shows themed pages, with the iOS app using a light theme
and the Android app using a dark theme:
7 Note
Changing a theme at runtime requires the use of XAML styles, and is not possible
using CSS.
.NET MAUI also has the ability to respond to system theme changes. The system theme
may change for a variety of reasons, depending on the device configuration. This
includes the system theme being explicitly changed by the user, it changing due to the
time of day, and it changing due to environmental factors such as low light. For more
information, see Respond to system theme changes.
Define themes
A theme is defined as a collection of resource objects stored in a ResourceDictionary.
The following example shows a ResourceDictionary for a light theme named LightTheme :
XAML
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemingDemo.LightTheme">
<Color x:Key="PageBackgroundColor">White</Color>
<Color x:Key="NavigationBarColor">WhiteSmoke</Color>
<Color x:Key="PrimaryColor">WhiteSmoke</Color>
<Color x:Key="SecondaryColor">Black</Color>
<Color x:Key="PrimaryTextColor">Black</Color>
<Color x:Key="SecondaryTextColor">White</Color>
<Color x:Key="TertiaryTextColor">Gray</Color>
<Color x:Key="TransparentColor">Transparent</Color>
</ResourceDictionary>
The following example shows a ResourceDictionary for a dark theme named DarkTheme :
XAML
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemingDemo.DarkTheme">
<Color x:Key="PageBackgroundColor">Black</Color>
<Color x:Key="NavigationBarColor">Teal</Color>
<Color x:Key="PrimaryColor">Teal</Color>
<Color x:Key="SecondaryColor">White</Color>
<Color x:Key="PrimaryTextColor">White</Color>
<Color x:Key="SecondaryTextColor">White</Color>
<Color x:Key="TertiaryTextColor">WhiteSmoke</Color>
<Color x:Key="TransparentColor">Transparent</Color>
</ResourceDictionary>
Each ResourceDictionary contains Color resources that define their respective themes,
with each ResourceDictionary using identical key values. For more information about
resource dictionaries, see Resource Dictionaries.
) Important
A code behind file is required for each ResourceDictionary, which calls the
InitializeComponent method. This is necessary so that a CLR object representing
XAML
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemingDemo.App">
<Application.Resources>
</Application.Resources>
</Application>
For more information about merging resource dictionaries, see Merged resource
dictionaries.
The following example shows three styles from that can be applied to all Label objects in
app:
XAML
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemingDemo.App">
<Application.Resources>
<Style x:Key="LargeLabelStyle"
TargetType="Label">
<Setter Property="TextColor"
<Setter Property="FontSize"
Value="30" />
</Style>
<Style x:Key="MediumLabelStyle"
TargetType="Label">
<Setter Property="TextColor"
<Setter Property="FontSize"
Value="25" />
</Style>
<Style x:Key="SmallLabelStyle"
TargetType="Label">
<Setter Property="TextColor"
<Setter Property="FontSize"
Value="15" />
</Style>
</Application.Resources>
</Application>
These styles are defined in the app-level resource dictionary, so that they can be
consumed by multiple pages. Each style consumes theme resources with the
DynamicResource markup extension.
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ThemingDemo"
x:Class="ThemingDemo.UserSummaryPage"
Title="User Summary"
BackgroundColor="{DynamicResource PageBackgroundColor}">
...
<ScrollView>
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
VerticalOptions="Center"
Margin="15"
...
</Grid>
<StackLayout Grid.Row="1"
Margin="10">
</StackLayout>
...
</Grid>
</ScrollView>
</ContentPage>
1. Remove the current theme from the app. This is achieved by clearing the
MergedDictionaries property of the app-level ResourceDictionary.
2. Load the selected theme. This is achieved by adding an instance of the selected
theme to the MergedDictionaries property of the app-level ResourceDictionary.
Any VisualElement objects that set properties with the DynamicResource markup
extension will then apply the new theme values. This occurs because the
DynamicResource markup extension maintains a link to dictionary keys. Therefore, when
the values associated with keys are replaced, the changes are applied to the
VisualElement objects.
In the sample application, a theme is selected via a modal page that contains a Picker.
The following code shows the OnPickerSelectionChanged method, which is executed
when the selected theme changes:
The following example shows removing the current theme and loading a new theme:
C#
ICollection<ResourceDictionary> mergedDictionaries =
Application.Current.Resources.MergedDictionaries;
if (mergedDictionaries != null)
mergedDictionaries.Clear();
mergedDictionaries.Add(new DarkTheme());
Devices typically include light and dark themes, which each refer to a broad set of
appearance preferences that can be set at the operating system level. Apps should
respect these system themes, and respond immediately when the system theme
changes.
The system theme may change for a variety of reasons, depending on the device
configuration. This includes the system theme being explicitly changed by the user, it
changing due to the time of day, and it changing due to environmental factors such as
low light.
.NET Multi-platform App UI (.NET MAUI) apps can respond to system theme changes by
consuming resources with the AppThemeBinding markup extension, and the
SetAppThemeColor and SetAppTheme<T> extension methods.
7 Note
.NET MAUI apps can respond to system theme changes on iOS 13 or greater,
Android 10 (API 29) or greater, macOS 10.14 or greater, and Windows 10 or
greater.
The following screenshot shows themed pages, for the light system theme on iOS and
the dark system theme on Android:
Define and consume theme resources
Resources for light and dark themes can be consumed with the AppThemeBinding
markup extension, and the SetAppThemeColor and SetAppTheme<T> extension methods.
With these approaches, resources are automatically applied based on the value of the
current system theme. In addition, objects that consume these resources are
automatically updated if the system theme changes while an app is running.
XAML
<StackLayout>
<Label Text="This text is green in light mode, and red in dark mode."
</StackLayout>
In this example, the text color of the first Label is set to green when the device is using
its light theme, and is set to red when the device is using its dark theme. Similarly, the
Image displays a different image file based upon the current system theme.
In addition, resources defined in a ResourceDictionary can be consumed with the
StaticResource markup extension:
XAML
<ContentPage ...>
<ContentPage.Resources>
<Color x:Key="LightPrimaryColor">WhiteSmoke</Color>
<Color x:Key="LightSecondaryColor">Black</Color>
<Color x:Key="DarkPrimaryColor">Teal</Color>
<Color x:Key="DarkSecondaryColor">White</Color>
<Style x:Key="ButtonStyle"
TargetType="Button">
<Setter Property="BackgroundColor"
Value="{AppThemeBinding Light={StaticResource
LightPrimaryColor}, Dark={StaticResource DarkPrimaryColor}}" />
<Setter Property="TextColor"
Value="{AppThemeBinding Light={StaticResource
LightSecondaryColor}, Dark={StaticResource DarkSecondaryColor}}" />
</Style>
</ContentPage.Resources>
</Grid>
</ContentPage>
In this example, the background color of the Grid and the Button style changes based
on whether the device is using its light theme or dark theme.
Extension methods
.NET MAUI includes SetAppThemeColor and SetAppTheme<T> extension methods that
enable VisualElement objects to respond to system theme changes.
The SetAppThemeColor method enables Color objects to be specified that will be set on a
target property based on the current system theme:
C#
In this example, the text color of the Label is set to green when the device is using its
light theme, and is set to red when the device is using its dark theme.
The SetAppTheme<T> method enables objects of type T to be specified that will be set on
a target property based on the current system theme:
C#
image.SetAppTheme<FileImageSource>(Image.SourceProperty, "lightlogo.png",
"darklogo.png");
In this example, the Image displays lightlogo.png when the device is using its light
theme, and darklogo.png when the device is using its dark theme.
C#
Light , which indicates that the device is using its light theme.
Dark , which indicates that the device is using its dark theme.
C#
Application.Current.UserAppTheme = AppTheme.Dark;
In this example, the app is set to use the theme defined for the system dark mode,
regardless of which system theme is currently operational.
7 Note
C#
};
) Important
To respond to theme changes on Android your MainActivity class must include the
ConfigChanges.UiMode flag in the Activity attribute. .NET MAUI apps created with
the Visual Studio project templates automatically include this flag.
Inspect the visual tree of a .NET MAUI
app
Article • 10/25/2022 • 2 minutes to read
.NET Multi-platform App UI (.NET MAUI) Live Visual Tree is a Visual Studio feature that
provides a tree view of the UI elements in your running .NET MAUI app.
Visual Studio
When your .NET MAUI app is running in debug configuration, with the debugger
attached, the Live Visual Tree window can be opened by selecting Debug >
Windows > Live Visual Tree from the Visual Studio menu bar:
Provided that Hot Reload is enabled, the Live Visual Tree window will display the
hierarchy of your app's UI elements regardless of whether the app's UI is built using
XAML or C#. However, you will have to disable Just My XAML to display the hierarchy of
your app's UI elements for UIs built using C#.
Just My XAML
Visual Studio
The view of the UI elements is simplified by default using a feature called Just My
XAML. Clicking the Show Just My XAML button disables the feature and shows all
UI elements in the visual tree:
Just My XAML can be permanently disabled by selecting Debug > Options > XAML
Hot Reload from the Visual Studio menu bar. Next, in the Options dialog box,
ensure that Enable Just My XAML in Live Visual Tree is disabled:
Find a UI element
The structure of a XAML UI has a lot of elements that you may not be interested in, and
if you don't have a full understanding of the app's source code you might have a
difficult time navigating the visual tree to find the UI element that you're looking for.
Therefore, on Windows the Live Visual Tree window has multiple approaches that let
you use the app's UI to help you find the element you want to examine:
Select element in the running application. You can enable this mode by clicking
the Select Element in the Running Application button in the Live Visual Tree
toolbar:
With this mode enabled, when you can select a UI element in the app the Live
Visual Tree window automatically updates to show the node in the tree
corresponding to that element.
Display layout adorners in the running application. You can enable this mode by
clicking the Display Layout Adorners in the Running Application button in the
Live Visual Tree toolbar:
When this mode is enabled, it causes the app window to show horizontal and
vertical lines along the bounds of the selected object so you can see what it aligns
to, as well as rectangles showing the margins.
Preview Selection. You can enable this mode by clicking the Preview Selected
Item button in the Live Visual Tree toolbar:
This mode shows the XAML source code where the element was declared,
provided that you have access to the app source code.
Visual states
Article • 03/03/2023 • 10 minutes to read
The .NET Multi-platform App UI (.NET MAUI) Visual State Manager provides a structured
way to make visual changes to the user interface from code. In most cases, the user
interface of an app is defined in XAML, and this XAML can include markup describing
how the Visual State Manager affects the visuals of the user interface.
The Visual State Manager introduces the concept of visual states. A .NET MAUI view such
as a Button can have several different visual appearances depending on its underlying
state — whether it's disabled, or pressed, or has input focus. These are the button's
states. Visual states are collected in visual state groups. All the visual states within a
visual state group are mutually exclusive. Both visual states and visual state groups are
identified by simple text strings.
The .NET MAUI Visual State Manager defines a visual state group named CommonStates
with the following visual states:
Normal
Disabled
Focused
Selected
PointerOver
The Normal , Disabled , Focused , and PointerOver visual states are supported on all
classes that derive from VisualElement, which is the base class for View and Page. In
addition, you can also define your own visual state groups and visual states.
The advantage of using the Visual State Manager to define appearance, rather than
accessing visual elements directly from code-behind, is that you can control how visual
elements react to different state entirely in XAML, which keeps all of the UI design in
one location.
7 Note
Triggers can also make changes to visuals in the user interface based on changes in
a view's properties or the firing of events. However, using triggers to deal with
various combinations of these changes can become confusing. With the Visual
State Manager, the visual states within a visual state group are always mutually
exclusive. At any time, only one state in each group is the current state.
Common visual states
The Visual State Manager allows you to include markup in your XAML file that can
change the visual appearance of a view if the view is normal, disabled, has input focus, is
selected, or has the mouse cursor hovering over it but not pressed. These are known as
the common states.
For example, suppose you have an Entry view on your page, and you want the visual
appearance of the Entry to change in the following ways:
The Entry should have a pink background when the Entry is disabled.
The Entry should have a lime background normally.
The Entry should expand to twice its normal height when it has input focus.
The Entry should have a light blue background when it has the mouse cursor
hovering over it but not pressed.
You can attach the Visual State Manager markup to an individual view, or you can define
it in a style if it applies to multiple views.
name of the group. Alternatively, the VisualStateGroup class defines a Name property
that you can use instead. For more information about attached properties, see Attached
properties.
The VisualState class defines a property named Setters , which is a collection of Setter
objects. These are the same Setter objects that you use in a Style object. Setters isn't
the content property of VisualState, so it's necessary to include property element tags
for the Setters property. Setter objects should be inserted as children of Setters . Each
Setter object indicates the value of a property when that state is current. Any property
referenced by a Setter object must be backed by a bindable property.
) Important
XAML
<Entry FontSize="18">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Focused">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>
The following screenshot shows the Entry in its four defined visual states:
When the Entry is in the Normal state, its background is lime. When the Entry gains input
focus its font size doubles. When the Entry becomes disabled, its background becomes
pink. The Entry doesn't retain its lime background when it gains input focus. When the
mouse pointer hovers over the Entry, but isn't pressed, the Entry background becomes
light blue. As the Visual State Manager switches between the visual states, the
properties set by the previous state are unset. Therefore, the visual states are mutually
exclusive.
If you want the Entry to have a lime background in the Focused state, add another
Setter to that visual state:
XAML
<VisualState x:Name="Focused">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
The following example shows an implicit style for an Entry that defines the common
visual states:
XAML
<Style TargetType="Entry">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Focused">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
When this style is included in a page-level resource dictionary, the Style object will be
applied to all Entry objects on the page. Therefore, all Entry objects on the page will
respond in the same way to their visual states.
The Setter type has a TargetName property, of type string , that represents the target
object that the Setter for a visual state will manipulate. When the TargetName property is
defined, the Setter sets the Property of the object defined in TargetName to Value :
XAML
<Setter TargetName="label"
Property="Label.TextColor"
Value="Red" />
In this example, a Label named label will have its TextColor property set to Red . When
setting the TargetName property you must specify the full path to the property in
Property . Therefore, to set the TextColor property on a Label, Property is specified as
Label.TextColor .
7 Note
The following example shows how to set state on multiple objects, from a single visual
state group:
XAML
<StackLayout>
<Entry x:Name="entry"
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="Scale"
Value="0.8" />
<Setter TargetName="entry"
Property="Entry.Text"
Value="Paris" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
</StackLayout>
In this example, the Normal state is active when the Button isn't pressed, and a response
can be entered into the Entry. The Pressed state becomes active when the Button is
pressed, and specifies that its Scale property will be changed from the default value of
1 to 0.8. In addition, the Entry named entry will have its Text property set to Paris.
Therefore, the result is that when the Button is pressed it's rescaled to be slightly
smaller, and the Entry displays Paris:
Then, when the Button is released it's rescaled to its default value of 1, and the Entry
displays any previously entered text.
) Important
Property paths are unsupported in Setter elements that specify the TargetName
property.
The following example shows how to use the Visual State Manager for input validation:
XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="VsmDemos.VsmValidationPage"
Title="VSM Validation">
<StackLayout x:Name="stackLayout"
Padding="10, 10">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValidityStates">
<VisualState Name="Valid">
<VisualState.Setters>
<Setter TargetName="helpLabel"
Property="Label.TextColor"
Value="Transparent" />
<Setter TargetName="entry"
Property="Entry.BackgroundColor"
Value="Lime" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Invalid">
<VisualState.Setters>
<Setter TargetName="entry"
Property="Entry.BackgroundColor"
Value="Pink" />
<Setter TargetName="submitButton"
Property="Button.IsEnabled"
Value="False" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
FontSize="18" />
<Entry x:Name="entry"
Placeholder="555-555-5555"
FontSize="18"
Margin="30, 0, 0, 0"
TextChanged="OnTextChanged" />
<Label x:Name="helpLabel"
<Button x:Name="submitButton"
Text="Submit"
FontSize="18"
Margin="0, 20"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>
In this example, visual states are attached to the StackLayout, and there are two
mutually-exclusive states named Valid and Invalid . If the Entry does not contain a
valid phone number, then the current state is Invalid , and so the Entry has a pink
background, the second Label is visible, and the Button is disabled. When a valid phone
number is entered, then the current state becomes Valid . The Entry gets a lime
background, the second Label disappears, and the Button is now enabled:
The code-behind file is responsible for handling the TextChanged event from the Entry.
The handler uses a regular expression to determine if the input string is valid or not. The
GoToState method in the code-behind file calls the static VisualStateManager.GoToState
C#
public VsmValidationPage()
InitializeComponent();
GoToState(false);
GoToState(isValid);
VisualStateManager.GoToState(stackLayout, visualState);
In this example, the GoToState method is called from the constructor to initialize the
state. There should always be a current state. The code-behind file then calls
VisualStateManager.GoToState , with a state name, on the object that defines the visual
states.
State triggers are added to the StateTriggers collection of a VisualState. This collection
can contain a single state trigger, or multiple state triggers. A VisualState will be applied
when any state triggers in the collection are active.
When using state triggers to control visual states, .NET MAUI uses the following
precedence rules to determine which trigger (and corresponding VisualState) will be
active:
If multiple triggers are simultaneously active (for example, two custom triggers) then the
first trigger declared in the markup takes precedence.
The manifest file for your .NET MAUI Android app is generated as part of the .NET MAUI
build process on Android. This build process takes the XML in the
Platforms\Android\AndroidManifest.xml file, and merges it with any XML that's
generated from specific attributes on your classes. The resulting manifest file can be
found in the obj folder. For example, it can be found at obj\Debug\net7.0-
android\AndroidManifest.xml for debug builds on .NET 7.
At build time, assemblies are scanned for non- abstract classes that derive from Activity
and that have the ActivityAttribute applied. These classes and attributes are used to
generate the app's manifest. For example, consider the following code:
C#
using Android.App;
namespace MyMauiApp;
This example results in nothing being generated in the manifest file. For an <activity/>
element to be generated, you'd need to add the ActivityAttribute:
C#
using Android.App;
namespace MyMauiApp;
[Activity]
This example causes the following XML fragment to be added to the manifest file:
XML
7 Note
Activity name
The type name of an activity is based on the 64-bit cyclic redundancy check of the
assembly-qualified name of the type being exported. This enables the same fully-
qualified name to be provided from two different assemblies without receiving a
packaging error.
To override this default and explicitly specify the name of your activity, use the Name
property:
C#
using Android.App;
namespace MyMauiApp;
[Activity (Name="companyname.mymauiapp.activity")]
XML
7 Note
You should only use the Name property for backward-compatibility reasons, as such
renaming can slow down type lookup at runtime.
A typical scenario for setting the Name property is when you need to obtain a readable
Java name for your activity. This can be useful if another Android app needs to be able
to open your app, or if you have a script for launching your app and testing startup
time.
C#
using Android.App;
namespace MyMauiApp;
XML
android:name="crc64bdb9c38958c20c7c.MainActivity">
<intent-filter>
</intent-filter>
</activity>
Permissions
When you add permissions to an Android app, they're recorded in the manifest file. For
example, if you set the ACCESS_NETWORK_STATE permission, the following element is
added to the manifest file:
XML
The .NET MAUI app project template sets the INTERNET and ACCESS_NETWORK_STATE
permissions in Platforms\Android\AndroidManifest.xml, because most apps require
internet access. If you remove the INTERNET permission from your manifest, debug
builds will still include the permission in the generated manifest file.
Tip
If you find that switching to a release build causes your app to lose a permission
that was available in the debug build, verify that you've explicitly set the required
permission in your manifest file.
C#
using Android.App;
using Android.Content;
namespace MyMauiApp;
[IntentFilter(new[] {Intent.ActionView},
android:name="crc64bdb9c38958c20c7c.MainActivity">
<intent-filter>
</intent-filter>
<intent-filter>
</intent-filter>
</activity>
Application element
The Android manifest file also provides a way for you to declare properties for your
entire app. This is achieved via the <application> element and its counterpart, the
ApplicationAttribute. Typically, you declare <application> properties for your entire app
and then override these properties as required on an activity basis.
C#
using Android.App;
using Android.Runtime;
namespace MyMauiApp;
: base(handle, ownership)
<application android:label="MyMauiApp"
android:theme="@style/Maui.SplashTheme" android:debuggable="true" ...>
7 Note
In this example, all activities in the app will default to the Maui.SplashTheme style. If you
set an activity's theme to Maui.MyTheme , only that activity will use the Maui.MyTheme style
while any other activities in your app will default to the Maui.SplashTheme style that's set
in the <application> element.
The ApplicationAttribute isn't the only way to configure <application> attributes. You
can also insert properties directly into the <application> element of the manifest file.
These properties are then merged into the generated manifest file. For more
information, see the properties section of ApplicationAttribute.
) Important
the title bar. .NET MAUI includes it in the generated manifest as the value of
android.label :
XML
To specify an activities label on the title bar, use the Label property:
C#
using Android.App;
namespace MyMauiApp;
XML
android:name="crc64bdb9c38958c20c7c.MyActivity" />
App icon
By default, your app will be given a .NET icon. For information about specifying a
custom icon, see Change a .NET MAUI app icon.
Attributes
The following table shows the .NET for Android attributes that generate Android
manifest XML fragments:
Attribute Description
See also
App Manifest Overview on developer.android.com
Apple account management
Article • 03/23/2023 • 3 minutes to read
When developing a .NET Multi-platform App UI (.NET MAUI) iOS app it's essential to test
it by deploying it to a device, prior to uploading it to the App Store for distribution. Both
of these tasks require you to have an Apple ID , and have enrolled your Apple ID in the
Apple Developer Program .
The Apple account management interface in Visual Studio and Visual Studio for Mac
enables you to add your Apple ID, and provides the ability to view information about
development teams associated with the Apple ID, view signing certificates and
provisioning profiles, create new signing certificates, and download existing provisioning
profiles.
Visual Studio
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts, click on
the Add button and select Individual Account... or Enterprise Account...:
2. To add an enterprise account, in the Enterprise Account... dialog, enter your
Apple ID and password, and click the Login button:
Provided that your account details are valid, your Apple Developer Account
will be added to Visual Studio.
Provided that your account details are valid, your Apple Developer Account
will be added to Visual Studio.
4. Once your account has been added successfully, you'll see your Apple ID and
any teams that your Apple ID is part of:
View signing certificates and provisioning
profiles
When you select an Apple Developer Account and a Team name, the View Details...
button becomes enabled. Clicking it opens the team details dialog that displays a list of
signing identifies and provisioning profiles that are installed on your machine. This
dialog organizes the signing identities by type, with the Status column advising you if
the certificate is:
Valid. The signing identity (both the certificate and the private key) is installed on
your machine and has not expired.
Not in Keychain. Apple holds a valid signing identity. To install this on your
machine, it must be exported from another machine. You cannot download the
signing identity from your Apple Developer Account as it doesn't contain the
private key.
Private key is missing. A certificate with no private key is installed in the keychain.
Expired. The certificate is expired. You should remove this from your keychain.
On iOS, .NET Multi-platform App UI (.NET MAUI) apps run in a sandbox that provides a
set of rules that limit access between the app and system resources or user data. Apple
provides capabilities, also known as app services, as a means of extending functionality
and widening the scope of what iOS apps can do. Capabilities enable you to add a
deeper integration with platform features to your app, such as integration with Siri. For
more information about capabilities, see Capabilities on developer.apple.com.
Capabilities are added to your app's provisioning profile, and are used when code
signing your app. The provisioning profile must contain an App ID, that matches your
app's bundle identifier, with the required capabilities enabled. The provisioning profile
can be created automatically in Visual Studio or Visual Studio for Mac, or manually in
your Apple Developer Account.
Capabilities are closely related to the concept of entitlements. They both request the
expansion of the sandbox your app runs in, to give it additional capabilities.
Entitlements are typically added when developing your app, while capabilities are
typically added when code signing your app for distribution. However, when automatic
provisioning is enabled, adding certain entitlements to your app will also update the
capabilities for your app in its provisioning profile. For more information about
entitlements, see Entitlements.
The following list shows the capabilities that can be automatically provisioned using
Visual Studio or Visual Studio for Mac:
HealthKit
HomeKit
Hotspot configuration
Inter-app audio
Multipath
Network extensions
Near field communication tag reader
Personal VPN
Siri
Wireless accessory configuration
In Visual Studio, all capabilities are added to your app's Entitlements.plist file. The
capabilities listed above are also added to your provisioning profile. For more
information about entitlements, including how to add an entitlements file to your
project, see Entitlements.
Visual Studio
1. In Visual Studio, ensure that the IDE is paired to a Mac Build host. For more
information, see Pair to Mac for iOS development.
2. In Visual Studio, enable automatic provisioning for your project. For more
information, see Enable automatic provisioning.
5. In the entitlements editor, select and configure any entitlements required for
your app:
6. Save the changes to your Entitlements.plist file to add the entitlement
key/value pairs to the file, and add the app service to your App ID.
It may also be necessary to set privacy keys in Info.plist, for certain capabilities.
) Important
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
There are two types of App ID - explicit and wildcard. An explicit App ID is unique to a
single app, and typically takes the form com.domainname.myid . An explicit App ID allows
the installation of one app, with a matching bundle identifier, to a device. Explicit App
IDs are required for apps that enable app-specific capabilities.
2. In the Register a new identifier page, select App IDs and click the Continue
button.
3. In the Register a new identifier page, select the App type and click the Continue
button.
4. In the Register an App ID page, provide a Description and set the Bundle ID to
Explicit. Then, enter an App ID in the format com.domainname.myid :
5. In the Register an App ID page, enable your required capabilities under the
Capabilities and App Services tabs:
7. In the Confirm your App ID page, review the information and then click the
Register button. Provided that your App ID successfully registers, you'll be
returned to the Identifiers section of your Apple Developer Account.
9. In the Edit your App ID Configuration page, any of your enabled capabilities that
require additional setup will have a Configure button:
Click any Configure buttons to configure your enabled capabilities. For more
information, see Configure app capabilities on developer.apple.com.
4. In the Generate a Provisioning Profile page, select the certificates to include in the
provisioning profile and then click the Continue button:
5. In the Generate a Provisioning Profile page, select the devices that the app will be
installed on and then click the Continue button:
1. In Visual Studio, download the provisioning profile you've just created so that it's
available for signing your app. For more information, see Download provisioning
profiles in Visual Studio.
2. In Visual Studio, enable manual provisioning for your project. For more
information, see Enable manual provisioning.
Troubleshoot
The following list details the common issues that can cause issues when developing a
.NET MAUI iOS app that uses capabilities:
Ensure that the correct App ID has been created and registered in the Identifiers
section of your Apple Developer Account.
Ensure that the capability has been added to the App ID and that the capability is
configured using the correct values.
Ensure that the provisioning profile has been installed on your development
machine and that the app's Info.plist file is using a bundle identifier that's identical
to your App ID.
Ensure that the app's Entitlements.plist file has the correct entitlements enabled.
Ensure that the any required privacy keys are set in Info.plist.
Ensure that the app consumes the Entitlements.plist file.
iOS entitlements
Article • 03/23/2023 • 12 minutes to read
On iOS, .NET Multi-platform App UI (.NET MAUI) apps run in a sandbox that provides a
set of rules that limit access between the app and system resources or user data.
Entitlements are used to request the expansion of the sandbox to give your app
additional capabilities, such as integration with Siri. Any entitlements used by your app
must be specified in the app's Entitlements.plist file. For more information about
entitlements, see Entitlements on developer.apple.com.
In addition to specifying entitlements, the Entitlements.plist file is used to code sign the
app. When code signing your app, the entitlements file is combined with information
from your Apple Developer Account, and other project information to apply a final set
of entitlements to your app.
Entitlements are closely related to the concept of capabilities. They both request the
expansion of the sandbox your app runs in, to give it additional capabilities.
Entitlements are typically added when developing your app, while capabilities are
typically added when code signing your app for distribution. However, when automatic
provisioning is enabled, adding certain entitlements to your app will also update the
capabilities for your app in its provisioning profile. For more information, see Add
capabilities with Visual Studio.
) Important
XML
<plist version="1.0">
<dict>
</dict>
</plist>
Set entitlements
Entitlements can be configured in Visual Studio by double-clicking the Entitlements.plist
file to open it in the entitlements editor.
Visual Studio
It may also be necessary to set privacy keys in Info.plist, for certain entitlements.
Consume entitlements
A .NET MAUI iOS app must be configured to consume the entitlements defined in the
Entitlements.plist file.
Visual Studio
1. In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the iOS > Bundle Signing tab.
2. In the Bundle Signing settings, click the Browse... button for the Custom
Entitlements field.
4. In the project properties, the Custom Entitlements field will be populated with
your entitlements file:
7 Note
Visual Studio will set the custom entitlements field for both debug and release
builds.
Key reference
The entitlement key/value pairs are listed below for reference. In Visual Studio they can
be added by editing the Entitlements.plist file as an XML file. In Visual Studio for Mac
they can be added via the Source view of the entitlements editor.
XML
<key>com.apple.developer.networking.wifi-info</key>
<true/>
App Attest
With the App Attest entitlement, you can generate a special cryptographic key on your
device and use it to validate the integrity of your app before a server provides access to
sensitive data.
XML
<key>com.apple.developer.devicecheck.appattest-environment</key>
<string>development</string>
App groups
The app groups entitlement enables your app to access group containers shared among
multiple related apps as well as perform inter-process communication between the
apps.
XML
<key>com.apple.security.application-groups</key>
<array>
<string>group.MyAppGroups</string>
</array>
Apple Pay
The Apple Pay entitlement enables users to easily and securely pay for physical good
and services such as groceries, clothing, tickets, and reservations using payment
information stored on their device.
XML
<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.your.merchantid</string>
</array>
Associated domains
The associated domains entitlement enables your app to be associated with specific
domains for specific services, such as accessing Safari, saved passwords, and activity
continuation.
XML
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:example.com</string>
</array>
XML
<key>com.apple.developer.authentication-services.autofill-credential-
provider</key>
<true/>
ClassKit
The ClassKit entitlement enables your app to privately and securely share student
progress with teachers on assigned activities, such as reading a chapter in a book or
taking a quiz, in school-managed environments.
XML
<key>com.apple.developer.ClassKit-environment</key>
<string>development</string>
XML
Communication notifications
The communication notifications entitlement enables an app to send communication
notifications from a person to a person or multiple people.
XML
<key>com.apple.developer.usernotifications.communication</key>
<true/>
Data protection
The data protection entitlement enables your app to use the built-in encryption on
supported devices. When you specify a file as protected, the system will store the file in
an encrypted format.
The entitlement is defined using the com.apple.developer.default-data-protection key,
of type String :
XML
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>
XML
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
Family controls
The family controls entitlement enables parental controls in your app, granting access to
the Managed Settings and Device Activity frameworks in the ScreenTime API. Use of
Family controls requires Family Sharing for user enrolment. It prevents removal of your
app and enables on-device content filters from Network Extensions.
XML
<key>com.apple.developer.family-controls</key>
<true/>
XML
<key>com.apple.developer.fileprovider.testing-mode</key>
<true/>
Fonts
The fonts entitlement enables your app, with user permission, to install and use custom
fonts.
XML
<key>com.apple.developer.user-fonts</key>
<array>
<string>system-installation</string>
</array>
Group activities
The group activities entitlement enables an app to communicate with the same app on
one or more other devices, to create a group activity within a FaceTime call. Group
activities on FaceTime let users watch video together, listen to music together, or
perform another synchronous activity.
<key>com.apple.developer.group-session</key>
<true/>
HealthKit
The HealthKit entitlement enables your app to access, with user permission, personal
health information.
XML
<key>com.apple.developer.healthkit</key>
<true/>
HomeKit
The HomeKit entitlement enables your app to interact with HomeKit accessories.
XML
<key>com.apple.developer.homekit</key>
<true/>
Hotspot configuration
The hotspot configuration entitlement entitlement enables your app to configure WiFi
networks.
<key>com.apple.developer.networking.HotspotConfiguration</key>
<true/>
iCloud
The iCloud entitlement enables your app to store data in the cloud, making it possible
for users to share their data across multiple devices.
XML
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.companyname.test</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
XML
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
Inter-app audio
The inter-app audio entitlement enables your app to send and receive audio to/from
other apps that have Inter-app audio enabled.
XML
<key>inter-app-audio</key>
<true/>
) Important
Keychain
The Keychain entitlement enables multiple apps written by the same team to share
passwords.
XML
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.companyname.test</string>
</array>
XML
<key>com.apple.developer.associated-domains.mdm-managed</key>
<true/>
Multipath
The Multipath entitlement enables your app to use multipath protocols such as
Multipath TCP, which will seamlessly handover traffic from one interface to another.
XML
<key>com.apple.developer.networking.multipath</key>
<true/>
XML
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>
For more information, see Near Field Communication Tag Reader Session Formats
Entitlement on developer.apple.com.
Network extensions
The network extensions entitlement enables you to create app extensions that extend
and customize the network capabilities of your device.
XML
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>content-filter-provider</string>
</array>
Personal VPN
The personal VPN entitlement enables your app to use custom VPN connections.
XML
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
Push notifications
The push notifications entitlement enables your app to receive push notifications.
XML
<key>aps-environment</key>
<string>development</string>
Push to talk
The push to talk entitlement enables your app to report Push to Talk channels to the
system so that it can handle transmitting and receiving background audio.
XML
<key>com.apple.developer.push-to-talk</key>
<true/>
XML
<key>com.apple.developer.shared-with-you</key>
<true/>
XML
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
Siri
The Siri entitlement enables your app to handle Siri requests.
XML
<key>com.apple.developer.siri</key>
<true/>
XML
<key>com.apple.developer.usernotifications.time-sensitive</key>
<true/>
Wallet
The wallet entitlement enables your app to manage passes, tickets, gift cards, and
loyalty cards. It supports a variety of bar code formats.
The entitlement is defined using the com.apple.developer.pass-type-identifiers key, of
type Array of String :
XML
<key>com.apple.developer.pass-type-identifiers</key>
<array>
<string>$(TeamIdentifierPrefix)*</string>
</array>
This example will enable your app to allow all pass types. To restrict your app and only
allow a set of team pass types, set the string value to
$(TeamIdentifierPrefix)pass.$(CFBundleIdentifier) where pass.$(CFBundleIdentifier)
WeatherKit
The WeatherKit entitlement enables an app to receive and process current and
forecasted weather information.
XML
<key>com.apple.developer.weatherkit</key>
<true/>
XML
<key>com.apple.external-accessory.wireless-configuration</key>
<true/>
An information property list file is an XML file encoded using Unicode UTF-8 that
contains configuration information for your .NET Multi-platform App UI (.NET MAUI)
app on iOS and Mac Catalyst. The root node of the file is a dictionary, which contains a
set of keys and values that define your app configuration. The name of the information
property list file is Info.plist, and is case sensitive. All .NET MAUI iOS and Mac Catalyst
apps must contain an Info.plist file that describes the app.
.NET MAUI creates Info.plist files for iOS and Mac Catalyst when you create a .NET MAUI
app from a project template. These files are located in the Platforms/iOS and
Platforms/Mac Catalyst folders, and are populated with an initial list of property list keys.
When you build your app, .NET MAUI copies your Info.plist file into the compiled bundle,
before code signing the bundle. During the copy operation, .NET MAUI uses build
properties to perform some variable substitution. It can also insert additional keys
representing configuration that's specified in other ways. Due to this, the information
property list file that ships in your app bundle isn't identical to the source file in your
project.
Visual Studio
Values for the Application Name, Bundle Identifier, Version, and Build
fields are retrieved from your app's project file. For more information, see
Provide app info.
Application name
The application name for a .NET MAUI app is stored in the app's project file as the
ApplicationTitle build property:
Visual Studio
In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab. The Application
Title field lists the application name.
When the value of the Application Title field is updated, the value of the Application
Name field in the application view in the Info.plist file will be automatically updated.
Application ID
The bundle identifier for a .NET MAUI app is stored in the app's project file as the
ApplicationId build property:
Visual Studio
In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab. The Application ID
field lists the bundle identifier.
When the value of the Application ID field is updated, the value of the Bundle Identifier
field in the application view in the Info.plist file will be automatically updated.
Visual Studio
In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab. The Application
Display Version field lists the application display version.
When the value of the Application Display Version field is updated, the value of the
Version field in the application view in the Info.plist file will be automatically updated.
Application version
The application version for a .NET MAUI app is stored in the app's project file as the
ApplicationVersion build property:
Visual Studio
In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab. The Application
Version field lists the application version.
When the value of the Application Version field is updated, the value of the Build field
in the application view in the Info.plist file will be automatically updated.
See also
Information Property List on developer.apple.com
Device provisioning for iOS
Article • 03/23/2023 • 2 minutes to read
While developing a .NET Multi-platform App UI (.NET MAUI) app it's essential to test it
by deploying the app to a physical device, in addition to the simulator. Device-only bugs
and performance issues can transpire when running on a device, due to hardware
limitations such as memory or network connectivity. To test an app on a physical device,
the device must be provisioned, and Apple must be informed that the device will be
used for testing.
The following diagram shows the steps required to provision an app so that it can be
deployed to a device:
Before attempting to deploy an app to a device, you must have an active subscription to
Apple's Developer Program. Apple offers two program options:
To register for either of these programs, you must first have an Apple ID . Then you
can visit the Apple Developer Program to register for a program.
To run an app on a device requires the app to include a thumbprint that contains
information about the app and the developer. iOS uses this thumbprint to make sure
that the app hasn't been tampered with. This is achieved by requiring app developers to
register their Apple ID as a developer, generate a certificate, register the device on
which the app will be deploying during the development process, and create an App ID
and provisioning profile.
When deploying an app to a device, a provisioning profile is also installed on the device.
The provisioning profile exists to verify the information that the app was signed with at
build time and is cryptographically signed by Apple. Together, the provisioning profile
and thumbprint checks determine if an app can be deployed to a device by checking the
following:
Certificate – has the app been signed with a private key that has a corresponding
public key in the provisioning profile? The certificate also associates the developer
with a development team.
App ID – does the bundle identifier for the app match the App ID in the
provisioning profile?
Device – is the device contained in the provisioning profile?
Once you've added your Apple Developer Account to Visual Studio, you can use any
associated team. Certificates, App IDs, and profiles can then be created against the
team. The team ID is also used to create a prefix for the App ID that will be included in
the provisioning profile, which enables Apple to verify that an app can be deployed to a
device.
) Important
Before you begin, ensure that you've accepted any user license agreements in your
Apple Developer Account and App Store Connect .
Visual Studio
1. In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the iOS > Bundle Signing tab and ensure that
Automatic Provisioning is selected in the Scheme drop-down:
2. In the Bundle Signing settings, click the Configure Automatic Provisioning
hyperlink.
Visual Studio
1. In Visual Studio, ensure that the IDE is paired to a Mac Build host. For more
information, see Pair to Mac for iOS development.
2. Ensure that your iOS device is connected to your Mac build host via USB or
WiFi. For more information about wireless deployment, see Wireless
deployment for .NET MAUI iOS apps.
3. In the Visual Studio toolbar, use the Debug Target drop-down to select iOS
Remote Devices and then the device that's connected to your Mac build host:
4. In the Visual Studio toolbar, press the green Start button to launch the app on
your remote device:
7 Note
An iOS device is plugged into your Mac. This automatically checks to see if the
device is registered in your Apple Developer Account. If it isn't, it will add it and
generate a new provisioning profile that contains it.
The Bundle ID of your app is changed. This updates the App ID and so a new
provisioning profile containing this App ID is created.
A supported capability is enabled in the Entitlements.plist file. This capability will be
added to the App ID and a new provisioning profile with the updated App ID is
generated. Not all capabilities are currently supported. For more information about
capabilities, see Capabilities.
In some cases, an app's entitlements require an explicit App ID. The following
entitlements do not support wildcard App IDs:
App Groups
Associated Domains
Apple Pay
Game Center
HealthKit
HomeKit
Hotspot
In-App purchase
Multipath
NFC
Personal VPN
Push Notifications
Wireless Accessory Configuration
If your app uses one of these entitlements, Visual Studio will attempt to create an
explicit App ID.
Troubleshoot
It may take several hours for a new Apple Developer Account to be approved. You won't
be able to enable automatic provisioning until the account has been approved.
If the automatic provisioning process fails with the error message Authentication
Service Is Unavailable , sign in to either App Store Connect or your Apple Developer
account to check that you have accepted the latest service agreements.
This can occur because automatic provisioning doesn't remove the need to manually
copy certificates between machines, when you're using multiple machines for
development. This is because the private key that creates a certificate only exists on the
machine that created the certificate.
7 Note
To copy a certificate from a Mac to another Mac, export the certificate from
Keychain Access on the Mac that created the certificate and then import it
into Keychain Access on the other Mac.
To copy a certificate from a Mac to a Windows machine, export the certificate
from Keychain Access on the Mac and then on the Windows machine import
it into Visual Studio with the Import Certificate button.
It's not possible to copy a certificate that was created by Visual Studio on a
Windows machine, to another machine, because it's password protected.
After the certificate has been imported, Visual Studio will show its status as "Valid":
It should then be possible for Visual Studio to automatically provision your project.
Manual provisioning for iOS apps
Article • 03/23/2023 • 11 minutes to read
Development teams, certificates, and profiles can all be managed by accessing the
Certificates, Identifiers & Profiles section of your Apple Developer Account.
Development certificates and their associated keys establish your identity with Apple,
and associate you with specific devices and profiles for development. This is analogous
to adding your digital signature to your apps. Apple uses these certificates to ensure
that you can only deploy your apps to specific devices.
) Important
You can only have two iOS development certificates at any one time. If you need to
create any more, you will need to revoke an existing one. Any machine using a
revoked certificate will not be able to sign their app.
Once you've added your Apple Developer Account to Visual Studio, you need to
generate a signing certificate.
Visual Studio
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts.
2. In the Apple Developer Accounts dialog, select a team and then select View
Details.
3. In the Details dialog, select Create Certificate > iOS Development. A new
signing identity will be created and will sync with Apple if you have the correct
permissions.
) Important
The private key and certificate that make up your signing identity will also be
exported to Keychain Access on your Mac build host, provided that the IDE is
paired to it. For more information, see Pair to Mac for iOS development.
2 Warning
Losing the certificate and associated keys can be incredibly disruptive, as it will
require revoking existing certificates and re-provisioning any associated devices.
After successfully setting up a development certificate, export a backup copy and
store it in a safe place. For more information on how to do this, see Export
developer accounts on help.apple.com.
Add a device
When creating a provisioning profile, the profile must include which devices can run the
app. Before selecting a device to be added to a provisioning profile you must first add
the device to your Apple Developer Account. This can be achieved with the following
steps:
1. Connect the device to be provisioned to your local Mac with a USB cable.
3. In Xcode, select the Devices tab, and select the device from the list of connected
devices.
6. In the Register a New Device page, set the correct Platform and provide a name
for the new device. Then paste the identifier from the clipboard into the Device ID
(UDID) field, and click the Continue button:
7. In the Register a New Device page, review the information and then click the
Register button.
Repeat the above steps for any iOS device that you want to deploy a .NET MAUI iOS app
onto.
Create an App ID
After adding a device to your Apple Developer Account, you should create an App ID.
An App ID is similar to a reverse-DNS string, that uniquely identifies an app, and should
be identical to the bundle identifier for your app.
) Important
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
Wildcard. A wildcard App ID allows you to use a single App ID to match multiple
apps, and typically takes the form com.domainname.* . A wildcard App ID can be
used to install most apps to a device, and should be used for apps that do not
enable app-specific capabilities.
Explicit. An explicit App ID is unique to a single app, and typically takes the form
com.domainname.myid . An explicit App ID allows the installation of one app, with a
matching bundle identifier, to a device. Explicit App IDs are typically used for apps
that enable app-specific capabilities such as Apple Pay, or Game Center.
The recommended approach is to create a wildcard App ID, unless your app uses app-
specific capabilities. For more information about capabilities, see Capabilities.
2. In the Register a new identifier page, select App IDs and click the Continue
button.
3. In the Register a new identifier page, select the App type and click the Continue
button.
4. In the Register an App ID page, provide a Description and set the Bundle ID to
Wildcard. Then, enter an App ID in the format com.domainname.* and click the
Continue button:
5. In the Confirm your App ID page, review the information and then click the
Register button.
4. In the Generate a Provisioning Profile page, select the certificates to include in the
provisioning profile and then click the Continue button.
5. In the Generate a Provisioning Profile page, select the devices that the app will be
installed on and then click the Continue button.
Visual Studio
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts.
2. In the Apple Developer Accounts dialog, select a team and click the View
Details... button.
3. In the Details dialog, verify that the new profile appears in the Provisioning
Profiles list. You may need to restart Visual Studio to refresh the list.
4. In the Details dialog, click the Download All Profiles button.
Visual Studio
1. In Solution Explorer right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab and ensure that
the value of the Application ID field corresponds to the format of the App ID
you created earlier.
2. In the project properties, navigate to the iOS Bundle Signing tab and ensure
that Manual Provisioning is selected in the Scheme drop-down:
Deploy to device
After configuring provisioning in your .NET MAUI app project, the app can be deployed
to a device.
Visual Studio
1. In Visual Studio, ensure that the IDE is paired to a Mac Build host. For more
information, see Pair to Mac for iOS development.
2. Ensure that your iOS device is connected to your Mac build host via USB or
WiFi. For more information about wireless deployment, see Wireless
deployment for .NET MAUI iOS apps.
3. In the Visual Studio toolbar, use the Debug Target drop-down to select iOS
Remote Devices and then the device that's connected to your Mac build host:
4. In the Visual Studio toolbar, press the green Start button to launch the app on
your remote device:
7 Note
On Mac Catalyst .NET Multi-platform App UI (.NET MAUI) apps run in a sandbox that
provides a set of rules that limit access between the app and system resources or user
data. Apple provides capabilities, also known as app services, as a means of extending
functionality and widening the scope of what Mac Catalyst apps can do. For more
information about capabilities, see Capabilities on developer.apple.com.
Capabilities are added to your app's provisioning profile, and are used when code
signing your app. The provisioning profile must contain an App ID, that matches your
app's bundle identifier, with the required capabilities enabled. The provisioning profile
should be created in your Apple Developer Account.
Capabilities are closely related to the concept of entitlements. They both request the
expansion of the sandbox your app runs in, to give it additional capabilities.
Entitlements are typically added when developing your app, while capabilities are
typically added when code signing your app for distribution. For more information
about entitlements, see Entitlements.
When adding a new capability to your provisioning profile, you should also add the
same capability to your app's Entitlements.plist file and ensure that the app consumes
this file. For more information, see Entitlements. It may also be necessary to set privacy
keys in Info.plist, for certain capabilities.
If you intend to distribute your app via the Mac App Store, see Create a
distribution certificate in Publish a Mac Catalyst app for Mac App Store
distribution.
If you intend to distribute your app outside the Mac App Store, see Create a
developer ID application certificate in Publish a Mac Catalyst app for distribution
outside the Mac App Store.
If you intend to distribute your app to a limited number of users on registered
devices, outside the Mac App Store, see Create a development certificate in Publish
a Mac Catalyst app for ad-hoc distribution.
) Important
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
There are two types of App ID - explicit and wildcard. An explicit App ID is unique to a
single app, and typically takes the form com.domainname.myid . An explicit App ID allows
the installation of one app, with a matching bundle identifier, to a device. Explicit App
IDs are required for apps that enable app-specific capabilities.
3. In the Register a new identifier page, select the App type and click the Continue
button.
4. In the Register an App ID page, provide a Description and set the Bundle ID to
Explicit. Then, enter an App ID in the format com.domainname.myid :
5. In the Register an App ID page, enable your required capabilities under the
Capabilities and App Services tabs:
7. In the Confirm your App ID page, review the information and then click the
Register button. Provided that your App ID successfully registers, you'll be
returned to the Identifiers section of your Apple Developer Account.
9. In the Edit your App ID Configuration page, any of your enabled capabilities that
require additional setup will have a Configure button:
Click any Configure buttons to configure your enabled capabilities. For more
information, see Configure app capabilities on developer.apple.com.
If you intend to distribute your app via the Mac App Store, see Create a
provisioning profile in Publish a Mac Catalyst app for Mac App Store distribution.
If you intend to distribute your app outside the Mac App Store, see Create a
provisioning profile in Publish a Mac Catalyst app for distribution outside the Mac
App Store.
If you intend to distribute your app to a limited number of users on registered
devices, outside the Mac App Store, see Create a provisioning profile in Publish a
Mac Catalyst app for ad-hoc distribution.
1. In Visual Studio for Mac, go to Visual Studio > Preferences > Publishing > Apple
Developer Account.
2. In the Apple Developer Accounts window, select a team and click the View Details...
button.
3. In the Details window, verify that the new profile appears in the Provisioning
Profiles list. You may need to restart Visual Studio for Mac to refresh the list.
4. In the Details dialog, click the Download All Profiles button.
7 Note
You can also download your provisioning profile in Xcode. For more information,
see Download your provisioning profile in Xcode.
Property Value
<CodesignKey> The name of the code signing key. Set to the name of your distribution
certificate, as displayed in Keychain Access.
<CodesignEntitlements> The path to the entitlements file that specifies the entitlements the app
requires. Set to Platforms\MacCatalyst\Entitlements.plist .
<CodesignProvision> The provisioning profile to use when signing the app bundle.
The following example shows a typical property group for building and signing your
Mac Catalyst app for Mac App Store distribution:
XML
<PropertyGroup
Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.
0-maccatalyst|AnyCPU'">
<EnableCodeSigning>True</EnableCodeSigning>
<CodesignProvision>MyMauiApp</CodesignProvision>
<CodesignEntitlements>Platforms\MacCatalyst\Entitlements.plist</CodesignEnti
tlements>
</PropertyGroup>
Troubleshoot
The following list details the common issues that can cause issues when developing a
.NET MAUI Mac Catalyst app that uses capabilities:
Ensure that the correct App ID has been created and registered in the Identifiers
section of your Apple Developer Account.
Ensure that the capability has been added to the App ID and that the capability is
configured using the correct values.
Ensure that the provisioning profile has been installed on your development
machine and that the app's Info.plist file is using a bundle identifier that's identical
to your App ID.
Ensure that the app's Entitlements.plist file has the correct entitlements enabled.
Ensure that the any required privacy keys are set in Info.plist.
Ensure that the app consumes the Entitlements.plist file.
Mac Catalyst entitlements
Article • 03/30/2023 • 11 minutes to read
On Mac Catalyst, .NET Multi-platform App UI (.NET MAUI) apps run in a sandbox that
provides a set of rules that limit access between the app and system resources or user
data. Entitlements are used to request the expansion of the sandbox to give your app
additional capabilities, such as integration with Siri. Any entitlements used by your app
must be specified in the app's Entitlements.plist file. For more information about
entitlements, see Entitlements on developer.apple.com.
In addition to specifying entitlements, the Entitlements.plist file is used to code sign the
app. When code signing your app, the entitlements file is combined with information
from your Apple Developer Account, and other project information to apply a final set
of entitlements to your app.
Entitlements are closely related to the concept of capabilities. They both request the
expansion of the sandbox your app runs in, to give it additional capabilities.
Entitlements are typically added when developing your app, while capabilities are
typically added when code signing your app for distribution. For more information
about capabilities, see Capabilities.
) Important
XML
<plist version="1.0">
<dict>
</dict>
</plist>
Set entitlements
Entitlements can be configured in Visual Studio for Mac by double-clicking the
Entitlements.plist file to open it in the entitlements editor:
1. In Visual Studio for Mac's Solution Window, double-click the Entitlements.plist file
from the Platforms > MacCatalyst folder of your .NET MAUI app project to open it
in the entitlements editor. Then, change from the Source view to the Entitlements
view:
2. In the entitlements editor, select and configure any entitlements required for your
app:
3. Save the changes to your Entitlements.plist file to add the entitlement key/value
pairs to the file.
It may also be necessary to set privacy keys in Info.plist, for certain entitlements.
Consume entitlements
A .NET MAUI Mac Catalyst app must be configured to consume the entitlements defined
in the Entitlements.plist file:
1. In Visual Studio for Mac's Solution Window, right-click on your .NET MAUI app
project and select Properties.
2. In the Project Properties window, select the Build > MacCatalyst > Bundle
Signing tab and click the ... button next to the Custom Entitlements field:
3. In the dialog, navigate to the folder containing your Entitlements.plist file, select
the file, and click the Open button.
4. In the Project Properties window, the Custom Entitlements field will be populated
with your entitlements file:
5. In the Project Properties window, click the OK button to close the window.
) Important
The custom entitlements field must be set separately for each build configuration
for your app.
Key reference
The entitlement key/value pairs are listed below for reference. In Visual Studio they can
be added by editing the Entitlements.plist file as an XML file. In Visual Studio for Mac
they can be added via the Source view of the entitlements editor.
XML
<key>com.apple.developer.networking.wifi-info</key>
<true/>
App Attest
With the App Attest entitlement, you can generate a special cryptographic key on your
device and use it to validate the integrity of your app before a server provides access to
sensitive data.
XML
<key>com.apple.developer.devicecheck.appattest-environment</key>
<string>development</string>
App groups
The app groups entitlement enables your app to access group containers shared among
multiple related apps as well as perform inter-process communication between the
apps.
XML
<key>com.apple.security.application-groups</key>
<array>
<string>group.MyAppGroups</string>
</array>
Apple Pay
The Apple Pay entitlement enables users to easily and securely pay for physical good
and services such as groceries, clothing, tickets, and reservations using payment
information stored on their device.
XML
<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.your.merchantid</string>
</array>
Associated domains
The associated domains entitlement enables your app to be associated with specific
domains for specific services, such as accessing Safari, saved passwords, and activity
continuation.
XML
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:example.com</string>
</array>
XML
<key>com.apple.developer.authentication-services.autofill-credential-
provider</key>
<true/>
ClassKit
The ClassKit entitlement enables your app to privately and securely share student
progress with teachers on assigned activities, such as reading a chapter in a book or
taking a quiz, in school-managed environments.
XML
<key>com.apple.developer.ClassKit-environment</key>
<string>development</string>
XML
Communication notifications
The communication notifications entitlement enables an app to send communication
notifications from a person to a person or multiple people.
XML
<key>com.apple.developer.usernotifications.communication</key>
<true/>
Data protection
The data protection entitlement enables your app to use the built-in encryption on
supported devices. When you specify a file as protected, the system will store the file in
an encrypted format.
XML
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>
XML
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
Family controls
The family controls entitlement enables parental controls in your app, granting access to
the Managed Settings and Device Activity frameworks in the ScreenTime API. Use of
Family controls requires Family Sharing for user enrolment. It prevents removal of your
app and enables on-device content filters from Network Extensions.
XML
<key>com.apple.developer.family-controls</key>
<true/>
XML
<key>com.apple.developer.fileprovider.testing-mode</key>
<true/>
Fonts
The fonts entitlement enables your app, with user permission, to install and use custom
fonts.
XML
<key>com.apple.developer.user-fonts</key>
<array>
<string>system-installation</string>
</array>
Group activities
The group activities entitlement enables an app to communicate with the same app on
one or more other devices, to create a group activity within a FaceTime call. Group
activities on FaceTime let users watch video together, listen to music together, or
perform another synchronous activity.
XML
<key>com.apple.developer.group-session</key>
<true/>
HealthKit
The HealthKit entitlement enables your app to access, with user permission, personal
health information.
XML
<key>com.apple.developer.healthkit</key>
<true/>
HomeKit
The HomeKit entitlement enables your app to interact with HomeKit accessories.
XML
<key>com.apple.developer.homekit</key>
<true/>
Hotspot configuration
The hotspot configuration entitlement entitlement enables your app to configure WiFi
networks.
XML
<key>com.apple.developer.networking.HotspotConfiguration</key>
<true/>
iCloud
The iCloud entitlement enables your app to store data in the cloud, making it possible
for users to share their data across multiple devices.
XML
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.companyname.test</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
XML
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
Inter-app audio
The inter-app audio entitlement enables your app to send and receive audio to/from
other apps that have Inter-app audio enabled.
<key>inter-app-audio</key>
<true/>
) Important
Keychain
The Keychain entitlement enables multiple apps written by the same team to share
passwords.
XML
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.companyname.test</string>
</array>
XML
<key>com.apple.developer.associated-domains.mdm-managed</key>
<true/>
Multipath
The Multipath entitlement enables your app to use multipath protocols such as
Multipath TCP, which will seamlessly handover traffic from one interface to another.
XML
<key>com.apple.developer.networking.multipath</key>
<true/>
XML
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>
For more information, see Near Field Communication Tag Reader Session Formats
Entitlement on developer.apple.com.
Network extensions
The network extensions entitlement enables you to create app extensions that extend
and customize the network capabilities of your device.
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>content-filter-provider</string>
</array>
Personal VPN
The personal VPN entitlement enables your app to use custom VPN connections.
XML
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
Push notifications
The push notifications entitlement enables your app to receive push notifications.
XML
<key>aps-environment</key>
<string>development</string>
Push to talk
The push to talk entitlement enables your app to report Push to Talk channels to the
system so that it can handle transmitting and receiving background audio.
The entitlement is defined using the com.apple.developer.push-to-talk key, of type
Boolean :
XML
<key>com.apple.developer.push-to-talk</key>
<true/>
XML
<key>com.apple.developer.shared-with-you</key>
<true/>
XML
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
Siri
The Siri entitlement enables your app to handle Siri requests.
The entitlement is defined using the com.apple.developer.siri key, of type Boolean :
XML
<key>com.apple.developer.siri</key>
<true/>
XML
<key>com.apple.developer.usernotifications.time-sensitive</key>
<true/>
Wallet
The wallet entitlement enables your app to manage passes, tickets, gift cards, and
loyalty cards. It supports a variety of bar code formats.
XML
<key>com.apple.developer.pass-type-identifiers</key>
<array>
<string>$(TeamIdentifierPrefix)*</string>
</array>
This example will enable your app to allow all pass types. To restrict your app and only
allow a set of team pass types, set the string value to
$(TeamIdentifierPrefix)pass.$(CFBundleIdentifier) where pass.$(CFBundleIdentifier)
WeatherKit
The WeatherKit entitlement enables an app to receive and process current and
forecasted weather information.
XML
<key>com.apple.developer.weatherkit</key>
<true/>
XML
<key>com.apple.external-accessory.wireless-configuration</key>
<true/>
An information property list file is an XML file encoded using Unicode UTF-8 that
contains configuration information for your .NET Multi-platform App UI (.NET MAUI)
app on iOS and Mac Catalyst. The root node of the file is a dictionary, which contains a
set of keys and values that define your app configuration. The name of the information
property list file is Info.plist, and is case sensitive. All .NET MAUI iOS and Mac Catalyst
apps must contain an Info.plist file that describes the app.
.NET MAUI creates Info.plist files for iOS and Mac Catalyst when you create a .NET MAUI
app from a project template. These files are located in the Platforms/iOS and
Platforms/Mac Catalyst folders, and are populated with an initial list of property list keys.
When you build your app, .NET MAUI copies your Info.plist file into the compiled bundle,
before code signing the bundle. During the copy operation, .NET MAUI uses build
properties to perform some variable substitution. It can also insert additional keys
representing configuration that's specified in other ways. Due to this, the information
property list file that ships in your app bundle isn't identical to the source file in your
project.
Visual Studio
Values for the Application Name, Bundle Identifier, Version, and Build
fields are retrieved from your app's project file. For more information, see
Provide app info.
Application name
The application name for a .NET MAUI app is stored in the app's project file as the
ApplicationTitle build property:
Visual Studio
In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab. The Application
Title field lists the application name.
When the value of the Application Title field is updated, the value of the Application
Name field in the application view in the Info.plist file will be automatically updated.
Application ID
The bundle identifier for a .NET MAUI app is stored in the app's project file as the
ApplicationId build property:
Visual Studio
In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab. The Application ID
field lists the bundle identifier.
When the value of the Application ID field is updated, the value of the Bundle Identifier
field in the application view in the Info.plist file will be automatically updated.
Visual Studio
In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab. The Application
Display Version field lists the application display version.
When the value of the Application Display Version field is updated, the value of the
Version field in the application view in the Info.plist file will be automatically updated.
Application version
The application version for a .NET MAUI app is stored in the app's project file as the
ApplicationVersion build property:
Visual Studio
In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the MAUI Shared > General tab. The Application
Version field lists the application version.
When the value of the Application Version field is updated, the value of the Build field
in the application view in the Info.plist file will be automatically updated.
See also
Information Property List on developer.apple.com
Specify the UI idiom for your Mac
Catalyst app
Article • 03/27/2023 • 2 minutes to read
A .NET Multi-platform App UI (.NET MAUI) Mac Catalyst app can run in the iPad or Mac
user interface idiom:
The iPad user interface idiom tells macOS to scale the app's user interface to match
the Mac display environment while preserving iPad-like appearance.
The Mac user interface idiom doesn't scale the app's user interface to match the
Mac display environment. Some controls change their size and appearance, and
interacting with them feels identical to interacting with AppKit controls. For
example, a UIButton appears identical to an NSButton.
By default, .NET MAUI Mac Catalyst apps use the iPad user interface idiom. If this is your
desired behavior, ensure that the app's Info.plist file only specifies 2 as the value of the
UIDeviceFamily key:
XML
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
You might discover that adopting the Mac user interface idiom enhances the user
experience of your app. To do this, update your app's Info.plist file to specify 6 as the
value of the UIDeviceFamily key:
XML
<key>UIDeviceFamily</key>
<array>
<integer>6</integer>
</array>
) Important
The Mac user interface idiom requires macOS 11.0+. Therefore, to use it you'll need
to set the SupportedOSPlatformVersion in your project file to at least 14.0, which is
the Mac Catalyst version equivalent of macOS 11.0.
Adopting the Mac user interface idiom may require you to make additional changes to
your app. For example, if your app uses images sized for iPad or has hard-coded sizes,
you may need to update your app to accommodate the size differences.
C#
#if MACCATALYST
UIKit.UIViewController viewController =
Platform.GetCurrentUIViewController();
if (viewController.TraitCollection.UserInterfaceIdiom ==
UIKit.UIUserInterfaceIdiom.Mac)
else
#endif
See also
Choosing a user interface idiom for your Mac app on developer.apple.com.
Platform integration
Article • 07/01/2022 • 5 minutes to read
Each platform that .NET Multi-platform App UI (.NET MAUI) supports offers unique
operating system and platform APIs that you can access from C#. .NET MAUI provides
cross-platform APIs to access much of this platform functionality, which includes access
to sensors, accessing information about the device an app is running on, checking
network connectivity, storing data securely, and initiating browser-based authentication
flows.
.NET MAUI separates these cross-platform APIs into different areas of functionality.
Application model
.NET MAUI provides the following functionality in the Microsoft.Maui.ApplicationModel
namespace:
Functionality Description
App actions The AppActions class enables you to create and respond to app shortcuts, which
provide additional ways of starting your app. For more information, see App
actions.
App The AppInfo class provides access to basic app information, which includes the
information app name and version, and the current active theme for the device. For more
information, see App information.
Browser The Browser class enables an app to open a web link in an in-app browser, or the
system browser. For more information, see Browser.
Launcher The Launcher class enables an app to open a URI, and is often used when deep
linking into another app's custom URI schemes. For more information, see
Launcher.
Main thread The MainThread class enables you to run code on the UI thread. For more
information, see Main thread.
Maps The Map class enables an app to open the system map app to a specific location
or place mark. For more information, see Maps.
Permissions The Permissions class enables you to check and request permissions at run-time.
For more information, see Permissions.
Functionality Description
Version The VersionTracking class enables you to check the app's version and build
tracking numbers, and determine if it's the first time the app has been launched. For more
information, see Version tracking.
Communication
.NET MAUI provides the following functionality in the
Microsoft.Maui.ApplicationModel.Communication namespace:
Functionality Description
Contacts The Contacts class enables an app to select a contact and read information
about it. For more information, see Contacts.
Email The Email class can be used to open the default email app, and can create a new
email with the specified recipients, subject, and body. For more information, see
Email.
Phone dialer The PhoneDialer class enables an app to open a phone number in the dialer. For
more information, see Phone dialer.
SMS The Sms class can be used to open the default SMS app and preload it with a
(messaging) recipient and message. For more information, see SMS.
Device features
.NET MAUI provides the following functionality in the Microsoft.Maui.Devices
namespace:
Functionality Description
Battery The Battery class enables an app to check the device's battery information, and
monitor the battery for changes. For more information, see Battery.
Functionality Description
Device The DeviceDisplay class enables an app to read information about the device's
display screen metrics. For more information, see Device display.
Device The DeviceInfo class enables an app to read information about the device the
information app is running on. For more information, see Device information.
Flashlight The FlashLight class can toggle the device's camera flash on and off, to emulate
a flashlight. For more information, see Flashlight.
Vibration The Vibration class enables you to start and stop the vibrate functionality for a
desired amount of time. For more information, see Vibration.
Media
.NET MAUI provides the following functionality in the Microsoft.Maui.Media namespace:
Functionality Description
Media picker The MediaPicker class enables you to prompt the user to pick or take a photo or
video on the device. For more information, see Media picker.
Screenshot The Screenshot class enables you to capture the current displayed screen of the
app. For more information, see Screenshot.
Text-to- The TextToSpeech class enables an app to utilize the built-in text-to-speech
speech engines to speak text from the device. For more information, see Text-to-Speech.
Unit The UnitConverters class provides unit converters to help you convert from one
converters unit of measurement to another. For more information, see Unit converters.
Sharing
.NET MAUI provides the following functionality in the
Microsoft.Maui.ApplicationModel.DataTransfer namespace:
Functionality Description
Clipboard The Clipboard class enables an app copy and paste text to and from the system
clipboard. For more information, see Clipboard.
Share files The Share class provides an API to send data, such as text or web links, to the
and text device's share function. For more information, see Share.
Storage
.NET MAUI provides the following functionality in the Microsoft.Maui.Storage
namespace:
Functionality Description
File picker The FilePicker class enables you to prompt the user to pick one or more files
from the device. For more information, see File picker.
File system The FileSystem class provides helper methods that access the app's cache and
helpers data folders, and helps access files that are stored in the app package. For more
information, see File system helpers.
Preferences The Preferences class helps to store app preferences in a key/value store. For
more information, see Preferences.
Secure The SecureStorage class helps to securely store simple key/value pairs. For more
storage information, see Secure storage.
In situations where .NET MAUI doesn't provide any APIs for accessing specific platform
APIs, you can write your own code to access the required platform APIs. For more
information, see Invoke platform code.
App actions
Article • 03/27/2023 • 3 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IAppActions interface, which lets you create and respond to app shortcuts. App
shortcuts are helpful to users because they allow you, as the app developer, to present
them with extra ways of starting your app. For example, if you were developing an email
and calendar app, you could present two different app actions, one to open the app
directly to the current day of the calendar, and another to open to the email inbox
folder.
Get started
To access the AppActions functionality, the following platform specific setup is required.
Windows
No setup is required.
Create actions
App actions can be created at any time, but are often created when an app starts. To
configure app actions, invoke the ConfigureEssentials(MauiAppBuilder,
Action<IEssentialsBuilder>) method on the MauiAppBuilder object in the
MauiProgram.cs file. There are two methods you must call on the IEssentialsBuilder
object to enable an app action:
1. AddAppAction
This method creates an action. It takes an id string to uniquely identify the action,
and a title string that's displayed to the user. You can optionally provide a
subtitle and an icon.
2. OnAppAction
The delegate passed to this method is called when the user invokes an app action,
provided the app action instance. Check the Id property of the action to
determine which app action was started by the user.
The following code demonstrates how to configure the app actions at app startup:
C#
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureEssentials(essentials =>
essentials
.OnAppAction(App.HandleAppActions);
});
return builder.Build();
Responding to actions
After app actions have been configured, the OnAppAction method is called for all app
actions invoked by the user. Use the Id property to differentiate them. The following
code demonstrates handling an app action:
C#
App.Current.Dispatcher.Dispatch(async () =>
_ => default(Page)
};
if (page != null)
await Application.Current.MainPage.Navigation.PopToRootAsync();
await Application.Current.MainPage.Navigation.PushAsync(page);
});
C#
if (AppActions.Current.IsSupported)
new AppAction("battery_info",
"Battery Info") });
Use the AppAction(String, String, String, String) constructor to set the following aspects
of an app action:
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IAppInfo interface, which provides information about your application.
C#
Unspecified
Light
Dark
Unspecified is returned when the operating system doesn't have a specific user
interface style. An example of this is on devices running versions of iOS older than 13.0.
The following code example demonstrates reading the theme:
C#
_ => "Unknown"
};
C#
AppInfo.Current.ShowSettingsUI();
This settings page allows the user to change application permissions and perform other
platform-specific tasks.
Windows
App information is taken from the Package.appxmanifest for the following fields:
BuildString — Uses the Build from the Version on the Identity node
Name — DisplayName on the Properties node
PackageName — Name on the Identity node
VersionString — Version on the Identity node
Requested theme
Code that accesses the IAppInfo.RequestedTheme property must be called on the UI
thread or an exception will be thrown.
Windows applications respect the RequestedTheme property setting in the Windows
App.xaml. If it's set to a specific theme, this API always returns this setting. To use
the dynamic theme of the OS, remove this property from your application. When
your app is run, it returns the theme set by the user in Windows settings: Settings >
Personalization > Colors > Choose your default app mode.
Browser
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IBrowser interface. This interface enables an application to open a web link in the
system-preferred browser or the external browser.
Get started
To access the browser functionality, the following platform-specific setup is required.
Windows
No setup is required.
C#
try
await Browser.Default.OpenAsync(uri,
BrowserLaunchMode.SystemPreferred);
This method returns after the browser is launched, not after it was closed by the user.
Browser.OpenAsync returns a bool value to indicate if the browser was successfully
launched.
Customization
If you're using the system-preferred browser, there are several customization options
available for iOS and Android. These options include a TitleMode (Android only) and
preferred color for the Toolbar (iOS and Android) and Controls (iOS only) that appear.
C#
try
LaunchMode = BrowserLaunchMode.SystemPreferred,
TitleMode = BrowserTitleMode.Show,
PreferredToolbarColor = Colors.Violet,
PreferredControlColor = Colors.SandyBrown
};
Platform differences
This section describes the platform-specific differences with the browser API.
Windows
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
ILauncher interface. This interface enables an application to open a URI by the system.
This way of opening an application is often used when deep linking into another
application's custom URI schemes.
) Important
Get started
To access the launcher functionality, the following platform-specific setup is required.
Windows
No setup is required.
C#
if (supportsUri)
await Launcher.Default.OpenAsync("lyft://ridetype?id=lyft_line");
The previous code example can be simplified by using the TryOpenAsync(Uri), which
checks if the URI scheme can be opened, before opening it:
C#
if (launcherOpened)
// Do something fun
The following code example writes text to a file, and opens the text file with the
launcher:
C#
) Important
C#
Title = Title,
: Rect.Zero
});
C#
: Rect.Zero
});
C#
looper = looper.Parent;
if (looper is Microsoft.Maui.Controls.View v)
C#
try
PresentationSourceBounds = element.GetAbsoluteBounds(),
Title = "Title",
Text = "Text"
});
catch (Exception)
You can pass in the calling element when the Command is triggered:
XML
<Button Text="Share"
Command="{Binding ShareWithFriendsCommand}"
For an example of the ViewHelpers class, see the .NET MAUI Sample hosted on
GitHub .
Platform differences
This section describes the platform-specific differences with the launcher API.
Windows
No platform differences.
Create a thread on the .NET MAUI UI thread
Article • 02/03/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) MainThread class
to run code on the main UI thread. Most operating systems use a single-threading model for code
involving the user interface. This model is necessary to properly serialize user-interface events,
including keystrokes and touch input. This thread is often called the main thread, the user-interface
thread, or the UI thread. The disadvantage of this model is that all code that accesses user interface
elements must run on the application's main thread.
When is it required
Applications sometimes need to use events that call the event handler on a secondary thread, such as
the Accelerometer or Compass sensors. All sensors might return information on a secondary thread
when used with faster sensing speeds. If the event handler needs to access user-interface elements, it
must invoke code on the main thread.
C#
MainThread.BeginInvokeOnMainThread(() =>
});
It is also possible to define a separate method for the code, and then call that code with the
BeginInvokeOnMainThread method:
C#
void MyMainThreadCode()
{
MainThread.BeginInvokeOnMainThread(MyMainThreadCode);
C#
if (MainThread.IsMainThread)
MyMainThreadCode();
else
MainThread.BeginInvokeOnMainThread(MyMainThreadCode);
This check isn't necessary. BeginInvokeOnMainThread itself tests if the current code is running on the
main thread or not. If the code is running on the main thread, BeginInvokeOnMainThread just calls the
provided method directly. If the code is running on a secondary thread, BeginInvokeOnMainThread
invokes the provided method on the main thread. Therefore, if the code you run is the same,
regardless of the main or secondary thread, simply call BeginInvokeOnMainThread without checking if
it's required. There is negligible overhead in doing so.
The only reason you would need to check the IsMainThread property is if you have branching logic
that does something different based on the thread.
Additional methods
The MainThread class includes the following additional static methods that can be used to interact
with user interface elements from backgrounds threads:
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) IMap
interface. This interface enables an application to open the installed map application to
a specific location or place mark.
The default implementation of the IMap interface is available through the Map.Default
property. Both the IMap interface and Map class are contained in the
Microsoft.Maui.ApplicationModel namespace.
Get started
To access the browser functionality, the following platform-specific setup is required.
Windows
No setup is required.
C#
try
Tip
When you use a Placemark to open the map, more information is required. The
information helps the map app search for the place you're looking for. The following
information is required:
CountryName
AdminArea
Thoroughfare
Locality
C#
AdminArea = "WA",
Locality = "Redmond"
};
try
Extension methods
As long as the Microsoft.Maui.Devices.Sensors namespace is imported, which a new
.NET MAUI project automatically does, you can use the built-in extension method
OpenMapsAsync to open the map:
C#
AdminArea = "WA",
Locality = "Redmond"
};
try
await placemark.OpenMapsAsync(options);
Add navigation
When you open the map, you can calculate a route from the device's current location to
the specified location. Pass the MapLaunchOptions type to the Map.OpenAsync method,
specifying the navigation mode. The following example opens the map app and
specifies a driving navigation mode:
C#
NavigationMode =
NavigationMode.Driving };
try
Platform differences
This section describes the platform-specific differences with the maps API.
Windows
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
Permissions class. This class allows you to check and request permissions at run-time.
The Permissions type is available in the Microsoft.Maui.ApplicationModel namespace.
Available permissions
.NET MAUI attempts to abstract as many permissions as possible. However, each
operating system has a different set of permissions. Even though the API allows access
to a common permission, there may be differences between operating systems related
to that permission. The following table describes the available permissions:
The following table uses ✔️to indicate that the permission is supported and ❌ to
indicate the permission isn't supported or isn't required:
CalendarRead ✔️ ✔️ ❌ ❌
CalendarWrite ✔️ ✔️ ❌ ❌
Camera ✔️ ✔️ ❌ ❌
ContactsRead ✔️ ✔️ ❌ ❌
ContactsWrite ✔️ ✔️ ❌ ❌
Flashlight ✔️ ❌ ❌ ❌
LocationWhenInUse ✔️ ✔️ ❌ ✔️
LocationAlways ✔️ ✔️ ❌ ❌
Media ❌ ✔️ ❌ ❌
Microphone ✔️ ✔️ ❌ ❌
Phone ✔️ ✔️ ❌ ❌
Photos ❌ ✔️ ❌ ✔️
Reminders ❌ ✔️ ❌ ❌
Sensors ✔️ ✔️ ❌ ❌
Permission Android iOS Windows tvOS
Sms ✔️ ✔️ ❌ ❌
Speech ✔️ ✔️ ❌ ❌
StorageRead ✔️ ❌ ❌ ❌
StorageWrite ✔️ ❌ ❌ ❌
Checking permissions
To check the current status of a permission, use the Permissions.CheckStatusAsync
method along with the specific permission to get the status for. The following example
checks the status of the LocationWhenInUse permission:
C#
It's best to check the status of the permission before requesting it. Each operating
system returns a different default state, if the user has never been prompted. iOS returns
Unknown, while others return Denied. If the status is Granted then there's no need to
make other calls. On iOS if the status is Denied you should prompt the user to change
the permission in the settings. On Android, you can call ShouldShowRationale to detect
if the user has already denied the permission in the past.
Permission status
When using CheckStatusAsync or RequestAsync, a PermissionStatus is returned that can
be used to determine the next steps:
Unknown
The permission is in an unknown state, or on iOS, the user has never been
prompted.
Denied
Granted
Restricted
In a restricted state.
Limited
Requesting permissions
To request a permission from the users, use the RequestAsync method along with the
specific permission to request. If the user previously granted permission, and hasn't
revoked it, then this method will return Granted without showing a dialog to the user.
Permissions shouldn't be requested from your MauiProgram or App class, and should
only be requested once the first page of the app has appeared.
C#
) Important
Example
The following code presents the general usage pattern for determining whether a
permission has been granted, and then requesting it if it hasn't.
C#
if (status == PermissionStatus.Granted)
return status;
return status;
if (Permissions.ShouldShowRationale<Permissions.LocationWhenInUse>())
return status;
Extending permissions
The Permissions API was created to be flexible and extensible for applications that
require more validation or permissions that aren't included in .NET MAUI. Create a class
that inherits from Permissions.BasePermission, and implement the required abstract
methods. The following example code demonstrates the basic abstract members, but
without implementation:
C#
// Indicates that the requestor should prompt the user as to why the app
requires the permission, because the
C#
(global::Android.Manifest.Permission.ReadExternalStorage, true),
(global::Android.Manifest.Permission.WriteExternalStorage, true)
}.ToArray();
You then check the permission in the same way as any other permission type provided
by .NET MAUI:
C#
Platform differences
This section describes the platform-specific differences with the permissions API.
Windows
No platform differences.
Version tracking
Article • 02/03/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IVersionTracking interface. This interface lets you check the applications version and
build numbers along with seeing additional information such as if it's the first time the
application launched.
namespace.
Get started
To enable version tracking in your app, invoke the ConfigureEssentials method on the
MauiAppBuilder object in the MauiProgram.cs file. Then, on the IEssentialsBuilder object,
call the UseVersionTracking() method:
C#
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureEssentials(essentials =>
essentials.UseVersionTracking();
});
return builder.Build();
C#
labelIsFirst.Text =
VersionTracking.Default.IsFirstLaunchEver.ToString();
labelCurrentVersionIsFirst.Text =
VersionTracking.Default.IsFirstLaunchForCurrentVersion.ToString();
labelCurrentBuildIsFirst.Text =
VersionTracking.Default.IsFirstLaunchForCurrentBuild.ToString();
labelCurrentVersion.Text =
VersionTracking.Default.CurrentVersion.ToString();
labelCurrentBuild.Text =
VersionTracking.Default.CurrentBuild.ToString();
labelFirstInstalledVer.Text =
VersionTracking.Default.FirstInstalledVersion.ToString();
labelFirstInstalledBuild.Text =
VersionTracking.Default.FirstInstalledBuild.ToString();
labelVersionHistory.Text = String.Join(',',
VersionTracking.Default.VersionHistory);
labelBuildHistory.Text = String.Join(',',
VersionTracking.Default.BuildHistory);
labelPreviousVersion.Text =
VersionTracking.Default.PreviousVersion?.ToString() ?? "none";
labelPreviousBuild.Text =
VersionTracking.Default.PreviousBuild?.ToString() ?? "none";
The first time the app is run after version tracking is enabled, the IsFirstLaunchEver
property will return true . If you add version tracking in a newer version of an already
released app, IsFirstLaunchEver may incorrectly report true . This property always
returns true the first time version tracking is enabled and the user runs the app. You
can't fully rely on this property if users have upgraded from older versions that weren't
tracking the version.
Platform differences
All version information is stored using the Preferences API, and is stored with a filename
of [YOUR-APP-PACKAGE-ID].microsoft.maui.essentials.versiontracking and follows the
same data persistence outlined in the Preferences documentation.
Contacts
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IContacts interface to select a contact and read information about it.
The default implementation of the IContacts interface is available through the Default
property. Both the IContacts interface and Contacts class are contained in the
Microsoft.Maui.ApplicationModel.Communication namespace.
) Important
Because of a namespace conflict, the Contacts type must be fully qualified when
targeting iOS or macOS: Microsoft.Maui.ApplicationModel.Communication.Contacts . New
projects automatically target these platforms, along with Android and Windows.
To write code that will compile for iOS and macOS, fully qualify the Contacts type.
Alternatively, provide a using directive to map the Communication namespace:
C#
Get started
To access the Contacts functionality the following platform-specific setup is required.
Windows
Pick a contact
You can request the user to pick a contact by calling the PickContactAsync() method. A
contact dialog will appear on the device allowing the user to select a contact. If the user
doesn't select a contact, null is returned.
C#
try
if (contact == null)
return;
string id = contact.Id;
C#
// No contacts
if (contacts == null)
yield break;
Platform differences
This section describes the platform-specific differences with the contacts API.
Windows
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IEmail interface to open the default email app. When the email app is loaded, it can be
set to create a new email with the specified recipients, subject, and body.
Get started
To access the email functionality, the following platform specific setup is required.
Windows
No setup is required.
Using Email
The Email functionality works by providing the email information as an argument to the
ComposeAsync method. In this example, the EmailMessage type is used to represent the
email information:
C#
if (Email.Default.IsComposeSupported)
Subject = subject,
Body = body,
BodyFormat = EmailBodyFormat.PlainText,
To = new List<string>(recipients)
};
await Email.Default.ComposeAsync(message);
File attachments
When creating the email provided to the email client, you can add file attachments. The
file type (MIME) is automatically detected, so you don't need to specify it. Some mail
clients may restrict the types of files you send, or possibly prevent attachments
altogether.
The following example demonstrates adding an image file to the email attachments.
C#
if (Email.Default.IsComposeSupported)
string body = "It was great to see you last weekend. I've attached a
photo of our adventures together.";
Subject = subject,
Body = body,
BodyFormat = EmailBodyFormat.PlainText,
To = new List<string>(recipients)
};
message.Attachments.Add(new EmailAttachment(picturePath));
await Email.Default.ComposeAsync(message);
Platform Differences
Windows
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IConnectivity interface to inspect the network accessibility of the device. The network
connection may have access to the internet. Devices also contain different kinds of
network connections, such as Bluetooth, cellular, or WiFi. The IConnectivity interface
has an event to monitor changes in the devices connection state.
Get started
To access the Connectivity functionality, the following platform-specific setup is
required.
Windows
No setup is required.
Using Connectivity
You can determine the scope of the current network by checking the NetworkAccess
property.
C#
if (accessType == NetworkAccess.Internet)
You can check what type of connection profile the device is actively using:
C#
IEnumerable<ConnectionProfile> profiles =
Connectivity.Current.ConnectionProfiles;
if (profiles.Contains(ConnectionProfile.WiFi))
C#
Connectivity.ConnectivityChanged +=
Connectivity_ConnectivityChanged;
~ConnectivityTest() =>
Connectivity.ConnectivityChanged -=
Connectivity_ConnectivityChanged;
if (e.NetworkAccess == NetworkAccess.ConstrainedInternet)
switch (item)
case ConnectionProfile.Bluetooth:
Console.Write("Bluetooth");
break;
case ConnectionProfile.Cellular:
Console.Write("Cell");
break;
case ConnectionProfile.Ethernet:
Console.Write("Ethernet");
break;
case ConnectionProfile.WiFi:
Console.Write("WiFi");
break;
default:
break;
Console.WriteLine();
Limitations
It's important to know that it's possible that Internet is reported by NetworkAccess but
full access to the web isn't available. Because of how connectivity works on each
platform, it can only guarantee that a connection is available. For instance, the device
may be connected to a Wi-Fi network, but the router is disconnected from the internet.
In this instance Internet may be reported, but an active connection isn't available.
Phone dialer
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IPhoneDialer interface. This interface enables an application to open a phone number in
the dialer.
Get started
To access the phone dialer functionality, the following platform-specific setup is
required.
Windows
No setup is required.
C#
if (PhoneDialer.Default.IsSupported)
PhoneDialer.Default.Open("000-000-0000");
SMS
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) ISms
interface to open the default SMS app and preload it with a message and recipient.
The default implementation of the ISms interface is available through the Sms.Default
property. Both the ISms interface and Sms class are contained in the
Microsoft.Maui.ApplicationModel.Communication namespace.
Get started
To access the SMS functionality, the following platform specific setup is required.
Windows
No setup is required.
Create a message
The SMS functionality works by creating a new SmsMessage object, and calling the
ComposeAsync method. You can optionally include a message and zero or more
recipients.
C#
if (Sms.Default.IsComposeSupported)
await Sms.Default.ComposeAsync(message);
Web authenticator
Article • 03/27/2023 • 7 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) the
IWebAuthenticator interface. This interface lets you start browser-based authentication
flows, which listen for a callback to a specific URL registered to the app.
Overview
Many apps require adding user authentication, and this often means enabling your
users to sign in to their existing Microsoft, Facebook, Google, or Apple Sign In account.
Tip
If you're interested in using your own web service for authentication, it's possible to use
WebAuthenticator to implement the client-side functionality.
The best practice here's to use a web backend as a middle layer between your mobile
app and the authentication provider.
) Important
Get started
To access the WebAuthenticator functionality the following platform-specific setup is
required.
Windows
U Caution
For WinUI 3, you'll need to declare your callback URI protocol in your
Package.appxmanifest file:
XML
<Applications>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="myapp">
<uap:DisplayName>My App</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
</Extensions>
</Application>
</Applications>
Using WebAuthenticator
The API consists mainly of a single method, AuthenticateAsync, which takes two
parameters:
C#
try
new Uri("https://mysite.com/mobileauth/Microsoft"),
new Uri("myapp://"));
catch (TaskCanceledException e)
The WebAuthenticator API takes care of launching the url in the browser and waiting
until the callback is received:
try
new WebAuthenticatorOptions()
PrefersEphemeralWebBrowserSession = true
});
catch (TaskCanceledException e)
Platform differences
This section describes the platform-specific differences with the web authentication API.
Windows
U Caution
Apple Sign In
According to Apple's review guidelines , if your app uses any social login service to
authenticate, it must also offer Apple Sign In as an option. To add Apple Sign In to your
apps, first you'll need to configure your app to use Apple Sign In.
For iOS 13 and higher, call the AppleSignInAuthenticator.AuthenticateAsync method.
This uses the native Apple Sign in APIs so your users get the best experience possible on
these devices. For example, you can write your shared code to use the correct API at
runtime:
C#
if (scheme.Equals("Apple")
else
Tip
For non-iOS 13 devices, this will start the web authentication flow, which can also
be used to enable Apple Sign In on your Android and Windows devices.
You can
sign into your iCloud account on your iOS simulator to test Apple Sign In.
ASP.NET core server back end
It's possible to use the WebAuthenticator API with any web back-end service. To use it
with an ASP.NET core app, configure the web app with the following steps:
1. Set up your external social authentication providers in an ASP.NET Core web app.
2. Set the Default Authentication Scheme to
CookieAuthenticationDefaults.AuthenticationScheme in your
.AddAuthentication() call.
C#
services.AddAuthentication(o =>
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddFacebook(fb =>
fb.AppId = Configuration["FacebookAppId"];
fb.AppSecret = Configuration["FacebookAppSecret"];
fb.SaveTokens = true;
});
Tip
If you'd like to include Apple Sign In, you can use the AspNet.Security.OAuth.Apple
NuGet package. You can view the full Startup.cs sample .
C#
[Route("mobileauth")]
[ApiController]
The purpose of this controller is to infer the scheme (provider) the app is requesting,
and start the authentication flow with the social provider. When the provider calls back
to the web backend, the controller parses out the result and redirects to the app's
callback URI with parameters.
Sometimes you may want to return data such as the provider's access_token back to
the app, which you can do via the callback URI's query parameters. Or, you may want to
instead create your own identity on your server and pass back your own token to the
app. What and how you do this part is up to you!
7 Note
The above sample demonstrates how to return the access token from the 3rd party
authentication (ie: OAuth) provider. To obtain a token you can use to authorize web
requests to the web backend itself, you should create your own token in your web
app, and return that instead. The Overview of ASP.NET Core authentication has
more information about advanced authentication scenarios in ASP.NET Core.
Battery
Article • 03/27/2023 • 3 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IBattery interface to check the device's battery information and monitor for changes.
This interface also provides information about the device's energy-saver status, which
indicates if the device is running in a low-power mode.
Get started
To access the Battery functionality the following platform-specific setup is required.
Windows
No setup is required.
The BatteryInfoChanged event is also available, and is raised when the state of the
battery changes. The following example demonstrates how to use the monitor the
BatteryInfoChanged event and report the battery status to Label controls:
C#
WatchBattery();
if (!_isBatteryWatched)
Battery.Default.BatteryInfoChanged += Battery_BatteryInfoChanged;
else
Battery.Default.BatteryInfoChanged -= Battery_BatteryInfoChanged;
_isBatteryWatched = !_isBatteryWatched;
) Important
The energy-saver status of the device can be read by accessing the EnergySaverStatus
property, which is either On, Off, or Unknown. If the status is On , the application should
avoid background processing or other activities that may consume a lot of power.
The battery will raise the EnergySaverStatusChanged event when the battery enters or
leaves energy-saver mode.
You can also obtain the current energy-saver status of the
device using the EnergySaverStatus property:
The following code example monitors the energy-saver status and sets a property
accordingly.
C#
_isBatteryLow = Battery.Default.EnergySaverStatus ==
EnergySaverStatus.On;
BatterySaverLabel.Text = _isBatteryLow.ToString();
Battery.Default.EnergySaverStatusChanged +=
Battery_EnergySaverStatusChanged;
}
_isBatteryLow = Battery.Default.EnergySaverStatus ==
EnergySaverStatus.On;
BatterySaverLabel.Text = _isBatteryLow.ToString();
Power source
The PowerSource property returns a BatteryPowerSource enumeration that indicates
how the device is being charged, if at all. If it's not being charged, the status will be
BatteryPowerSource.Battery. The BatteryPowerSource.AC, BatteryPowerSource.Usb, and
BatteryPowerSource.Wireless values indicate that the battery is being charged.
The following code example sets the text of a Label control based on power source.
C#
_ => "Unknown"
};
Platform differences
This section describes the platform-specific differences with the battery.
Windows
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IDeviceDisplay interface to read information about the device's screen metrics. This
interface can be used to request the screen stays awake while the app is running.
C#
sb.AppendLine($"Pixel width:
{DeviceDisplay.Current.MainDisplayInfo.Width} / Pixel Height:
{DeviceDisplay.Current.MainDisplayInfo.Height}");
sb.AppendLine($"Density:
{DeviceDisplay.Current.MainDisplayInfo.Density}");
sb.AppendLine($"Orientation:
{DeviceDisplay.Current.MainDisplayInfo.Orientation}");
sb.AppendLine($"Rotation:
{DeviceDisplay.Current.MainDisplayInfo.Rotation}");
sb.AppendLine($"Refresh Rate:
{DeviceDisplay.Current.MainDisplayInfo.RefreshRate}");
DisplayDetailsLabel.Text = sb.ToString();
C#
DeviceDisplay.Current.KeepScreenOn = AlwaysOnSwitch.IsToggled;
Platform differences
This section describes the platform-specific differences with the device display.
Windows
No platform differences.
Device information
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IDeviceInfo interface to read information about the device the app is running on.
C#
sb.AppendLine($"Model: {DeviceInfo.Current.Model}");
sb.AppendLine($"Manufacturer: {DeviceInfo.Current.Manufacturer}");
sb.AppendLine($"Name: {DeviceInfo.Current.Name}");
sb.AppendLine($"Idiom: {DeviceInfo.Current.Idiom}");
sb.AppendLine($"Platform: {DeviceInfo.Current.Platform}");
_ => false
};
DisplayDeviceLabel.Text = sb.ToString();
DevicePlatform.Android
DevicePlatform.iOS
DevicePlatform.macOS
DevicePlatform.MacCatalyst
DevicePlatform.tvOS
DevicePlatform.Tizen
DevicePlatform.WinUI
DevicePlatform.watchOS
DevicePlatform.Unknown
C#
DeviceInfo.Current.Platform == DevicePlatform.Android;
DeviceIdiom.Phone
DeviceIdiom.Tablet
DeviceIdiom.Desktop
DeviceIdiom.TV
DeviceIdiom.Watch
DeviceIdiom.Unknown
C#
if (DeviceInfo.Current.Idiom == DeviceIdiom.Desktop)
Device type
IDeviceInfo.DeviceType property an enumeration to determine if the application is
running on a physical or virtual device. A virtual device is a simulator or emulator.
C#
_ => false
};
Platform differences
This section describes the platform-specific differences with the device information.
Windows
No platform differences.
Accessing device sensors
Article • 03/27/2023 • 12 minutes to read
Devices have all sorts of sensors that are available to you. Some sensors can detect
movement, others changes in the environment, such as light. Monitoring and reacting to
these sensors makes your app dynamic in adapting to how the device is being used. You
can also respond to changes in the sensors and alert the user. This article gives you a
brief overview of the common sensors supported by .NET Multi-platform App UI (.NET
MAUI).
Sensor speed
Sensor speed sets the speed in which a sensor will return data to your app. When you
start a sensor, you provide the desired sensor speed with the SensorSpeed enumeration.
Fastest
Get the sensor data as fast as possible (not guaranteed to return on UI thread).
Game
Default
UI
2 Warning
Monitoring too many sensors at once may affect the rate sensor data is returned to
your app.
Accelerometer
The accelerometer sensor measures the acceleration of the device along its three axes.
The data reported by the sensor represents how the user is moving the device.
The IAccelerometer interface provides access to the sensor, and is available through the
Accelerometer.Default property. Both the IAccelerometer interface and Accelerometer
class are contained in the Microsoft.Maui.Devices.Sensors namespace.
To start monitoring the accelerometer sensor, call the IAccelerometer.Start method. .NET
MAUI sends accelerometer data changes to your app by raising the
IAccelerometer.ReadingChanged event. Use the IAccelerometer.Stop method to stop
monitoring the sensor. You can detect the monitoring state of the accelerometer with
the IAccelerometer.IsMonitoring property, which will be true if the accelerometer was
started and is currently being monitored.
The following code example demonstrates monitoring the accelerometer for changes:
C#
if (Accelerometer.Default.IsSupported)
if (!Accelerometer.Default.IsMonitoring)
// Turn on accelerometer
Accelerometer.Default.ReadingChanged +=
Accelerometer_ReadingChanged;
Accelerometer.Default.Start(SensorSpeed.UI);
else
Accelerometer.Default.Stop();
Accelerometer.Default.ReadingChanged -=
Accelerometer_ReadingChanged;
AccelLabel.TextColor = Colors.Green;
The coordinate-system is defined relative to the screen of the device in its default
orientation. The axes aren't swapped when the device's screen orientation changes.
The X axis is horizontal and points to the right, the Y axis is vertical and points up and
the Z axis points towards the outside of the front face of the screen. In this system,
coordinates behind the screen have negative Z values.
Examples:
When the device lies flat on a table and is pushed on its left side toward the right,
the X acceleration value is positive.
When the device lies flat on a table, the acceleration value is +1.00 G or
( + 9.81m / s 2), which corresponds to the acceleration of the device (0m / s 2) minus
the force of gravity ( − 9.81m / s 2) and normalized as in G.
When the device lies flat on a table and is pushed toward the sky with an
acceleration of A m / s 2, the acceleration value is equal to A + 9.81 which
corresponds to the acceleration of the device ( + Am / s 2) minus the force of gravity
( − 9.81m / s 2) and normalized in G.
Barometer
The barometer sensor measures the ambient air pressure. The data reported by the
sensor represents the current air pressure. This data is reported the first time you start
monitoring the sensor and then each time the pressure changes.
The IBarometer interface provides access to the sensor, and is available through the
Barometer.Default property. Both the IBarometer interface and Barometer class are
contained in the Microsoft.Maui.Devices.Sensors namespace.
To start monitoring the barometer sensor, call the IBarometer.Start method. .NET MAUI
sends air pressure readings to your app by raising the IBarometer.ReadingChanged
event. Use the IBarometer.Stop method to stop monitoring the sensor. You can detect
the monitoring state of the barometer with the IBarometer.IsMonitoring property, which
will be true if the barometer is currently being monitored.
The following code example demonstrates monitoring the barometer for changes:
C#
if (Barometer.Default.IsSupported)
if (!Barometer.Default.IsMonitoring)
// Turn on barometer
Barometer.Default.ReadingChanged += Barometer_ReadingChanged;
Barometer.Default.Start(SensorSpeed.UI);
else
Barometer.Default.ReadingChanged -= Barometer_ReadingChanged;
BarometerLabel.TextColor = Colors.Green;
Windows
The ICompass interface provides access to the sensor, and is available through the
Compass.Default property. Both the ICompass interface and Compass class are contained
in the Microsoft.Maui.Devices.Sensors namespace.
To start monitoring the compass sensor, call the ICompass.Start method. .NET MAUI
raises the ICompass.ReadingChanged event when the compass heading changes. Use
the ICompass.Stop method to stop monitoring the sensor. You can detect the
monitoring state of the compass with the ICompass.IsMonitoring property, which will be
true if the compass is currently being monitored.
The following code example demonstrates monitoring the compass for changes:
C#
if (Compass.Default.IsSupported)
if (!Compass.Default.IsMonitoring)
// Turn on compass
Compass.Default.ReadingChanged += Compass_ReadingChanged;
Compass.Default.Start(SensorSpeed.UI);
else
Compass.Default.Stop();
Compass.Default.ReadingChanged -= Compass_ReadingChanged;
CompassLabel.TextColor = Colors.Green;
Windows
Shake
Even though this article is listing shake as a sensor, it isn't. The accelerometer is used to
detect when the device is shaken.
The IAccelerometer interface provides access to the sensor, and is available through the
Accelerometer.Default property. Both the IAccelerometer interface and Accelerometer
class are contained in the Microsoft.Maui.Devices.Sensors namespace.
The detect shake API uses raw readings from the accelerometer to calculate
acceleration. It uses a simple queue mechanism to detect if 3/4ths of the recent
accelerometer events occurred in the last half second. Acceleration is calculated by
adding the square of the X, Y, and Z (x 2 + y 2 + z 2) readings from the accelerometer and
comparing it to a specific threshold.
The following code example demonstrates monitoring the accelerometer for the
ShakeDetected event:
C#
if (Accelerometer.Default.IsSupported)
if (!Accelerometer.Default.IsMonitoring)
// Turn on accelerometer
Accelerometer.Default.ShakeDetected +=
Accelerometer_ShakeDetected;
Accelerometer.Default.Start(SensorSpeed.Game);
else
Accelerometer.Default.Stop();
Accelerometer.Default.ShakeDetected -=
Accelerometer_ShakeDetected;
Gyroscope
The gyroscope sensor measures the angular rotation speed around the device's three
primary axes.
The IGyroscope interface provides access to the sensor, and is available through the
Gyroscope.Default property. Both the IGyroscope interface and Gyroscope class are
contained in the Microsoft.Maui.Devices.Sensors namespace.
To start monitoring the gyroscope sensor, call the IGyroscope.Start method. .NET MAUI
sends gyroscope data changes to your app by raising the IGyroscope.ReadingChanged
event. The data provided by this event is measured in rad/s (radian per second). Use the
IGyroscope.Stop method to stop monitoring the sensor. You can detect the monitoring
state of the gyroscope with the IGyroscope.IsMonitoring property, which will be true if
the gyroscope was started and is currently being monitored.
C#
private void ToggleGyroscope()
if (Gyroscope.Default.IsSupported)
if (!Gyroscope.Default.IsMonitoring)
// Turn on gyroscope
Gyroscope.Default.ReadingChanged += Gyroscope_ReadingChanged;
Gyroscope.Default.Start(SensorSpeed.UI);
else
Gyroscope.Default.ReadingChanged -= Gyroscope_ReadingChanged;
GyroscopeLabel.TextColor = Colors.Green;
Magnetometer
The magnetometer sensor indicates the device's orientation relative to Earth's magnetic
field.
The IMagnetometer interface provides access to the sensor, and is available through the
Magnetometer.Default property. Both the IMagnetometer interface and Magnetometer
class are contained in the Microsoft.Maui.Devices.Sensors namespace.
C#
if (Magnetometer.Default.IsSupported)
if (!Magnetometer.Default.IsMonitoring)
// Turn on magnetometer
Magnetometer.Default.ReadingChanged +=
Magnetometer_ReadingChanged;
Magnetometer.Default.Start(SensorSpeed.UI);
else
Magnetometer.Default.Stop();
Magnetometer.Default.ReadingChanged -=
Magnetometer_ReadingChanged;
MagnetometerLabel.TextColor = Colors.Green;
Orientation
The orientation sensor monitors the orientation of a device in 3D space.
7 Note
This sensor isn't used for determining if the device's video display is in portrait or
landscape mode. Use the DeviceDisplay.Current.MainDisplayInfo.Orientation
property instead. For more information, see Device display information.
The IOrientationSensor interface provides access to the sensor, and is available through
the OrientationSensor.Default property. Both the IOrientationSensor interface and
OrientationSensor class are contained in the Microsoft.Maui.Devices.Sensors
namespace.
C#
if (OrientationSensor.Default.IsSupported)
if (!OrientationSensor.Default.IsMonitoring)
// Turn on orientation
OrientationSensor.Default.ReadingChanged +=
Orientation_ReadingChanged;
OrientationSensor.Default.Start(SensorSpeed.UI);
else
OrientationSensor.Default.Stop();
OrientationSensor.Default.ReadingChanged -=
Orientation_ReadingChanged;
OrientationLabel.TextColor = Colors.Green;
The device (generally a phone or tablet) has a 3D coordinate system with the following
axes:
The positive X-axis points to the right of the display in portrait mode.
The positive Y-axis points to the top of the device in portrait mode.
The positive Z-axis points out of the screen.
The positive X-axis is tangent to the surface of the Earth and points east.
The positive Y-axis is also tangent to the surface of the Earth and points north.
The positive Z-axis is perpendicular to the surface of the Earth and points up.
The Quaternion describes the rotation of the device's coordinate system relative to the
Earth's coordinate system.
These are right-hand coordinate systems, so with the thumb of the right hand pointed
in the positive direction of the rotation axis, the curve of the fingers indicate the
direction of rotation for positive angles.
Examples:
When the device lies flat on a table with its screen facing up, with the top of the
device (in portrait mode) pointing north, the two coordinate systems are aligned.
The Quaternion value represents the identity quaternion (0, 0, 0, 1). All rotations
can be analyzed relative to this position.
When the device lies flat on a table with its screen facing up, and the top of the
device (in portrait mode) pointing west, the Quaternion value is (0, 0, 0.707, 0.707).
The device has been rotated 90 degrees around the Z axis of the Earth.
When the device is held upright so that the top (in portrait mode) points towards
the sky, and the back of the device faces north, the device has been rotated 90
degrees around the X axis. The Quaternion value is (0.707, 0, 0, 0.707).
If the device is positioned so its left edge is on a table, and the top points north,
the device has been rotated -90 degrees around the Y axis (or 90 degrees around
the negative Y axis). The Quaternion value is (0, -0.707, 0, 0.707).
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IFlashlight interface. With this interface, you can toggle the device's camera flash on and
off, to emulate a flashlight.
Get started
To access the flashlight functionality the following platform-specific setup is required.
Windows
No setup is required.
Use Flashlight
The flashlight can be turned on and off through the TurnOnAsync() and TurnOffAsync()
methods. The following code example ties the flashlight's on or off state to a Switch
control:
C#
try
if (FlashlightSwitch.IsToggled)
await Flashlight.Default.TurnOnAsync();
else
await Flashlight.Default.TurnOffAsync();
Platform differences
This section describes the platform-specific differences with the flashlight.
Windows
The Lamp API is used to turn on or off the first detected lamp on the back of the
device.
Geocoding
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IGeocoding interface. This interfaces provides APIs to geocode a placemark to a
positional coordinates and reverse geocode coordinates to a placemark.
Get started
To access the Geocoding functionality the following platform-specific setup is required.
Windows
A Bing Maps API key is required to use geocoding functionality. Sign up for a free
Bing Maps account. Under My account > My keys, create a new key and fill out
information based on your application type, which should be Windows Application.
C#
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureEssentials(essentials =>
essentials.UseMapServiceToken("YOUR-API-TOKEN");
});
return builder.Build();
Use geocoding
The following example demonstrates how to get the location coordinates for an
address:
C#
if (location != null)
The altitude isn't always available. If it isn't available, the Altitude property might be
null , or the value might be 0 . If the altitude is available, the value is in meters above
sea level.
Reverse geocoding
Reverse geocoding is the process of getting placemarks for an existing set of
coordinates. The following example demonstrates getting placemarks:
C#
if (placemark != null)
return
$"AdminArea: {placemark.AdminArea}\n" +
$"CountryCode: {placemark.CountryCode}\n" +
$"CountryName: {placemark.CountryName}\n" +
$"FeatureName: {placemark.FeatureName}\n" +
$"Locality: {placemark.Locality}\n" +
$"PostalCode: {placemark.PostalCode}\n" +
$"SubAdminArea: {placemark.SubAdminArea}\n" +
$"SubLocality: {placemark.SubLocality}\n" +
$"SubThoroughfare: {placemark.SubThoroughfare}\n" +
$"Thoroughfare: {placemark.Thoroughfare}\n";
return "";
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IGeolocation interface. This interface provides APIs to retrieve the device's current
geolocation coordinates.
Get started
To access the Geolocation functionality, the following platform-specific setup is
required:
Windows
No setup is required.
7 Note
When necessary, the Geolocation API prompts the user for permissions.
C#
try
if (location != null)
return "None";
Depending on the device, not all location values may be available. For example, the
Altitude property might be null , have a value of 0, or have a positive value indicating
the meters above sea level. Other values that may not be present include the Speed and
Course properties.
7 Note
When necessary, the Geolocation API prompt's the user for permissions.
The following code example demonstrates how to request the device's location, while
supporting cancellation:
C#
try
_isCheckingLocation = true;
if (location != null)
// FeatureNotSupportedException
// FeatureNotEnabledException
// PermissionException
finally
_isCheckingLocation = false;
_cancelTokenSource.Cancel();
Not all location values may be available, depending on the device. For example, the
Altitude property might be null , have a value of 0, or have a positive value indicating
the meters above sea level. Other values that may not be present include Speed and
Course.
2 Warning
GetLocationAsync can return null in some scenarios. This indicates that the
underlying platform is unable to obtain the current location.
Accuracy
The following sections outline the location accuracy distance, per platform:
) Important
iOS has some limitations regarding accuracy. For more information, see the
Platform differences section.
Lowest
Android 500
iOS 3000
Low
Android 500
iOS 1000
Medium (Default)
iOS 100
Windows 30-500
High
Android 0 - 100
iOS 10
Windows <= 10
Best
Android 0 - 100
iOS ~0
Windows <= 10
C#
The following code calculates the distance between the United States of America cities
of Boston and San Francisco:
C#
The Location(Double, Double, Double) constructor accepts the latitude and longitude
arguments, respectively. Positive latitude values are north of the equator, and positive
longitude values are east of the Prime Meridian. Use the final argument to
CalculateDistance to specify miles or kilometers. The UnitConverters class also defines
Platform differences
This section describes the platform-specific differences with the geolocation API.
Windows
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IHapticFeedback interface to control haptic feedback on a device. Haptic feedback is
generally manifested by a gentle vibration sensation provided by the device to give a
response to the user. Some examples of haptic feedback are when a user types on a
virtual keyboard or when they play a game where the player's character has an
encounter with an enemy character.
Get started
To access the haptic feedback functionality, the following platform-specific setup is
required.
Windows
No setup is required.
C#
HapticFeedback.Default.Perform(HapticFeedbackType.Click);
HapticFeedback.Default.Perform(HapticFeedbackType.LongPress);
Vibration
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IVibration interface. This interface lets you start and stop the vibrate functionality for a
desired amount of time.
Get started
To access the Vibration functionality, the following platform specific setup is required.
Windows
No setup is required.
C#
Vibration.Default.Vibrate(vibrationLength);
Vibration.Default.Cancel();
Platform differences
This section describes the platform-specific differences with the vibration API.
Windows
No platform differences.
Media picker for photos and videos
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IMediaPicker interface. This interface lets a user pick or take a photo or video on the
device.
Get started
To access the media picker functionality, the following platform-specific setup is
required.
Windows
No setup is required.
PickPhotoAsync
CapturePhotoAsync
PickVideoAsync
CaptureVideoAsync
Each method optionally takes in a MediaPickerOptions parameter type that allows the
Title to be set on some operating systems, which is displayed to the user.
) Important
All methods must be called on the UI thread because permission checks and
requests are automatically handled by .NET MAUI.
Take a photo
Call the CapturePhotoAsync method to open the camera and let the user take a photo.
If the user takes a photo, the return value of the method will be a non-null value. The
following code sample uses the media picker to take a photo and save it to the cache
directory:
C#
if (MediaPicker.Default.IsCaptureSupported)
if (photo != null)
await sourceStream.CopyToAsync(localFileStream);
Tip
The FullPath property doesn't always return the physical path to the file. To get the
file, use the OpenReadAsync method.
Screenshot
Article • 02/03/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IScreenshot interface. This interface lets you take a capture of the current displayed
screen of the app.
Capture a screenshot
To capture a screenshot of the current app, use the CaptureAsync() method. This
method returns a IScreenshotResult, which contains information about the capture, such
as the width and height of the screenshot. The following example demonstrates a
method that captures a screenshot and returns it as an ImageSource.
C#
if (Screenshot.Default.IsCaptureSupported)
return null;
Text-to-Speech
Article • 03/27/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
ITextToSpeech interface. This interface enables an application to utilize the built-in text-
to-speech engines to speak back text from the device. You can also use it to query for
available languages.
Get started
To access text-to-speech functionality, the following platform-specific setup is required.
Windows
No setup is required.
Using Text-to-Speech
Text-to-speech works by calling the SpeakAsync method with the text to speak, as the
following code example demonstrates:
C#
This method takes in an optional CancellationToken to stop the utterance once it starts.
C#
CancellationTokenSource cts;
if (cts?.IsCancellationRequested ?? true)
return;
cts.Cancel();
Text-to-Speech will automatically queue speech requests from the same thread.
C#
isBusy = true;
Task.WhenAll(
Settings
To control the volume, pitch, and locale of the voice, use the SpeechOptions class. Pass
an instance of that class to the SpeakAsync(String, SpeechOptions, CancellationToken)
method. The GetLocalesAsync() method retrieves a collection of the locales provided by
the operating system.
C#
Locale = locales.FirstOrDefault()
};
Pitch 0 2.0
Volume 0 1.0
Limitations
Utterance queueing isn't guaranteed if called across multiple threads.
Background audio playback isn't officially supported.
Unit converters
Article • 02/03/2023 • 2 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
UnitConverters class. This class provides several unit converters to help developers
convert from one unit of measurement to another.
C#
FahrenheitToCelsius
CelsiusToFahrenheit
CelsiusToKelvin
KelvinToCelsius
MilesToMeters
MilesToKilometers
KilometersToMiles
MetersToInternationalFeet
InternationalFeetToMeters
DegreesToRadians
RadiansToDegrees
DegreesPerSecondToRadiansPerSecond
RadiansPerSecondToDegreesPerSecond
DegreesPerSecondToHertz
RadiansPerSecondToHertz
HertzToDegreesPerSecond
HertzToRadiansPerSecond
KilopascalsToHectopascals
HectopascalsToKilopascals
KilopascalsToPascals
HectopascalsToPascals
AtmospheresToPascals
PascalsToAtmospheres
CoordinatesToMiles
CoordinatesToKilometers
KilogramsToPounds
PoundsToKilograms
StonesToPounds
PoundsToStones
Android platform-specifics
Article • 12/23/2022 • 2 minutes to read
Setting the input method editor options for the soft keyboard for an Entry. For
more information, see Entry input method editor options on Android.
Enabling fast scrolling in a ListView. For more information, see ListView fast
scrolling on Android.
Controlling the transition that's used when opening a SwipeView. For more
information, see SwipeView swipe transition Mode.
Controlling whether a WebView can display mixed content. For more information,
see WebView mixed content on Android.
Enabling zoom on a WebView. For more information, see WebView zoom on
Android.
The following platform-specific functionality is provided for the .NET MAUI Application
class on Android:
Setting the operating mode of a soft keyboard. For more information, see Soft
keyboard input mode on Android.
Entry input method editor options on
Android
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) Android platform-specific sets the input
method editor (IME) options for the soft keyboard for an Entry. This includes setting the
user action button in the bottom corner of the soft keyboard, and the interactions with
the Entry. It's consumed in XAML by setting the Entry.ImeOptions attached property to
a value of the ImeFlags enumeration:
XAML
<ContentPage ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls">
<StackLayout ...>
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
entry.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>
().SetImeOptions(ImeFlags.Send);
Default – indicates that no specific action key is required, and that the underlying
control will produce its own if it can. This will either be Next or Done .
None – indicates that no action key will be made available.
Go – indicates that the action key will perform a "go" operation, taking the user to
user to the results of searching for the text they have typed.
Send – indicates that the action key will perform a "send" operation, delivering the
NoPersonalizedLearning – indicates that the spellchecker will neither learn from the
user, nor suggest corrections based on what the user has previously typed.
NoFullscreen – indicates that the UI should not go fullscreen.
The result is that a specified ImeFlags value is applied to the soft keyboard for the Entry,
which sets the input method editor options:
ListView fast scrolling on Android
Article • 12/23/2022 • 2 minutes to read
XAML
<ContentPage ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls">
<StackLayout>
...
GroupDisplayBinding="{Binding Key}"
IsGroupingEnabled="true"
android:ListView.IsFastScrollEnabled="true">
...
</ListView>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
listView.SetBinding(ItemsView<Cell>.ItemsSourceProperty,
"GroupedEmployees");
listView.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>
().SetIsFastScrollEnabled(true);
listView.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>
().SetIsFastScrollEnabled(!listView.On<Microsoft.Maui.Controls.PlatformConfi
guration.Android>().IsFastScrollEnabled());
The result is that fast scrolling through data in a ListView can be enabled, which changes
the size of the scroll thumb:
Soft keyboard input mode on Android
Article • 06/24/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) Android platform-specific is used to set
the operating mode for a soft keyboard input area, and is consumed in XAML by setting
the Application.WindowSoftInputModeAdjust attached property to a value of the
WindowSoftInputModeAdjust enumeration:
XAML
<Application ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls"
android:Application.WindowSoftInputModeAdjust="Resize">
...
</Application>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
App.Current.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>
().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);
set the soft keyboard input area operating mode, with the WindowSoftInputModeAdjust
enumeration providing two values: Pan and Resize . The Pan value uses the AdjustPan
adjustment option, which doesn't resize the window when an input control has focus.
Instead, the contents of the window are panned so that the current focus isn't obscured
by the soft keyboard. The Resize value uses the AdjustResize adjustment option, which
resizes the window when an input control has focus, to make room for the soft
keyboard.
The result is that the soft keyboard input area operating mode can be set when an input
control has focus:
SwipeView swipe transition mode on
Android
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) Android platform-specific controls the
transition that's used when opening a SwipeView. It's consumed in XAML by setting the
SwipeView.SwipeTransitionMode bindable property to a value of the
SwipeTransitionMode enumeration:
XAML
<ContentPage ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls" >
<StackLayout>
<SwipeView android:SwipeView.SwipeTransitionMode="Drag">
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
swipeView.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>
().SetSwipeTransitionMode(SwipeTransitionMode.Drag);
// ...
control the transition that's used when opening a SwipeView. The SwipeTransitionMode
enumeration provides two possible values:
Reveal indicates that the swipe items will be revealed as the SwipeView content is
swiped, and is the default value of the SwipeView.SwipeTransitionMode property.
Drag indicates that the swipe items will be dragged into view as the SwipeView
content is swiped.
XAML
<TabbedPage ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls"
android:TabbedPage.OffscreenPageLimit="2"
android:TabbedPage.IsSwipePagingEnabled="true">
...
</TabbedPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
On<Microsoft.Maui.Controls.PlatformConfiguration.Android>()
.SetOffscreenPageLimit(2)
.SetIsSwipePagingEnabled(true);
7 Note
enable swiping between pages in a TabbedPage. In addition, the TabbedPage class in the
Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific namespace also has a
EnableSwipePaging method that enables this platform-specific, and a
are used to set the number of pages that should be retained in an idle state on either
side of the current page.
The result is that swipe paging through the pages displayed by a TabbedPage is
enabled:
TabbedPage page transition animations
on Android
Article • 01/04/2023 • 2 minutes to read
XAML
<TabbedPage ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls"
android:TabbedPage.IsSmoothScrollEnabled="false">
...
</TabbedPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
On<Microsoft.Maui.Controls.PlatformConfiguration.Android>
().SetIsSmoothScrollEnabled(false);
7 Note
This .NET Multi-platform App UI (.NET MAUI) Android platform-specific is used to set
the placement of the toolbar on a TabbedPage. It's consumed in XAML by setting the
TabbedPage.ToolbarPlacement attached property to a value of the ToolbarPlacement
enumeration:
XAML
<TabbedPage ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls"
android:TabbedPage.ToolbarPlacement="Bottom">
...
</TabbedPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
On<Microsoft.Maui.Controls.PlatformConfiguration.Android>
().SetToolbarPlacement(ToolbarPlacement.Bottom);
7 Note
Default – indicates that the toolbar is placed at the default location on the page.
This is the top of the page on phones, and the bottom of the page on other device
idioms.
Top – indicates that the toolbar is placed at the top of the page.
Bottom – indicates that the toolbar is placed at the bottom of the page.
7 Note
This .NET Multi-platform App UI (.NET MAUI) Android platform-specific controls whether
a WebView can display mixed content. Mixed content is content that's initially loaded
over an HTTPS connection, but which loads resources (such as images, audio, video,
stylesheets, scripts) over an HTTP connection. It's consumed in XAML by setting the
WebView.MixedContentMode attached property to a value of the MixedContentHandling
enumeration:
XAML
<ContentPage ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls">
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
webView.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>
().SetMixedContentMode(MixedContentHandling.AlwaysAllow);
AlwaysAllow – indicates that the WebView will allow an HTTPS origin to load
This .NET Multi-platform App UI (.NET MAUI) Android platform-specific enables pinch-
to-zoom and a zoom control on a WebView. It's consumed in XAML by setting the
WebView.EnableZoomControls and WebView.DisplayZoomControls bindable properties to
boolean values:
XAML
<ContentPage ...
xmlns:android="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;asse
mbly=Microsoft.Maui.Controls">
<WebView Source="https://www.microsoft.com"
android:WebView.EnableZoomControls="true"
android:WebView.DisplayZoomControls="true" />
</ContentPage>
Alternatively, the platform-specific can be consumed from C# using the fluent API:
C#
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
...
webView.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>()
.EnableZoomControls(true)
.DisplayZoomControls(true);
) Important
Zoom controls must be both enabled and displayed, via the respective bindable
properties or methods, to be overlaid on a WebView.
iOS platform-specifics
Article • 02/16/2023 • 2 minutes to read
The following platform-specific functionality is provided for .NET MAUI views on iOS:
Setting the Cell background color. For more information, see Cell background
color on iOS.
Controlling when item selection occurs in a DatePicker. For more information, see
DatePicker item selection on iOS.
Ensuring that inputted text fits into an Entry by adjusting the font size. For more
information, see Entry font size on iOS.
Setting the cursor color in a Entry. For more information, see Entry cursor color on
iOS.
Controlling whether ListView header cells float during scrolling. For more
information, see ListView group header style on iOS.
Controlling whether row animations are disabled when the ListView items
collection is being updated. For more information, see ListView row animations on
iOS.
Setting the separator style on a ListView. For more information, see ListView
separator style on iOS.
Controlling when item selection occurs in a Picker. For more information, see
Picker item selection on iOS.
Controlling whether a SearchBar has a background. For more information, see
SearchBar style on iOS.
Enabling the Slider.Value property to be set by tapping on a position on the
Slider bar, rather than by having to drag the Slider thumb. For more information,
see Slider thumb tap on iOS.
Controlling the transition that's used when opening a SwipeView. For more
information, see SwipeView swipe transition mode.
Controlling when item selection occurs in a TimePicker. For more information, see
TimePicker item selection on iOS.
The following platform-specific functionality is provided for .NET MAUI pages on iOS:
Controlling whether the detail page of a FlyoutPage has shadow applied to it,
when revealing the flyout page. For more information, see FlyoutPage shadow.
Controlling whether the navigation bar is translucent. For more information, see
Navigation bar translucency on iOS.
Controlling whether the page title is displayed as a large title in the page
navigation bar. For more information, see Large page titles on iOS.
Disabling the safe area layout guide, which ensures that page content is positioned
on an area of the screen that is safe for all iOS devices. For more information, see
Disable the safe area layout guide on iOS.
Setting the presentation style of modal pages. For more information, see Modal
page presentation style.
Setting the translucency mode of the tab bar on a TabbedPage. For more
information, see TabbedPage translucent TabBar on iOS.
The following platform-specific functionality is provided for .NET MAUI layouts on iOS:
The following platform-specific functionality is provided for the .NET MAUI Application
class on iOS:
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific sets the default
background color of Cell instances. It's consumed in XAML by setting the
Cell.DefaultBackgroundColor bindable property to a Color:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout Margin="20">
IsGroupingEnabled="true">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell ios:Cell.DefaultBackgroundColor="Teal">
<Label Margin="10,10"
Text="{Binding Key}"
FontAttributes="Bold" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
...
</ListView>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
viewCell.On<iOS>().SetDefaultBackgroundColor(Colors.Teal);
The ListView.On<iOS> method specifies that this platform-specific will only run on iOS.
The Cell.SetDefaultBackgroundColor method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, sets the cell
background color to a specified Color. In addition, the Cell.DefaultBackgroundColor
method can be used to retrieve the current cell background color.
The result is that the background color in a Cell can be set to a specific Color:
DatePicker item selection on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific controls when item
selection occurs in a DatePicker, allowing you to specify that item selection occurs when
browsing items in the control, or only once the Done button is pressed. It's consumed in
XAML by setting the DatePicker.UpdateMode attached property to a value of the
UpdateMode enumeration:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout>
<DatePicker MinimumDate="01/01/2020"
MaximumDate="12/31/2020"
ios:DatePicker.UpdateMode="WhenFinished" />
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
datePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
The DatePicker.On<iOS> method specifies that this platform-specific will only run on
iOS. The DatePicker.SetUpdateMode method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control when item selection occurs, with the UpdateMode enumeration providing two
possible values:
Immediately – item selection occurs as the user browses items in the DatePicker.
C#
switch (datePicker.On<iOS>().UpdateMode())
case UpdateMode.Immediately:
datePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
break;
case UpdateMode.WhenFinished:
datePicker.On<iOS>().SetUpdateMode(UpdateMode.Immediately);
break;
The result is that a specified UpdateMode is applied to the DatePicker, which controls
when item selection occurs:
Entry Cursor Color on iOS
Article • 04/03/2023 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific sets the cursor color
of an Entry to a specified color. It's consumed in XAML by setting the Entry.CursorColor
bindable property to a Color:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
entry.On<iOS>().SetCursorColor(Colors.LimeGreen);
The Entry.On<iOS> method specifies that this platform-specific will only run on iOS. The
Entry.SetCursorColor method, in the
The result is that the cursor color in a Entry can be set to a specific Color:
Entry font size on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific is used to scale the
font size of an Entry to ensure that the inputted text fits in the control. It's consumed in
XAML by setting the Entry.AdjustsFontSizeToFitWidth attached property to a boolean
value:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
<StackLayout Margin="20">
<Entry x:Name="entry"
FontSize="22"
ios:Entry.AdjustsFontSizeToFitWidth="true" />
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
entry.On<iOS>().EnableAdjustsFontSizeToFitWidth();
The Entry.On<iOS> method specifies that this platform-specific will only run on iOS. The
Entry.EnableAdjustsFontSizeToFitWidth method, in the
C#
entry.On<iOS>().SetAdjustsFontSizeToFitWidth(!entry.On<iOS>
().AdjustsFontSizeToFitWidth());
The result is that the font size of the Entry is scaled to ensure that the inputted text fits
in the control:
FlyoutPage shadow on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) platform-specific controls whether the
detail page of a FlyoutPage has shadow applied to it, when revealing the flyout page. It's
consumed in XAML by setting the FlyoutPage.ApplyShadow bindable property to true :
XAML
<FlyoutPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
ios:FlyoutPage.ApplyShadow="true">
...
</FlyoutPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
public iOSFlyoutPageCode()
On<iOS>().SetApplyShadow(true);
The FlyoutPage.On<iOS> method specifies that this platform-specific will only run on
iOS. The FlyoutPage.SetApplyShadow method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control whether the detail page of a FlyoutPage has shadow applied to it, when
revealing the flyout page. In addition, the GetApplyShadow method can be used to
determine whether shadow is applied to the detail page of a FlyoutPage.
The result is that the detail page of a FlyoutPage can have shadow applied to it, when
revealing the flyout page:
Large Page Titles on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific is used to display the
page title as a large title on the navigation bar of a NavigationPage, for devices that use
iOS 11 or greater. A large title is left aligned and uses a larger font, and transitions to a
standard title as the user begins scrolling content, so that the screen real estate is used
efficiently. However, in landscape orientation, the title will return to the center of the
navigation bar to optimize content layout. It's consumed in XAML by setting the
NavigationPage.PrefersLargeTitles attached property to a boolean value:
XAML
<NavigationPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
...
ios:NavigationPage.PrefersLargeTitles="true">
...
</NavigationPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
navigationPage.On<iOS>().SetPrefersLargeTitles(true);
The NavigationPage.On<iOS> method specifies that this platform-specific will only run on
iOS. The NavigationPage.SetPrefersLargeTitle method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, controls
Provided that large titles are enabled on the NavigationPage, all pages in the navigation
stack will display large titles. This behavior can be overridden on pages by setting the
Page.LargeTitleDisplay attached property to a value of the LargeTitleDisplayMode
enumeration:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
Title="Large Title"
ios:Page.LargeTitleDisplay="Never">
...
</ContentPage>
Alternatively, the page behavior can be overridden from C# using the fluent API:
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
public iOSLargeTitlePageCode
On<iOS>().SetLargeTitleDisplay(LargeTitleDisplayMode.Never);
...
The Page.On<iOS> method specifies that this platform-specific will only run on iOS. The
Page.SetLargeTitleDisplay method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, controls the
large title behavior on the Page, with the LargeTitleDisplayMode enumeration providing
three possible values:
Always – force the navigation bar and font size to use the large format.
Automatic – use the same style (large or small) as the previous item in the
navigation stack.
Never – force the use of the regular, small format navigation bar.
C#
switch (On<iOS>().LargeTitleDisplay())
case LargeTitleDisplayMode.Always:
On<iOS>().SetLargeTitleDisplay(LargeTitleDisplayMode.Automatic);
break;
case LargeTitleDisplayMode.Automatic:
On<iOS>().SetLargeTitleDisplay(LargeTitleDisplayMode.Never);
break;
case LargeTitleDisplayMode.Never:
On<iOS>().SetLargeTitleDisplay(LargeTitleDisplayMode.Always);
break;
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific controls whether
ListView header cells float during scrolling. It's consumed in XAML by setting the
ListView.GroupHeaderStyle bindable property to a value of the GroupHeaderStyle
enumeration:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout Margin="20">
...
</ListView>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
listView.On<iOS>().SetGroupHeaderStyle(GroupHeaderStyle.Grouped);
The ListView.On<iOS> method specifies that this platform-specific will only run on iOS.
The ListView.SetGroupHeaderStyle method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control whether ListView header cells float during scrolling. The GroupHeaderStyle
enumeration provides two possible values:
Plain – indicates that header cells float when the ListView is scrolled (default).
Grouped – indicates that header cells do not float when the ListView is scrolled.
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific controls whether row
animations are disabled when the ListView items collection is being updated. It's
consumed in XAML by setting the ListView.RowAnimationsEnabled bindable property to
false :
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout>
...
</ListView>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
listView.On<iOS>().SetRowAnimationsEnabled(false);
The ListView.On<iOS> method specifies that this platform-specific will only run on iOS.
The ListView.SetRowAnimationsEnabled method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control whether row animations are disabled when the ListView items collection is being
updated. In addition, the ListView.GetRowAnimationsEnabled method can be used to
return whether row animations are disabled on the ListView.
7 Note
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific controls whether the
separator between cells in a ListView uses the full width of the ListView. It's consumed in
XAML by setting the ListView.SeparatorStyle attached property to a value of the
SeparatorStyle enumeration:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout Margin="20">
...
</ListView>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
listView.On<iOS>().SetSeparatorStyle(SeparatorStyle.FullWidth);
The ListView.On<iOS> method specifies that this platform-specific will only run on iOS.
The ListView.SetSeparatorStyle method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control whether the separator between cells in the ListView uses the full width of the
ListView, with the SeparatorStyle enumeration providing two possible values:
Default – indicates the default iOS separator behavior. This is the default behavior.
FullWidth – indicates that separators will be drawn from one edge of the ListView
to the other.
The result is that a specified SeparatorStyle value is applied to the ListView, which
controls the width of the separator between cells:
7 Note
Once the separator style has been set to FullWidth , it cannot be changed back to
Default at runtime.
Modal page presentation style on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific is used to set the
presentation style of a modal page, and in addition can be used to display modal pages
that have transparent backgrounds. It's consumed in XAML by setting the
Page.ModalPresentationStyle bindable property to a UIModalPresentationStyle
enumeration value:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
ios:Page.ModalPresentationStyle="OverFullScreen">
...
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
public iOSModalFormSheetPageCode()
On<iOS>
().SetModalPresentationStyle(UIModalPresentationStyle.OverFullScreen);
The Page.On<iOS> method specifies that this platform-specific will only run on iOS. The
Page.SetModalPresentationStyle method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to set
FullScreen , which sets the modal presentation style to encompass the whole
screen. By default, modal pages are displayed using this presentation style.
FormSheet , which sets the modal presentation style to be centered on and smaller
to a different style.
OverFullScreen , which sets the modal presentation style to cover the screen.
PageSheet , which sets the modal presentation style to cover the underlying
content.
The result is that the modal presentation style on a Page can be set:
7 Note
Pages that use this platform-specific to set the modal presentation style must use
modal navigation. For more information, see Perform modal navigation.
NavigationPage bar translucency on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific is used to change the
transparency of the navigation bar on a NavigationPage, and is consumed in XAML by
setting the NavigationPage.IsNavigationBarTranslucent attached property to a boolean
value:
XAML
<NavigationPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
BackgroundColor="Blue"
ios:NavigationPage.IsNavigationBarTranslucent="true">
...
</NavigationPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
(App.Current.MainPage as
Microsoft.Maui.Controls.NavigationPage).BackgroundColor = Colors.Blue;
(App.Current.MainPage as Microsoft.maui.Controls.NavigationPage).On<iOS>
().EnableTranslucentNavigationBar();
The NavigationPage.On<iOS> method specifies that this platform-specific will only run on
iOS. The NavigationPage.EnableTranslucentNavigationBar method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
make the navigation bar translucent. In addition, the NavigationPage class in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace also has a
DisableTranslucentNavigationBar method that restores the navigation bar to its default
C#
(App.Current.MainPage as Microsoft.Maui.Controls.NavigationPage)
.On<iOS>()
.SetIsNavigationBarTranslucent(!(App.Current.MainPage as
Microsoft.Maui.Controls.NavigationPage).On<iOS>
().IsNavigationBarTranslucent());
The result is that the transparency of the navigation bar can be changed:
Picker item selection on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific controls when item
selection occurs in a Picker, allowing the user to specify that item selection occurs when
browsing items in the control, or only once the Done button is pressed. It's consumed in
XAML by setting the Picker.UpdateMode attached property to a value of the UpdateMode
enumeration:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout Margin="20">
...
</Picker>
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
picker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
The Picker.On<iOS> method specifies that this platform-specific will only run on iOS.
The Picker.SetUpdateMode method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control when item selection occurs, with the UpdateMode enumeration providing two
possible values:
Immediately – item selection occurs as the user browses items in the Picker. This is
C#
switch (picker.On<iOS>().UpdateMode())
case UpdateMode.Immediately:
picker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
break;
case UpdateMode.WhenFinished:
picker.On<iOS>().SetUpdateMode(UpdateMode.Immediately);
break;
The result is that a specified UpdateMode is applied to the Picker, which controls when
item selection occurs:
Disable the safe area layout guide on
iOS
Article • 02/16/2023 • 2 minutes to read
By default, .NET Multi-platform App UI (.NET MAUI) apps automatically position page
content on an area of the screen that is safe for all devices. This is known as the safe
area layout guide, and ensures that content isn't clipped by rounded device corners, the
home indicator, or the sensor housing on some iPhone models.
This iOS platform-specific disables the safe area layout guide, and is consumed in XAML
by setting the Page.UseSafeArea attached property to false :
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
ios:Page.UseSafeArea="False">
<StackLayout>
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
On<iOS>().SetUseSafeArea(false);
The Page.On<iOS> method specifies that this platform-specific will only run on iOS. The
Page.SetUseSafeArea method, in the
7 Note
The Layout class defines a IgnoreSafeArea property that ensures that content is
positioned on an area of the screen that is safe for all iOS devices. This property can
be set to false on any layout class, such as a Grid or
<Microsoft.Maui.Controls.StackLayout>, to perform the equivalent of this platform-
specific.
The safe area can be customized by retrieving its Thickness value with the
Page.SafeAreaInsets method from the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace. It can then be
modified as required and assigned to the page's Padding property in the OnAppearing
override:
C#
base.OnAppearing();
safeInsets.Left = 20;
Padding = safeInsets;
An implicit timer is triggered when a touch gesture begins in a ScrollView on iOS and
the ScrollView decides, based on the user action within the timer span, whether it should
handle the gesture or pass it to its content. By default, the iOS ScrollView delays content
touches, but this can cause problems in some circumstances with the ScrollView content
not winning the gesture when it should. Therefore, this .NET Multi-platform App UI
(.NET MAUI) platform-specific controls whether a ScrollView handles a touch gesture or
passes it to its content. It's consumed in XAML by setting the
ScrollView.ShouldDelayContentTouches attached property to a boolean value:
XAML
<FlyoutPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<FlyoutPage.Flyout>
<ContentPage Title="Menu"
BackgroundColor="Blue" />
</FlyoutPage.Flyout>
<FlyoutPage.Detail>
<ContentPage>
<ScrollView x:Name="scrollView"
ios:ScrollView.ShouldDelayContentTouches="false">
<StackLayout Margin="0,20">
<Slider />
Clicked="OnButtonClicked" />
</StackLayout>
</ScrollView>
</ContentPage>
</FlyoutPage.Detail>
</FlyoutPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
scrollView.On<iOS>().SetShouldDelayContentTouches(false);
The ScrollView.On<iOS> method specifies that this platform-specific will only run on
iOS. The ScrollView.SetShouldDelayContentTouches method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
C#
scrollView.On<iOS>().SetShouldDelayContentTouches(!scrollView.On<iOS>
().ShouldDelayContentTouches());
The result is that a ScrollView can disable delaying receiving content touches, so that in
this scenario the Slider receives the gesture rather than the Detail page of the
FlyoutPage:
SearchBar style on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific controls whether a
SearchBar has a background. It's consumed in XAML by setting the
SearchBar.SearchBarStyle bindable property to a value of the UISearchBarStyle
enumeration:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout>
<SearchBar ios:SearchBar.SearchBarStyle="Minimal"
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
searchBar.On<iOS>().SetSearchBarStyle(UISearchBarStyle.Minimal);
The SearchBar.On<iOS> method specifies that this platform-specific will only run on iOS.
The SearchBar.SetSearchBarStyle method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control whether the SearchBar has a background. The UISearchBarStyle enumeration
provides three possible values:
Default indicates that the SearchBar has the default style. This is the default value
When a PanGestureRecognizer is attached to a view inside a scrolling view, all of the pan
gestures are captured by the PanGestureRecognizer and aren't passed to the scrolling
view. Therefore, the scrolling view will no longer scroll.
true :
XAML
<Application ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
ios:Application.PanGestureRecognizerShouldRecognizeSimultaneously="true">
...
</Application>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
Application.Current.On<iOS>
().SetPanGestureRecognizerShouldRecognizeSimultaneously(true);
The Application.On<iOS> method specifies that this platform-specific will only run on
iOS. The Application.SetPanGestureRecognizerShouldRecognizeSimultaneously method,
in the Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used
to control whether a pan gesture recognizer in a scrolling view will capture the pan
gesture, or capture and share the pan gesture with the scrolling view. In addition, the
Application.GetPanGestureRecognizerShouldRecognizeSimultaneously method can be
used to return whether the pan gesture is shared with the scrolling view that contains
the PanGestureRecognizer.
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific enables the
Slider.Value property to be set by tapping on a position on the Slider bar, rather than
by having to drag the Slider thumb. It's consumed in XAML by setting the
Slider.UpdateOnTap bindable property to true :
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout>
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
slider.On<iOS>().SetUpdateOnTap(true);
The Slider.On<iOS> method specifies that this platform-specific will only run on iOS.
The Slider.SetUpdateOnTap method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control whether a tap on the Slider bar will set the Slider.Value property. In addition,
the Slider.GetUpdateOnTap method can be used to return whether a tap on the Slider
bar will set the Slider.Value property.
The result is that a tap on the Slider bar can move the Slider thumb and set the
Slider.Value property:
SwipeView swipe transition mode on
iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific controls the
transition that's used when opening a SwipeView. It's consumed in XAML by setting the
SwipeView.SwipeTransitionMode bindable property to a value of the
SwipeTransitionMode enumeration:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout>
<SwipeView ios:SwipeView.SwipeTransitionMode="Drag">
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Delete"
IconImageSource="delete.png"
BackgroundColor="LightPink"
Invoked="OnDeleteSwipeItemInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
</SwipeView>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
swipeView.On<iOS>().SetSwipeTransitionMode(SwipeTransitionMode.Drag);
// ...
The SwipeView.On<iOS> method specifies that this platform-specific will only run on iOS.
The SwipeView.SetSwipeTransitionMode method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control the transition that's used when opening a SwipeView. The SwipeTransitionMode
enumeration provides two possible values:
Reveal indicates that the swipe items will be revealed as the SwipeView content is
content is swiped.
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific is used to set the
translucency mode of the tab bar on a TabbedPage. It's consumed in XAML by setting
the TabbedPage.TranslucencyMode bindable property to a TranslucencyMode enumeration
value:
XAML
<TabbedPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls"
ios:TabbedPage.TranslucencyMode="Opaque">
...
</TabbedPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
On<iOS>().SetTranslucencyMode(TranslucencyMode.Opaque);
The TabbedPage.On<iOS> method specifies that this platform-specific will only run on
iOS. The TabbedPage.SetTranslucencyMode method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to set
the translucency mode of the tab bar on a TabbedPage by specifying one of the
following TranslucencyMode enumeration values:
Default , which sets the tab bar to its default translucency mode. This is the default
In addition, the GetTranslucencyMode method can be used to retrieve the current value
of the TranslucencyMode enumeration that's applied to the TabbedPage.
The result is that the translucency mode of the tab bar on a TabbedPage can be set:
TimePicker item selection on iOS
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) iOS platform-specific controls when item
selection occurs in a TimePicker, allowing the user to specify that item selection occurs
when browsing items in the control, or only once the Done button is pressed. It's
consumed in XAML by setting the TimePicker.UpdateMode attached property to a value
of the UpdateMode enumeration:
XAML
<ContentPage ...
xmlns:ios="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly
=Microsoft.Maui.Controls">
<StackLayout>
<TimePicker Time="14:00:00"
ios:TimePicker.UpdateMode="WhenFinished" />
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
...
timePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
The TimePicker.On<iOS> method specifies that this platform-specific will only run on
iOS. The TimePicker.SetUpdateMode method, in the
Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific namespace, is used to
control when item selection occurs, with the UpdateMode enumeration providing two
possible values:
Immediately – item selection occurs as the user browses items in the TimePicker.
C#
switch (timePicker.On<iOS>().UpdateMode())
case UpdateMode.Immediately:
timePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
break;
case UpdateMode.WhenFinished:
timePicker.On<iOS>().SetUpdateMode(UpdateMode.Immediately);
break;
The result is that a specified UpdateMode is applied to the TimePicker, which controls
when item selection occurs:
Windows platform-specifics
Article • 12/23/2022 • 2 minutes to read
The following platform-specific functionality is provided for .NET MAUI views, pages,
and layouts on Windows:
Setting an access key for a VisualElement. For more information, see VisualElement
Access Keys on Windows.
Detecting reading order from text content in Entry, Editor, and Label instances. For
more information, see InputView Reading Order on Windows.
Enabling tap gesture support in a ListView. For more information, see ListView
SelectionMode on Windows.
Enabling the pull direction of a RefreshView to be changed. For more information,
see RefreshView Pull Direction on Windows.
Enabling a SearchBar to interact with the spell check engine. For more information,
see SearchBar Spell Check on Windows.
The following platform-specific functionality is provided for the .NET MAUI Application
class on Windows:
Specifying the directory in the project that image assets will be loaded from. For
more information, see Default Image Directory on Windows.
Default image directory on Windows
Article • 06/24/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) Windows platform-specific defines the
directory in the project that image assets will be loaded from. It's consumed in XAML by
setting the Application.ImageDirectory to a string that represents the project
directory that contains image assets:
XAML
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:windows="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;asse
mbly=Microsoft.Maui.Controls"
...
windows:Application.ImageDirectory="Assets">
...
</Application>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;
...
Application.Current.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows
>().SetImageDirectory("Assets");
specify the project directory that images will be loaded from. In addition, the
GetImageDirectory method can be used to return a string that represents the project
The result is that all images used in an app will be loaded from the specified project
directory.
InputView reading order on Windows
Article • 12/23/2022 • 2 minutes to read
This .NET Multi-platform App UI (.NET MAUI) Windows platform-specific enables the
reading order (left-to-right or right-to-left) of bidirectional text in Entry, Editor, and
Label objects to be detected dynamically. It's consumed in XAML by setting the
InputView.DetectReadingOrderFromContent (for Entry and Editor objects) or
XAML
<ContentPage ...
xmlns:windows="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;asse
mbly=Microsoft.Maui.Controls">
<StackLayout>
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;
...
editor.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetDetectReadingOrderFromContent(true);
C#
editor.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetDetectReadingOrderFromContent(!editor.On<Microsoft.Maui.Controls.Platf
ormConfiguration.Windows>().GetDetectReadingOrderFromContent());
The result is that Entry, Editor, and Label objects can have the reading order of their
content detected dynamically:
7 Note
Unlike setting the FlowDirection property, the logic for views that detect the
reading order from their text content will not affect the alignment of text within the
view. Instead, it adjusts the order in which blocks of bidirectional text are laid out.
ListView SelectionMode on Windows
Article • 12/23/2022 • 2 minutes to read
On Windows, by default the .NET Multi-platform App UI (.NET MAUI) ListView uses the
native ItemClick event to respond to interaction, rather than the native Tapped event.
This provides accessibility functionality so that the Windows Narrator and the keyboard
can interact with the ListView. However, it also renders any tap gestures inside the
ListView inoperable.
This .NET MAUI Windows platform-specific controls whether items in a ListView can
respond to tap gestures, and hence whether the native ListView fires the ItemClick or
Tapped event. It's consumed in XAML by setting the ListView.SelectionMode attached
property to a value of the ListViewSelectionMode enumeration:
XAML
<ContentPage ...
xmlns:windows="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;asse
mbly=Microsoft.Maui.Controls">
<StackLayout>
...
</ListView>
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;
...
listView.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetSelectionMode(ListViewSelectionMode.Inaccessible);
control whether items in a ListView can respond to tap gestures, with the
ListViewSelectionMode enumeration providing two possible values:
Accessible – indicates that the ListView will fire the native ItemClick event to
handle interaction. Therefore, items in the ListView can respond to tap gestures.
However, there's no accessibility functionality and hence the Windows Narrator
and the keyboard can't interact with the ListView.
7 Note
The Accessible and Inaccessible selection modes are mutually exclusive, and you
will need to choose between an accessible ListView or a ListView that can respond
to tap gestures.
This .NET Multi-platform App UI (.NET MAUI) Windows platform-specific enables the
pull direction of a RefreshView to be changed to match the orientation of the scrollable
control that's displaying data. It's consumed in XAML by setting the
RefreshView.RefreshPullDirection bindable property to a value of the
RefreshPullDirection enumeration:
XAML
<ContentPage ...
xmlns:windows="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;asse
mbly=Microsoft.Maui.Controls">
<RefreshView windows:RefreshView.RefreshPullDirection="LeftToRight"
IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<ScrollView>
...
</ScrollView>
</RefreshView>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;
...
refreshView.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetRefreshPullDirection(RefreshPullDirection.LeftToRight);
7 Note
When you change the pull direction, the starting position of the progress circle
automatically rotates so that the arrow starts in the appropriate position for the
pull direction.
SearchBar spell check on Windows
Article • 12/23/2022 • 2 minutes to read
XAML
<ContentPage ...
xmlns:windows="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;asse
mbly=Microsoft.Maui.Controls">
<StackLayout>
...
</StackLayout>
</ContentPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;
...
searchBar.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetIsSpellCheckEnabled(true);
C#
searchBar.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetIsSpellCheckEnabled(!searchBar.On<Microsoft.Maui.Controls.PlatformConf
iguration.Windows>().GetIsSpellCheckEnabled());
The result is that text entered into the SearchBar can be spell checked, with incorrect
spellings being indicated to the user:
7 Note
Access keys are keyboard shortcuts that improve the usability and accessibility of apps
on Windows by providing an intuitive way for users to quickly navigate and interact with
the app's visible UI through a keyboard instead of via touch or a mouse. They are
combinations of the Alt key and one or more alphanumeric keys, typically pressed
sequentially. Keyboard shortcuts are automatically supported for access keys that use a
single alphanumeric character.
Access key tips are floating badges displayed next to controls that include access keys.
Each access key tip contains the alphanumeric keys that activate the associated control.
When a user presses the Alt key, the access key tips are displayed.
XAML
<TabbedPage ...
xmlns:windows="clr-
namespace:Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;asse
mbly=Microsoft.Maui.Controls">
windows:VisualElement.AccessKey="1">
<StackLayout>
...
windows:VisualElement.AccessKey="B" />
...
Clicked="OnButtonClicked"
windows:VisualElement.AccessKey="F"
windows:VisualElement.AccessKeyPlacement="Top"
windows:VisualElement.AccessKeyHorizontalOffset="20"
windows:VisualElement.AccessKeyVerticalOffset="20" />
...
</StackLayout>
</ContentPage>
...
</TabbedPage>
C#
using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific;
...
page.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetAccessKey("1");
switchView.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetAccessKey("A");
entry.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
().SetAccessKey("B");
...
var button4 = new Button { Text = "Access key F, placement top with offsets"
};
button4.Clicked += OnButtonClicked;
button4.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>()
.SetAccessKey("F")
.SetAccessKeyPlacement(AccessKeyPlacement.Top)
.SetAccessKeyHorizontalOffset(20)
.SetAccessKeyVerticalOffset(20);
...
The VisualElement.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>
method specifies that this platform-specific will only run on Windows. The
VisualElement.SetAccessKey method, in the
for displaying the access key tip, with the AccessKeyPlacement enumeration providing
the following possible values:
Auto – indicates that the access key tip placement will be determined by the
operating system.
Top – indicates that the access key tip will appear above the top edge of the
VisualElement.
Bottom – indicates that the access key tip will appear below the lower edge of the
VisualElement.
Right – indicates that the access key tip will appear to the right of the right edge
of the VisualElement.
Left – indicates that the access key tip will appear to the left of the left edge of
the VisualElement.
Center – indicates that the access key tip will appear overlaid on the center of the
VisualElement.
7 Note
Typically, the Auto key tip placement is sufficient, which includes support for
adaptive user interfaces.
or right, and the argument to the SetAccessKeyVerticalOffset method indicates how far
to move the access key tip up or down.
7 Note
Access key tip offsets can't be set when the access key placement is set Auto .
The result is that access key tips can be displayed next to any VisualElement instances
that define access keys, by pressing the Alt key:
When a user activates an access key, by pressing the Alt key followed by the access key,
the default action for the VisualElement will be executed. For example, when a user
activates the access key on a Switch, the Switch is toggled. When a user activates the
access key on an Entry, the Entry gains focus. When a user activates the access key on a
Button, the event handler for the Clicked event is executed.
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IClipboard interface. With this interface, you can copy and paste text to and from the
system clipboard.
Tip
Access to the clipboard must be done on the main user interface thread. For more
information on how to invoke methods on the main user interface thread, see
MainThread.
Using Clipboard
Access to the clipboard is limited to string data. You can check if the clipboard contains
data, set or clear the data, and read the data. The ClipboardContentChanged event is
raised whenever the clipboard data changes.
The following code example demonstrates using a button to set the clipboard data:
C#
The following code example demonstrates using a button to read the clipboard data.
The code first checks if the clipboard has data, read that data, and then uses a null
value with SetTextAsync to clear the clipboard:
C#
if (Clipboard.Default.HasText)
await ClearClipboard();
else
await Clipboard.Default.SetTextAsync(null);
C#
await Clipboard.Default.SetTextAsync(null);
C#
Clipboard.Default.ClipboardContentChanged +=
Clipboard_ClipboardContentChanged;
Share
Article • 03/27/2023 • 3 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IShare interface. This interface provides an API to send data, such as text or web links, to
the devices share function.
When a share request is made, the device displays a share window, prompting the user
to choose an app to share with:
Get started
To access the Share functionality, the following platform-specific setup is required:
Windows
No setup is required.
C#
Text = text,
});
Uri = uri,
});
Share a file
You can also share files to other applications on the device. .NET MAUI automatically
detects the file type (MIME) and requests a share. However, operating systems may
restrict which types of files can be shared. To share a single file, use the ShareFileRequest
type.
The following code example writes a text file to the device, and then requests to share it:
C#
string fn = "Attachment.txt";
});
The following code example writes two text files to the device, and then requests to
share them:
C#
});
Presentation location
) Important
C#
Title = Title,
: Rect.Zero
});
C#
: Rect.Zero
});
C#
looper = looper.Parent;
if (looper is Microsoft.Maui.Controls.View v)
try
PresentationSourceBounds = element.GetAbsoluteBounds(),
Title = "Title",
Text = "Text"
});
catch (Exception)
You can pass in the calling element when the Command is triggered:
XML
<Button Text="Share"
Command="{Binding ShareWithFriendsCommand}"
For an example of the ViewHelpers class, see the .NET MAUI Sample hosted on
GitHub .
Platform differences
This section describes the platform-specific differences with the share API.
Windows
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IFilePicker interface. With the IFilePicker interface, you can prompt the user to pick
Get started
To access the FilePicker functionality, the following platform specific setup is required.
Windows
No setup is required.
) Important
All methods must be called on the UI thread because permission checks and
requests are automatically handled by .NET MAUI.
Pick a file
The PickAsync method prompts the user to pick a file from the device. Use the
PickOptions type to specify the title and file types allowed with the picker. The following
example demonstrates opening the picker and processing the selected image:
C#
try
if (result != null)
if (result.FileName.EndsWith("jpg",
StringComparison.OrdinalIgnoreCase) ||
result.FileName.EndsWith("png",
StringComparison.OrdinalIgnoreCase))
return result;
return null;
C#
{ DevicePlatform.iOS, new[] {
"public.my.comic.extension" } }, // UTType values
});
FileTypes = customFileType,
};
Searching for files based on the file type may be different from one platform to the
other. For more information, see Platform differences.
Tip
The FullPath property doesn't always return the physical path to the file. To get the
file, use the OpenReadAsync method.
Platform differences
This section describes the platform-specific differences with the file picker.
Windows
When filtering by file type, use the file extension, including the . character. For
example, you can filter to GIF files with .gif .
File system helpers
Article • 03/27/2023 • 3 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IFileSystem interface. This interface provides helper methods that access the app's
cache and data directories, and helps access files in the app package.
Cache directory
To get the application's directory to store cache data. Cache data can be used for any
data that needs to persist longer than temporary data, but shouldn't be data that is
required to operate the app, as the operating system may clear this storage.
C#
C#
Bundled files
To open a file that is bundled into the app package, use the OpenAppPackageFileAsync
method and pass the file name. This method returns a read-only Stream representing
the file contents. The following example demonstrates using a method to read the text
contents of a file:
C#
data folder:
C#
content = content.ToUpperInvariant();
string targetFile =
System.IO.Path.Combine(FileSystem.Current.AppDataDirectory, targetFileName);
await streamWriter.WriteAsync(content);
Platform differences
This section describes the platform-specific differences with the file system helpers.
Windows
FileSystem.CacheDirectory
FileSystem.AppDataDirectory
FileSystem.OpenAppPackageFileAsync
Files that were added to the project with the Build Action of MauiAsset can
be opened with this method. .NET MAUI projects will process any file in the
Resources\Raw folder as a MauiAsset.
Preferences
Article • 03/27/2023 • 3 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
IPreferences interface. This interface helps store app preferences in a key/value store.
Storage types
Preferences are stored with a String key. The value of a preference must be one of the
following data types:
Boolean
Double
Int32
Single
Int64
String
DateTime
Values of DateTime are stored in a 64-bit binary (long integer) format using two
methods defined by the DateTime class:
See the documentation of these methods for adjustments that might be made to
decoded values when a DateTime is stored that isn't a Coordinated Universal Time (UTC)
value.
Set preferences
Preferences are set by calling the Preferences.Set method, providing the key and value:
C#
Preferences.Default.Set("first_name", "John");
Preferences.Default.Set("age", 28);
Preferences.Default.Set("has_pets", true);
Get preferences
To retrieve a value from preferences, you pass the key of the preference, followed by the
default value when the key doesn't exist:
C#
C#
C#
Preferences.Default.Remove("first_name");
C#
Preferences.Default.Clear();
Shared keys
The preferences stored by your app are only visible to your app. However, you can also
create a shared preference that can be used by other extensions or a watch app. When
you set, remove, or retrieve a preference, an optional string parameter can be supplied
to specify the name of the container the preference is stored in.
Preferences.Set
Preferences.Get
Preferences.Remove
Preferences.Clear
) Important
Platform differences
This section describes the platform-specific differences with the preferences API.
Windows
sharedName is specified, the LocalSettings are used. Otherwise the name is used to
create a new container inside of LocalSettings .
LocalSettings restricts the preference key names to 255 characters or less. Each
preference value can be up to 8K bytes in size, and each composite setting can be
up to 64 K bytes in size.
Persistence
Uninstalling the application causes all preferences to be removed, except when the app
runs on Android 6.0 (API level 23) or later, while using the Auto Backup feature. This
feature is on by default and preserves app data, including Shared Preferences, which is
what the Preferences API uses. You can disable this by following Google's Auto Backup
documentation .
Limitations
Performance may be impacted if you store large amounts of text, as the API was
designed to store small amounts of text.
Secure storage
Article • 03/27/2023 • 4 minutes to read
This article describes how you can use the .NET Multi-platform App UI (.NET MAUI)
ISecureStorage interface. This interface helps securely store simple key/value pairs.
Get started
To access the SecureStorage functionality, the following platform-specific setup is
required:
Windows
No setup is required.
Tip
It's possible that an exception is thrown when calling GetAsync or SetAsync . This
can be caused by a device not supporting secure storage, encryption keys
changing, or corruption of data. it's best to handle this by removing and adding the
setting back if possible.
Write a value
To save a value for a given key in secure storage:
C#
Read a value
To retrieve a value from secure storage:
C#
if (oauthToken == null)
Tip
If there isn't a value associated with the key, GetAsync returns null .
Remove a value
To remove a specific value, remove the key:
C#
C#
SecureStorage.Default.RemoveAll();
Platform differences
This section describes the platform-specific differences with the secure storage API.
Windows
Limitations
Performance may be impacted if you store large amounts of text, as the API was
designed to store small amounts of text.
Platform helpers
Article • 03/27/2023 • 2 minutes to read
Windows
Method Purpose
MapServiceToken A property that gets or sets the map service API key.
OnLaunched The lifecycle method that's called when the app is launched.
.NET Multi-platform App UI (.NET MAUI) apps use multi-targeting to target multiple
platforms from a single project.
The project for a .NET MAUI app contains a Platforms folder, with each child folder
representing a platform that .NET MAUI can target:
The folders for each target platform contain platform-specific code that starts the app
on each platform, plus any additional platform code you add. At build time, the build
system only includes the code from each folder when building for that specific platform.
For example, when you build for Android the files in the Platforms > Android folder will
be built into the app package, but the files in the other Platforms folders won't be.
In addition to this default multi-targeting approach, .NET MAUI apps can also be multi-
targeted based on your own filename and folder criteria. This enables you to structure
your .NET MAUI app project so that you don't have to place your platform code into
sub-folders of the Platforms folder.
XML
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-android')) !=
true">
<None Include="**\**\*.Android.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\**\*.MaciOS.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\**\*.iOS.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-maccatalyst')) !=
true">
<None Include="**\**\*.MacCatalyst.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\*.Windows.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
This XML configures the build system to remove platform-based filename patterns
under specific conditions:
Don't compile C# code whose filename ends with .Android.cs, if you aren't building
for Android.
Don't compile C# code whose filename ends with .MaciOS.cs, if you aren't building
for iOS and Mac Catalyst.
Don't compile C# code whose filename ends with .iOS.cs, if you aren't building for
iOS.
Don't compile C# code whose filename ends with .MacCatalyst.cs, if you aren't
building for Mac Catalyst.
Don't compile C# code whose filename ends with .Windows.cs, if you aren't
building for Windows.
) Important
Filename-based multi-targeting can be combined with folder-based multi-
targeting. For more information, see Combine filename and folder multi-
targeting.
XML
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-android')) !=
true">
<None Include="**\Android\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\MaciOS\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\iOS\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-maccatalyst')) !=
true">
<None Include="**\MacCatalyst\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\Windows\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
This XML configures the build system to remove platform-based folder patterns under
specific conditions:
Don't compile C# code that's located in the Android folder, or sub-folder of the
Android folder, if you aren't building for Android.
Don't compile C# code that's located in the MaciOS folder, or sub-folder of the
MaciOS folder, if you aren't building for iOS and Mac Catalyst.
Don't compile C# code that's located in the iOS folder, or sub-folder of the iOS
folder, if you aren't building for iOS.
Don't compile C# code that's located in the MacCatalyst folder, or sub-folder of
the MacCatalyst folder, if you aren't building for Mac Catalyst.
Don't compile C# code that's located in the Windows folder, or sub-folder of the
Windows folder, if you aren't building for Windows.
) Important
XML
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-android')) !=
true">
<None Include="**\**\*.Android.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
<None Include="**\Android\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\**\*.MaciOS.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
<None Include="**\MaciOS\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\**\*.iOS.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
<None Include="**\iOS\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-maccatalyst')) !=
true">
<None Include="**\**\*.MacCatalyst.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
<None Include="**\MacCatalyst\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<None Include="**\*.Windows.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
<None Include="**\Windows\**\*.cs"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
This XML configures the build system to remove platform-based filename and folder
patterns under specific conditions:
Don't compile C# code whose filename ends with .Android.cs, or that's located in
the Android folder or sub-folder of the Android folder, if you aren't building for
Android.
Don't compile C# code whose filename ends with .MaciOS.cs, or that's located in
the MaciOS folder or sub-folder of the MaciOS folder, if you aren't building for iOS
and Mac Catalyst.
Don't compile C# code whose filename ends with .iOS.cs, or that's located in the
iOS folder or sub-folder of the iOS folder, if you aren't building for iOS.
Don't compile C# code whose filename ends with .MacCatalyst.cs, or that's located
in the MacCatalyst folder or sub-folder of the MacCatalyst folder, if you aren't
building for Mac Catalyst.
Don't compile C# code whose filename ends with .Windows.cs, or that's located in
the Windows folder or sub-folder of the Windows folder, if you aren't building for
Windows.
Invoke platform code
Article • 07/12/2022 • 5 minutes to read
In situations where .NET Multi-platform App UI (.NET MAUI) doesn't provide any APIs for
accessing specific platform APIs, you can write your own code to access the required
platform APIs. This requires knowledge of Apple's iOS and MacCatalyst APIs , Google's
Android APIs , and Microsoft's Windows App SDK APIs.
Conditional compilation
Platform code can be invoked from cross-platform code by using conditional
compilation to target different platforms.
The following example shows the DeviceOrientation enumeration, which will be used to
specify the orientation of your device:
C#
namespace InvokePlatformCodeDemos.Services
Undefined,
Landscape,
Portrait
Retrieving the orientation of your device requires writing platform code. This can be
accomplished by writing a method that uses conditional compilation to target different
platforms:
C#
#if ANDROID
using Android.Content;
using Android.Views;
using Android.Runtime;
#elif IOS
using UIKit;
#endif
using InvokePlatformCodeDemos.Services;
namespace InvokePlatformCodeDemos.Services.ConditionalCompilation
#if ANDROID
IWindowManager windowManager =
Android.App.Application.Context.GetSystemService(Context.WindowService).Java
Cast<IWindowManager>();
SurfaceOrientation orientation =
windowManager.DefaultDisplay.Rotation;
#elif IOS
UIInterfaceOrientation orientation =
UIApplication.SharedApplication.StatusBarOrientation;
#else
return DeviceOrientation.Undefined;
#endif
provided for:
C#
C#
using InvokePlatformCodeDemos.Services;
using InvokePlatformCodeDemos.Services.ConditionalCompilation;
...
At build time the build system uses conditional compilation to target Android and iOS
platform code to the correct platform.
The folders for each target platform contain platform-specific code that starts the app
on each platform, plus any additional platform code you add. At build time, the build
system only includes the code from each folder when building for that specific platform.
For example, when you build for Android the files in the Platforms > Android folder will
be built into the app package, but the files in the other Platforms folders won't be. This
approach uses a feature called multi-targeting to target multiple platforms from a single
project.
Multi-targeting can be combined with partial classes and partial methods to invoke
platform functionality from cross-platform code. The process for doing this is to:
1. Define the cross-platform API as a partial class that defines partial method
signatures for any operations you want to invoke on each platform. For more
information, see Define the cross-platform API.
2. Implement the cross-platform API per platform, by defining the same partial class
and the same partial method signatures, while also providing the method
implementations. For more information, see Implement the API per platform.
3. Invoke the cross-platform API by creating an instance of the partial class and
invoking its methods as required. For more information, see Invoke the cross-
platform API.
The following example shows the DeviceOrientation enumeration, which will be used to
specify the orientation of your device:
C#
namespace InvokePlatformCodeDemos.Services
Undefined,
Landscape,
Portrait
The following example shows a cross-platform API that can be used to retrieve the
orientation of a device:
C#
namespace InvokePlatformCodeDemos.Services.PartialMethods
Platform Folder
) Important
Platform implementations must be in the same namespace and same class that the
cross-platform API was defined in.
Android
C#
using Android.Content;
using Android.Runtime;
using Android.Views;
namespace InvokePlatformCodeDemos.Services.PartialMethods;
IWindowManager windowManager =
Android.App.Application.Context.GetSystemService(Context.WindowService).Java
Cast<IWindowManager>();
SurfaceOrientation orientation =
windowManager.DefaultDisplay.Rotation;
iOS
The following example shows the implementation of the GetOrientation method on
iOS:
C#
using UIKit;
namespace InvokePlatformCodeDemos.Services.PartialMethods;
UIInterfaceOrientation orientation =
UIApplication.SharedApplication.StatusBarOrientation;
C#
using InvokePlatformCodeDemos.Services;
using InvokePlatformCodeDemos.Services.PartialMethods;
...
At build time the build system will use multi-targeting to combine the cross-platform
partial class with the partial class for the target platform, and build it into the app
package.
Configure multi-targeting
.NET MAUI apps can also be multi-targeted based on your own filename and folder
criteria. This enables you to structure your .NET MAUI app project so that you don't have
to place your platform code into child folders of the Platforms folder.
Typically, a .NET Multi-platform App UI (.NET MAUI) app includes pages that contain
layouts, such as Grid, and layouts that contain views, such as Button. Pages, layouts, and
views all derive from Element. Native embedding enables any .NET MAUI controls that
derive from Element to be consumed in .NET for Android, .NET for iOS, .NET for Mac
Catalyst, and WinUI native apps.
The process for consuming a .NET MAUI control in a native app is as follows:
7 Note
When using native embedding, .NET MAUI's data binding engine still works.
However, page navigation must be performed using the native navigation API.
XML
<PropertyGroup>
...
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<UseMaui>true</UseMaui>
</PropertyGroup>
A consequence of doing this is that it replaces the native implicit namespace support
with .NET MAUI namespaces, so you'll have to explicitly add using statements to your
code files for native types.
XML
<PropertyGroup>
...
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<UseMaui>true</UseMaui>
<EnableDefaultXamlItems>false</EnableDefaultXamlItems>
</PropertyGroup>
This will stop you receiving build errors about the InitializeComponent method already
being defined.
Typically, the pattern for initializing .NET MAUI in a native app project is to create a
MauiAppBuilder object, invoke the UseMauiEmbedding method on it, and then create a
MauiApp object by invoking the Build() method on the MauiAppBuilder object. In
addition, a MauiContext object should then be created from the MauiApp object. The
MauiContext object will be used when converting .NET MAUI controls to native types.
The examples in the following sections show the UseMauiEmbedding method being
invoked at app startup.
Android
On Android, the OnCreate override in the MainActivity class is typically the place to
perform app startup related tasks. The following code example shows .NET MAUI being
initialized in the MainActivity class:
C#
using Android.App;
using Android.OS;
using Android.Views;
using AndroidX.AppCompat.App;
using Microsoft.Maui.Embedding;
namespace Notes.Android;
MauiContext _mauiContext;
base.OnCreate(savedInstanceState);
builder.UseMauiEmbedding<Microsoft.Maui.Controls.Application>();
...
C#
using Foundation;
using Microsoft.Maui.Embedding;
using UIKit;
namespace Notes.MaciOS;
[Register("AppDelegate")]
MauiContext _mauiContext;
get;
set;
builder.UseMauiEmbedding<Microsoft.Maui.Controls.Application>();
builder.Services.Add(new
Microsoft.Extensions.DependencyInjection.ServiceDescriptor(typeof(UIWindow),
Window));
...
return true;
Windows
On Windows apps built using WinUI, .NET MAUI can typically be initialized in the native
App class, Window class, or a Page class. The following code example shows .NET MAUI
C#
using Microsoft.Maui;
using Microsoft.Maui.Embedding;
using Microsoft.Maui.Hosting;
using Microsoft.UI.Xaml.Controls;
namespace Notes.Windows;
public MainPage()
builder.UseMauiEmbedding<Microsoft.Maui.Controls.Application>();
...
Android
On Android, ToPlatform converts the .NET MAUI control to an Android View object:
C#
C#
using Android.OS;
using Android.Views;
using Microsoft.Maui.Platform;
namespace Notes.Android;
DetailView = detailView;
_mauiContext = mauiContext;
return DetailView.ToPlatform(_mauiContext);
C#
AndroidX.Fragment.App.Fragment notesPageFragment =
myMauiPage.CreateSupportFragment(_mauiContext);
C#
C#
UIViewController myPageController =
myMauiPage.ToUIViewController(_mauiContext);
Windows
On Windows, ToPlatform converts the .NET MAUI control to a FrameworkElement object:
C#
The SQLite database engine allows .NET Multi-platform App UI (.NET MAUI) apps to
load and save data objects in shared code. You can integrate SQLite.NET into .NET MAUI
apps, to store and retrieve information in a local database, by following these steps:
The sample app uses an SQLite database table to store todo items.
There are a number of NuGet packages with similar names. The correct package has
these attributes:
ID: sqlite-net-pcl
Authors: SQLite-net
Owners: praeclarum
NuGet link: sqlite-net-pcl
Despite the package name, use the sqlite-net-pcl NuGet package in .NET MAUI projects.
) Important
Install SQLitePCLRaw.bundle_green
In addition to sqlite-net-pcl, you temporarily need to install the underlying dependency
that exposes SQLite on each platform:
ID: SQLitePCLRaw.bundle_green
Version: 2.1.2
Authors: Eric Sink
Owners: Eric Sink
NuGet link: SQLitePCLRaw.bundle_green
C#
SQLite.SQLiteOpenFlags.ReadWrite |
SQLite.SQLiteOpenFlags.Create |
SQLite.SQLiteOpenFlags.SharedCache;
Path.Combine(FileSystem.AppDataDirectory, DatabaseFilename);
In this example, the constants file specifies default SQLiteOpenFlag enum values that are
used to initialize the database connection. The SQLiteOpenFlag enum supports these
values:
Create : The connection will automatically create the database file if it doesn't exist.
FullMutex : The connection is opened in serialized threading mode.
PrivateCache : The connection will not participate in the shared cache, even if it's
enabled.
ReadWrite : The connection can read and write data.
SharedCache : The connection will participate in the shared cache, if it's enabled.
locked.
ProtectionCompleteUnlessOpen : The file is encrypted until it's opened but is then
You may need to specify different flags depending on how your database will be used.
For more information about SQLiteOpenFlags , see Opening A New Database
Connection on sqlite.org.
Lazy initialization
The TodoItemDatabase uses asynchronous lazy initialization to delay initialization of the
database until it's first accessed, with a simple Init method that gets called by each
method in the class:
C#
SQLiteAsyncConnection Database;
public TodoItemDatabase()
return;
...
The following example shows the data manipulation methods in the sample app:
C#
...
await Init();
await Init();
await Init();
await Init();
if (item.ID != 0)
else
await Init();
Access data
The TodoItemDatabase class can be registered as a singleton that can be used
throughout the app if you are using dependency injection. For example, you can register
your pages and the database access class as services on the IServiceCollection object,
in MauiProgram.cs, with the AddSingleton and AddTransient methods:
C#
builder.Services.AddSingleton<TodoListPage>();
builder.Services.AddTransient<TodoItemPage>();
builder.Services.AddSingleton<TodoItemDatabase>();
These services can then be automatically injected into class constructors, and accessed:
C#
TodoItemDatabase database;
InitializeComponent();
database = todoItemDatabase;
if (string.IsNullOrWhiteSpace(Item.Name))
return;
await database.SaveItemAsync(Item);
await Shell.Current.GoToAsync("..");
C#
TodoItemDatabase database;
public TodoItemPage()
InitializeComponent();
Advanced configuration
SQLite provides a robust API with more features than are covered in this article and the
sample app. The following sections cover features that are important for scalability.
Write-ahead logging
By default, SQLite uses a traditional rollback journal. A copy of the unchanged database
content is written into a separate rollback file, then the changes are written directly to
the database file. The COMMIT occurs when the rollback journal is deleted.
Write-Ahead Logging (WAL) writes changes into a separate WAL file first. In WAL mode,
a COMMIT is a special record, appended to the WAL file, which allows multiple
transactions to occur in a single WAL file. A WAL file is merged back into the database
file in a special operation called a checkpoint.
WAL can be faster for local databases because readers and writers do not block each
other, allowing read and write operations to be concurrent. However, WAL mode
doesn't allow changes to the page size, adds additional file associations to the database,
and adds the extra checkpointing operation.
C#
await Database.EnableWriteAheadLoggingAsync();
Copy a database
There are several cases where it may be necessary to copy a SQLite database:
A database has shipped with your application but must be copied or moved to
writeable storage on the mobile device.
You need to make a backup or copy of the database.
You need to version, move, or rename the database file.
In general, moving, renaming, or copying a database file is the same process as any
other file type with a few additional considerations:
Representational State Transfer (REST) is an architectural style for building web services.
REST requests are typically made over HTTPS using the same HTTP verbs that web
browsers use to retrieve web pages and to send data to servers. The verbs are:
GET – this operation is used to retrieve data from the web service.
POST – this operation is used to create a new item of data on the web service.
PUT – this operation is used to update an item of data on the web service.
PATCH – this operation is used to update an item of data on the web service by
describing a set of instructions about how the item should be modified.
DELETE – this operation is used to delete an item of data on the web service.
A base URI.
HTTP methods, such as GET, POST, PUT, PATCH, or DELETE.
A media type for the data, such as JavaScript Object Notation (JSON).
REST-based web services typically use JSON messages to return data to the client. JSON
is a text-based data-interchange format that produces compact payloads, which results
in reduced bandwidth requirements when sending data. The simplicity of REST has
helped make it the primary method for accessing web services in mobile apps.
C#
The ID property is used to uniquely identify each TodoItem object, and is used by the
web service to identify data to be updated or deleted. For example, to delete the
TodoItem whose ID is 6bb8a868-dba1-4f1a-93b7-24ebce87e243 , the .NET MAUI app sends
When the Web API framework receives a request, it routes the request to an action.
These actions are public methods in the TodoItemsController class. The Web API
framework uses routing middleware to match the URLs of incoming requests and map
them to actions. REST APIs should use attribute routing to model the app's functionality
as a set of resources whose operations are represented by HTTP verbs. Attribute routing
uses a set of attributes to map actions directly to route templates. For more information
about attribute routing, see Attribute routing for REST APIs. For more information about
building the REST service using ASP.NET Core, see Creating backend services for native
mobile applications.
The HttpClient object should be declared at the class-level so that it lives for as long as
the app needs to make HTTP requests:
C#
HttpClient _client;
JsonSerializerOptions _serializerOptions;
public RestService()
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
...
Retrieve data
The HttpClient.GetAsync method is used to send a GET request to the web service
specified by the URI, and then receive the response from the web service:
C#
try
if (response.IsSuccessStatusCode)
Items = JsonSerializer.Deserialize<List<TodoItem>>(content,
_serializerOptions);
return Items;
If the HTTP operation was successful, the content of the response is read. The
HttpResponseMessage.Content property represents the content of the response, and is of
type HttpContent. The HttpContent class represents the HTTP body and content
headers, such as Content-Type and Content-Encoding . The content is then read into a
string using the HttpContent.ReadAsStringAsync method. The string is then
deserialized from JSON to a List of TodoItem objects.
2 Warning
Create data
The HttpClient.PostAsync method is used to send a POST request to the web service
specified by the URI, and then to receive the response from the web service:
C#
try
if (isNewItem)
else
if (response.IsSuccessStatusCode)
In this example, the TodoItem instance is serialized to a JSON payload for sending to the
web service. This payload is then embedded in the body of the HTTP content that will be
sent to the web service before the request is made with the PostAsync method.
The REST service sends an HTTP status code in its response, which can be obtained from
the HttpResponseMessage.IsSuccessStatusCode property, to indicate whether the HTTP
request succeeded or failed. The typical responses for this operation are:
201 (CREATED) – the request resulted in a new resource being created before the
response was sent.
400 (BAD REQUEST) – the request is not understood by the server.
409 (CONFLICT) – the request could not be carried out because of a conflict on
the server.
Update data
The HttpClient.PutAsync method is used to send a PUT request to the web service
specified by the URI, and then receive the response from the web service:
C#
...
...
The operation of the PutAsync method is identical to the PostAsync method that's used
for creating data in the web service. However, the possible responses sent from the web
service differ.
The REST service sends an HTTP status code in its response, which can be obtained from
the HttpResponseMessage.IsSuccessStatusCode property, to indicate whether the HTTP
request succeeded or failed. The typical responses for this operation are:
204 (NO CONTENT) – the request has been successfully processed and the
response is intentionally blank.
400 (BAD REQUEST) – the request is not understood by the server.
404 (NOT FOUND) – the requested resource does not exist on the server.
Delete data
The HttpClient.DeleteAsync method is used to send a DELETE request to the web
service specified by the URI, and then receive the response from the web service:
C#
try
if (response.IsSuccessStatusCode)
The REST service sends an HTTP status code in its response, which can be obtained from
the HttpResponseMessage.IsSuccessStatusCode property, to indicate whether the HTTP
request succeeded or failed. The typical responses for this operation are:
204 (NO CONTENT) – the request has been successfully processed and the
response is intentionally blank.
400 (BAD REQUEST) – the request is not understood by the server.
404 (NOT FOUND) – the requested resource does not exist on the server.
Local development
If you're developing a REST web service locally with a framework such as ASP.NET Core
Web API, you can debug your web service and .NET MAUI app at the same time. In this
scenario, to consume your web service over HTTP from Android emulators and iOS
simulators, you must enable clear-text HTTP traffic in your .NET MAUI app. For more
information, see Connect to local web services from Android emulators and iOS
simulators.
Connect to local web services from
Android emulators and iOS simulators
Article • 11/08/2022 • 8 minutes to read
Many mobile and desktop apps consume web services. During the software
development phase, it's common to deploy a web service locally and consume it from
an app running in the Android emulator or iOS simulator. This avoids having to deploy
the web service to a hosted endpoint, and enables a straightforward debugging
experience because both the app and web service are running locally.
.NET Multi-platform App UI (.NET MAUI) apps that run on Windows or MacCatalyst can
consume ASP.NET Core web services that are running locally over HTTP or HTTPS
without any additional work, provided that you've trusted your development certificate.
However, additional work is required when the app is running in the Android emulator
or iOS simulator, and the process is different depending on whether the web service is
running over HTTP or HTTPS.
Android
Each instance of the Android emulator is isolated from your development machine
network interfaces, and runs behind a virtual router. Therefore, an emulated device can't
see your development machine or other emulator instances on the network.
However, the virtual router for each emulator manages a special network space that
includes pre-allocated addresses, with the 10.0.2.2 address being an alias to your host
loopback interface (127.0.0.1 on your development machine). Therefore, given a local
web service that exposes a GET operation via the /api/todoitems/ relative URI, an app
running on the Android emulator can consume the operation by sending a GET request
to http://10.0.2.2:<port>/api/todoitems/ or https://10.0.2.2:<port>/api/todoitems/ .
iOS
The iOS simulator uses the host machine network. Therefore, apps running in the
simulator can connect to web services running on your local machine via the machines
IP address or via the localhost hostname. For example, given a local web service that
exposes a GET operation via the /api/todoitems/ relative URI, an app running on the
iOS simulator can consume the operation by sending a GET request to
http://localhost:<port>/api/todoitems/ or https://localhost:<port>/api/todoitems/ .
7 Note
When running a .NET MAUI app in the iOS simulator from Windows, the app is
displayed in the remote iOS simulator for Windows. However, the app is running
on the paired Mac. Therefore, there's no localhost access to a web service running
in Windows for an iOS app running on a Mac.
In the code that defines the URL of your local web service in your .NET MAUI app,
ensure that the web service URL specifies the HTTP scheme, and the correct hostname.
The DeviceInfo class can be used to detect the platform the app is running on. The
correct hostname can then be set as follows:
C#
For more information about the DeviceInfo class, see Device information.
In addition, to run your app on Android you must add the required network security
configuration, and to run your app on iOS you must opt-out of Apple Transport Security
(ATS). For more information, see Android network security configuration and iOS ATS
configuration.
You must also ensure that your ASP.NET Core web service is configured to allow HTTP
traffic. This can be achieved by adding a HTTP profile to the profiles section of
launchSettings.json in your ASP.NET Core web service project:
JSON
...
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "api/todoitems",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
...
A .NET MAUI app running in the Android emulator or iOS simulator can then consume
an ASP.NET Core web service that's running locally over HTTP, provided that web service
is launched with the http profile.
XML
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>
XML
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<application
android:networkSecurityConfig="@xml/network_security_config" ...>
...
</application>
</manifest>
For more information about network security configuration files, see Network security
configuration on developer.android.com.
XML
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
For more information about ATS, see Preventing Insecure Network Connections on
developer.apple.com.
.NET CLI
.NET CLI
Alternatively, when you run an ASP.NET Core 2.1 project (or above), that uses HTTPS,
Visual Studio will detect if the development certificate is missing and will offer to install
it and trust it.
7 Note
For more information about enabling local HTTPS on your machine, see Enable local
HTTPS.
C#
For more information about the DeviceInfo class, see Device information.
NSUrlSessionHandler class.
The following example shows a class that configures the AndroidMessageHandler class on
Android and the NSUrlSessionHandler class on iOS to trust localhost communication
over HTTPS:
C#
#if ANDROID
return true;
};
return handler;
#elif IOS
TrustOverrideForUrl = IsHttpsLocalhost
};
return handler;
#else
#endif
#if IOS
if (url.StartsWith("https://localhost"))
return true;
return false;
#endif
object to a callback that ignores the result of the certificate security check for the local
HTTPS development certificate.
IsHttpsLocalHost delegate returns true when the URL starts with https://localhost .
C#
#if DEBUG
#else
#endif
A .NET MAUI app running in the Android emulator or iOS simulator can then consume
an ASP.NET Core web service that's running locally over HTTPS.
Deployment
Article • 03/23/2023 • 4 minutes to read
.NET Multi-platform App UI (.NET MAUI) uses a single-project system to manage the
configuration of your cross-platform app. This configuration includes properties and
settings that control building and packaging the app on each platform. For more
information, see Project configuration for .NET MAUI apps.
Android
You can debug and test your apps on the Android emulator, which can be run in a
variety of configurations to simulate different devices. Each configuration is called a
virtual device. When you deploy and test your apps on the emulator, you select a pre-
configured or custom virtual device that simulates a physical Android device such as a
Pixel phone. For more information, see Debug on the Android Emulator.
While the Android emulator enables you to rapidly develop and test your apps, you'll
also want to test your apps on a real Android device. To run on a device, you'll need to
enable developer mode on the device and connect it to your development machine. For
more information, see Set up Android device for debugging.
When distributing a .NET MAUI app for Android, you generate an apk (Android Package)
or an aab (Android App Bundle) file. The apk is used for installing your app to an
Android device, and the aab is used to publish your app to an Android store. With just a
few configuration changes to your project, your app can be packaged for distribution.
For more information, see Publish a .NET MAUI app for Android.
iOS
Building native iOS apps using .NET MAUI requires access to Apple's build tools, which
only run on a Mac. Because of this, Visual Studio must connect to a network-accessible
Mac to build .NET MAUI iOS apps. Visual Studio's Pair to Mac feature discovers,
connects to, authenticates with, and remembers Mac build hosts so that you can work
productively on Windows. For more information, see Pair to Mac for iOS development.
When combined with Pair to Mac, the remote iOS Simulator for Windows allows you to
test your apps on an iOS simulator displayed in Windows alongside Visual Studio. For
more information, see Remote iOS Simulator for Windows.
While developing a .NET MAUI iOS app it's essential to test it by deploying the app to a
physical device, in addition to the simulator. Device-only bugs and performance issues
can transpire when running on a device, due to hardware limitations such as memory or
network connectivity. To test an app on a physical device, the device must be
provisioned, and Apple must be informed that the device will be used for testing. For
more information about device provisioning, see Device provisioning for iOS.
Rather than having to use a USB cable to connect an iOS device to your Mac to deploy
and debug a .NET MAUI app, Visual Studio and Visual Studio for Mac can deploy .NET
MAUI iOS apps to devices wirelessly, and debug them wirelessly. For more information,
see Wireless deployment for .NET MAUI iOS apps.
Alternatively, hot restart enables you to quickly deploy iOS apps to a 64-bit local device,
from Visual Studio, without requiring a Mac build host. It also removes the need for a
full package rebuild by pushing new changes to the existing app bundle that's already
present on your locally connected iOS device. It supports changes to code files,
resources, and project references, enabling you to quickly test changes to your apps
during their development. For more information, see Deploy an iOS app to a local
device using hot restart.
When distributing a .NET MAUI app for iOS, you generate an .ipa file. An .ipa file is an
iOS app archive file that stores an iOS app. Distributing a .NET MAUI app on iOS
requires that the app is provisioned using a provisioning profile. Provisioning profiles
are files that contain code signing information, as well as the identity of the app and its
intended distribution mechanism. For more information about publishing an .NET MAUI
app for iOS, see Publish a .NET MAUI app for iOS.
On iOS, .NET MAUI apps run in a sandbox that provides a set of rules that limit access
between the app and system resources or user data. Apple provides capabilities, also
known as app services, as a means of extending functionality and widening the scope of
what iOS apps can do. Capabilities are added to your app's provisioning profile, and are
used when code signing your app. For more information, see Capabilities.
Mac Catalyst
When distributing your .NET MAUI Mac Catalyst app, you generate an .app or a .pkg file.
An .app file is a self-contained app that can be run without installation, whereas a .pkg is
an app packaged in an installer. Distributing a .NET MAUI Mac Catalyst app requires that
the app is provisioned using a provisioning profile. Provisioning profiles are files that
contain code signing information, as well as the identity of the app and its intended
distribution mechanism. For more information about publishing an .NET MAUI Mac
Catalyst app, see Publish a .NET MAUI Mac Catalyst app.
Windows
You can test and debug your apps on a local Windows machine, which requires you to
enable Developer Mode. For more information, see Deploy and debug your .NET MAUI
app on Windows.
When distributing a .NET MAUI app for Windows, you can publish the app and its
dependencies to a folder for deployment to another system. You can also package the
app into an MSIX package, which has numerous benefits for the users installing your
app. For more information, see Publish a .NET MAUI app for Windows.
Deploy an iOS app using hot restart
Article • 03/02/2023 • 6 minutes to read
Typically when building an app, your code is compiled and combined with other project
resources to build an app bundle that's deployed to your simulator or device. With this
model, when you make a change to your app, a new app bundle has to be built and
deployed. While incremental builds can help to reduce compilation time, deployments
usually take the same amount of time regardless of the size of the change.
.NET Multi-platform App UI (.NET MAUI) hot restart enables you to quickly deploy a
.NET MAUI app to a 64-bit local iOS device, from Visual Studio 2022, without requiring a
Mac build host. It removes the need for a full app bundle rebuild by pushing changes to
the existing app bundle that's already present on your locally connected iOS device. It
supports changes to code files, resources, and project references, enabling you to
quickly test changes to your app during its development.
) Important
Hot restart can only be used to deploy apps that use the debug build configuration.
You'll still need a Mac build host to build, sign, and deploy your app for production
purposes.
There are a number of requirements that must be met to use hot restart to deploy a
.NET MAUI app to a locally connected iOS device:
Setup
Perform the following steps to set up hot restart:
1. In the Visual Studio toolbar, use the Debug Target drop-down to select iOS Local
Devices and then the Local Device entry:
2. In the Visual Studio toolbar, select Local Device:
The Setup Hot Restart setup wizard will appear, which will guide you through
setting up a local iOS device for hot restart deployment.
Wait for iTunes to download and then install it. If you install it from the Microsoft
Store, once the installation completes please open it, then follow additional
prompts to enable it to discover locally connected devices.
5. In the Setup Hot Restart setup wizard, select Next to move to the next step of the
wizard that will prompt you to connect a local iOS device:
6. Connect your iOS device to your development machine via a USB cable. A prompt
may appear on your device asking you to trust your development machine. On
your device, click Trust and follow any additional device prompts.
7. In the Setup Hot Restart setup wizard, select Next once your local iOS device is
detected:
7 Note
If the setup wizard fails to detect your local iOS device, disconnect then
reconnect your local iOS device from your development machine. In addition,
ensure that iTunes recognizes your local iOS device.
8. In the Setup Hot Restart setup wizard, click the Sign in with an individual account
hyperlink to configure hot restart to use your individual Apple Developer Program
account:
The Individual account dialog appears.
7 Note
9. Create an App Store Connect API key. This will require you to have an Apple
Developer account and paid Apple Developer Program enrollment. For
information about creating an App Store Connect API key, see Creating API Keys
for App Store Connect API on developer.apple.com.
10. In the Individual account dialog, enter your App Store Connect API key data:
The Name, Issuer ID, and Key ID data can be found in App Store Connect by
selecting Users and Access and then the Keys tab. The Private key can also be
downloaded from this location:
11. In the Individual account dialog, click the Add button. The Individual account
dialog will close.
12. In the Setup Hot Restart setup wizard, click the Finish button:
Your Apple Developer Program account will be added to Visual Studio and the
Setup Hot Restart setup wizard will close.
14. In the project properties, expand iOS and select Bundle Signing. Use the Scheme
drop-down to select Automatic Provisioning and then click the Configure
Automatic Provisioning hyperlink:
Visual Studio will complete the automatic provisioning process. Then, click the Ok
button to dismiss the Configure Automatic Provisioning dialog.
7 Note
2. In the Visual Studio toolbar, select your local connected iOS device in the debug
target drop-down, and click the Run button to build your app and deploy it to
your local iOS device:
3. After deploying your app, Visual Studio will display the Connecting Debugger
dialog:
Launch the app on your device and Visual Studio will connect the debugger to
your running app, and the Connecting Debugger dialog will be dismissed.
While you're debugging your app, you can edit your C# code and press the restart
button in the Visual Studio toolbar to restart your debug session with the new changes
applied:
In these scenarios, the HOTRESTART preprocessor symbol can be used to prevent code
from executing when debugging with hot restart:
C#
#if !HOTRESTART
#endif
iOS uses a watchdog that monitors app launch times and responsiveness, and
terminates unresponsive apps. For example, the watchdog terminates apps that block
the main thread for a significant time. On old iOS devices, the watchdog may terminate
an app that's been deployed using hot restart before the debugger has connected to it.
The workaround is to reduce the amount of processing performed in the app's startup
path, and to use a more recent iOS device.
To report additional issues, please use the feedback tool at Help > Send Feedback >
Report a Problem .
Wireless deployment for .NET MAUI iOS
apps
Article • 12/23/2022 • 4 minutes to read
Rather than having to use a USB cable to connect an iOS device to your Mac to deploy
and debug a .NET Multi-platform App UI (.NET MAUI) app, Visual Studio and Visual
Studio for Mac can deploy .NET MAUI iOS apps to devices wirelessly, and debug them
wirelessly. To do this you must pair your iOS device with Xcode on your Mac. Once
paired, the device can be selected from the device target list in Visual Studio and Visual
Studio for Mac.
) Important
.NET MAUI iOS apps must have been provisioned before they can be deployed to a
device for testing and debugging. For more information, see Device provisioning
for iOS.
1. Ensure that your iOS device is connected to the same wireless network as your
Mac.
2. Plug your iOS device into your Mac using a USB cable.
7 Note
The first time you connect an iOS device to your Mac, you'll need to tap the
Trust button in the Trust This Computer dialog on your device.
3. Open Xcode and click Window > Devices and Simulators. In the window that
appears, click Devices.
4. In the Devices and Simulators window, in the left column, select your device. Then
in the detail area select the Connect via network checkbox:
Xcode pairs with the iOS device.
5. In the Devices and Simulators window, in the left column, a network icon will
appear next to a connected device that's paired:
6. Disconnect the USB cable and check that the device remains paired.
Xcode will retain the pairing settings, so the device shouldn't need to be paired again.
1. Ensure that your iOS device is connected to the same wireless network as your
Mac.
2. Open Xcode and click Window > Devices and Simulators. In the window that
appears, click Devices.
3. In the Devices and Simulators window, in the left column, select your paired
device. Then right-click the device and select the Unpair Device item.
Deploy to device
After wirelessly pairing your device to Xcode, provisioned .NET MAUI iOS apps can be
wirelessly deployed to the device.
Visual Studio
1. Ensure that your iOS device is wirelessly paired to your Mac build host. For
more information, see Pair an iOS device.
2. In Visual Studio, ensure that the IDE is paired to a Mac Build host. For more
information, see Pair to Mac for iOS development.
3. In the Visual Studio toolbar, use the Debug Target drop-down to select iOS
Remote Devices and then the device that's connected to your Mac build host:
4. In the Visual Studio toolbar, press the green Start button to launch the app on
your remote device:
7 Note
Troubleshoot
Ensure that your iOS device is connected to the same network as your Mac.
Ensure that your device is provisioned. For more information about provisioning,
see Device provisioning for iOS.
Verify that Xcode can see the device:
In Xcode, choose Window > Devices and Simulators, and in the window that
appears click Devices. The device should appear under Connected.
Ping the device:
Find the device's IP address. On the device open Settings, tap Wi-Fi, and then
tap the information button next to the network that's active.
On a Mac, open Terminal and type ping followed by the device's IP address.
Provided that your Mac can see the device, you'll receive output similar to:
zsh
PING 192.168.1.107 (192.168.1.107): 56 data bytes
If there's an error, the output will be Request timeout for icmp_seq 0 . If you
can't ping the device, then the Internet Control Message Protocol (ICMP) is
blocked or there's another connectivity issue.
Ensure that port 62078 is open.
Connect the device to the network using an Ethernet cable:
Use the Lightning to USB Camera Adapter and a USB to Ethernet adapter.
Re-pair the iOS device:
Unpair the device. For more information, see Unpair an iOS device.
Pair the iOS device with Xcode. For more information, see Pair an iOS device.
Project configuration for .NET MAUI
apps
Article • 01/13/2023 • 17 minutes to read
.NET MAUI uses a single-project system to manage the configuration of your cross-
platform app. Project configuration in .NET MAUI is similar to other projects in Visual
Studio, right-click on the project in the Solution Explorer, and select Properties.
Application
The Application section describes some settings related to which platforms your app
targets, as well as the output file and default namespace.
General
Assembly $(MSBuildProjectName) Specifies the name of the output file that will hold
name the assembly manifest.
iOS Targets
If you're going to target iOS and macOS (using Mac Catalyst), these settings
describe the target iOS version.
Target the iOS platform Checked Specifies that this project will target the
iOS platform.
Minimum Target iOS 14.2 The minimum version of iOS your app
Framework targets.
Android Targets
If you're going to target Android, these settings describe the target Android
version.
Target the Checked When checked, the .NET MAUI project will target and
Android build an Android version of your app. Uncheck to disable
platform the Android target.
Target Android net6.0- The Target Framework Moniker used to target Android.
Framework android or
net7.0-
android
Windows Targets
If you're going to target Windows, these settings describe the target Windows
version.
Target the Checked When checked, the .NET MAUI project will target
Windows and build a Windows version of your app. Uncheck
platform to disable the Windows target.
Build
The Build section describes settings related to compiling your app.
General
Settings related to target platforms.
Conditional compilation symbols
Platform target
Specifies the processor to be targeted by the output file. Choose Any CPU to
specify that any processor is acceptable, allowing the application to run on the
broadest range of hardware.
Typically this is set to Any CPU and the runtime identifier setting is used to target a
CPU platform.
Option Description
Any (Default) Compiles your assembly to run on any platform. Your application runs as
CPU a 64-bit process whenever possible and falls back to 32-bit when only that mode
is available.
x64 Compiles your assembly to be run by the 64-bit runtime on a computer that
supports the AMD64 or EM64T instruction set.
ARM32 Compiles your assembly to run on a computer that has an Advanced RISC
Machine (ARM) processor.
ARM64 Compiles your assembly to run by the 64-bit runtime on a computer that has an
Advanced RISC Machine (ARM) processor that supports the A64 instruction set.
Nullable
Specifies the project-wide C# nullable context. For more information, see Nullable
References.
Option Description
Disable Nullable warnings are disabled. All reference type variables are nullable
reference types.
Enable The compiler enables all null reference analysis and all language features.
Warnings The compiler performs all null analysis and emits warnings when code might
dereference null.
Option Description
Annotations The compiler doesn't perform null analysis or emit warnings when code
might dereference null.
Enables implicit global usings to be declared by the project SDK. This is enabled by
default and imports many of the .NET MAUI namespaces automatically to all code
files. Code files don't need to add using statements for common .NET MAUI
namespaces. For more information, see MSBuild properties - ImplicitUsings.
Unsafe code
Allow code that uses the unsafe keyword to compile. This is disabled by default.
Optimize code
Enable compiler optimizations for smaller, faster, and more efficient output. There
is an option for each target platform, in Debug or Release mode. Generally, this is
enabled for Release mode, as the code is optimized for speed at the expense of
helpful debugging information.
Debug symbols
Warning level
Blocks the compiler from generating the specified warnings. Separate multiple
warning numbers with a comma , or a semicolon ; .
When enabled, instructs the compiler to treat warnings as errors. This is disabled
by default.
Treat specific warnings as errors
Specifies which warnings are treated as errors. Separate multiple warning numbers
with a comma , or a semicolon ; .
Output
Settings related to generating the output file.
Specifies the base location for the project's output during build. Subfolders will be
appended to this path to differentiate project configuration.
Defaults to .\bin\ .
Specifies the base location for the project's intermediate output during build.
Subfolders will be appended to the path to differentiate project configuration.
Defaults to .\obj\ .
Reference assembly
When enabled, produces a reference assembly containing the public API of the
project. This is disabled by default.
Documentation file
Events
In this section you can add commands that run during the build.
Pre-build event
Specifies commands that run before the build starts. Does not run if the project is
up-to-date. A non-zero exit code will fail the build before it runs.
Post-build event
Specifies commands that run before the build starts. Does not run if the project is
up-to-date. A non-zero exit code will fail the build before it runs.
When to run the post-build event
Strong naming
Settings related to signing the assembly.
Advanced
Additional settings related to the build.
Language version
The version of the language available to the code in the project. Defaults to 10.0 .
Throw exceptions when integer arithmetic produces out of range values. This
setting is available for each platform. The default is disabled for each platform.
Deterministic
Produce identical compilation output for identical inputs. This setting is available
for each platform. The default is enabled for each platform.
File alignment
Specifies, in bytes, where to align the sections of the output file. This setting is
available for each platform. The default is 512 for each platform.
Package
The Package section describes settings related to generating a NuGet package.
General
Settings related to generating a NuGet package.
When enabled, produces a NuGet package file during build operations. This is
disabled by default.
Package ID
The case-insensitive package identifier, which must be unique across the NuGet
package gallery, such as nuget.org. IDs may not contain spaces or characters that
aren't valid for a URL, and generally follow .NET namespace rules.
Title
Package Version
Authors
Company
Product
Description
Copyright
Project URL
A URL for the package's home page, often shown in UI displays as well as
nuget.org.
Icon
The icon image for the package. Image file size is limited to 1 MB. Supported file
formats include JPEG and PNG. An image resolution of 128x128 is recommended.
README
The README document for the package. Must be a Markdown (.md) file.
Repository URL
Specifies the URL for the repository where the source code for the package resides
and/or from which it's being built. For linking to the project page, use the 'Project
URL' field, instead.
Repository type
Tags
A semicolon-delimited list of tags and keywords that describe the package and aid
discoverability of the packages through search and filtering.
Release notes
A description of the changes made in the release of the package, often used in UI
like the Updates tab of the Visual Studio Package Manager in place of the package
description.
Assembly version
File version
The version associated with the file, defaults to 1.0.0.0 if not set.
License
Package License
Symbols
Code Analysis
Settings related to code analysis.
All analyzers
Settings related to when analysis runs.
Run on build
When enabled, runs code analysis on build. Defaults to enabled.
When enabled, runs code analysis live in the editor as you type. Defaults to
enabled.
.NET analysis
Settings related to .NET analyzers.
When enabled, produces diagnostics about code style on build. This is disabled by
default.
When enabled, runs .NET analyzers to help with API usage. Defaults to enabled.
Analysis level
The set of analyzers that should be run in the project. Defaults to Latest . For more
information, see MSBuild: AnalysisLevel.
MAUI Shared
These are project settings for .NET MAUI that are shared across all target platforms.
General
General settings related to .NET MAUI.
Application Title
Application ID
The identifier of the application in reverse domain name format, for example:
com.microsoft.maui .
Application ID (GUID)
The version of the application. This should be a single digit integer. Defaults to 1 .
Android
These are Android-specific .NET MAUI settings.
Manifest
Settings related to the Android manifest.
Application name
The string that's displayed as the name of the application. This is the name that's
shown in the app's title bar. If not set, the label of the app's MainActivity is used as
the application name. The default setting is @string/app_name , which refers to the
string resource app_name location in Resources/values/Strings.xaml .
A string that's used to uniquely identify the application. Typically, the package
name is based on a reversed internet domain name convention, such as
com.company.appname .
Application icon
Specifies the application icon resource that will be displayed for the app. The
setting @drawable/icon refers to the image file icon.png located in the
Resources/mipmap folder.
Application theme
Sets the UI style that's applied to the entire app. Every view in the app applies to
the style attributes that are defined in the selected theme.
An integer value greater than zero that defines the version number of the app.
Higher numbers indicate more recent versions. This value is evaluated
programmatically by Android and by other apps, it isn't shown to users.
Install location
Option Description
Internal-only (Default) Specifies that the app can't be installed or moved to external
storage.
Prefer Specifies that the app should be installed in external storage, if possible.
external
Prefer Specifies that the app should be installed in internal storage, if possible.
internal
The oldest API level of an Android device that can install and run the app. Also
referred to as minSdkVersion .
The target API level of the Android device where the app expects to run. This API
level is used at run-time, unlike Target Framework, which is used at build time.
Android uses this version as a way to provide forward compatibility. Also referred
to as targetSdkVersion , this should match Target Framework compileSdkVersion .
Options
Miscellaneous options for building an Android app.
Either apk or bundle , which packages the Android application as an APK file or
Android App Bundle, respectively. This can be set individually for both Debug and
Release modes.
App Bundles are the latest format for Android release builds that are intended for
submission on Google Play.
When enabled, deploys the app faster than normal to the target device. This
process speeds up the build/deploy/debug cycle because the package isn't
reinstalled when only assemblies are changed. Only the updated assemblies are
resynchronized to the target device.
When enabled, generates one Android package (apk) per selected Application
Binary Interface (ABI). This is disabled by default.
When enabled, uses the incremental Android packaging system (aapt2). This is
enabled by default.
Multi-dex
When enabled, allows the Android build system to use multidex. The default is
disabled.
Code shrinker
r8 is the next-generation tool which converts Java byte code to optimized dex
code.
Uncompressed resources
Developer instrumentation
Debugger
Selects which debugger to use. The default is .NET (Xamarin) , which is used for
managed code. The C++ debugger can be selected to debug native libraries used
by the app.
AOT
Enables Ahead-of-Time (AOT) compilation. This can be set for individually for both
Debug and Release modes.
LLVM
Startup Tracing
Enables startup tracing. This can be set for individually for both Debug and Release
modes.
Garbage Collection
Enable trimming
When enabled, trims the application during publishing. This can be set for
individually for both Debug and Release modes. For more information, see Trim
self-contained deployments and executables and Trim options.
Trimming granularity
Controls how aggressively IL is discarded. There are two modes to select from:
Link enables member-level trimming, which removes unused members from
types.
CopyUsed (default) enableds assembly-level trimming, which keeps an entire
The default is 1G .
Package Signing
When enabled, signs the .APK file using the keystore details. This is disabled by default.
iOS
These are iOS-specific .NET MAUI settings.
Build
Settings related to building the iOS app.
Linker behavior
The linker can strip out unused methods, properties, fields, events, structs, and
even classes in order to reduce the overall size of the application. You can add a
Preserve attribute to any of these in order to prevent the linker from stripping it
2 Warning
Enabling this feature may hinder debugging, as it may strip out property
accessors that would allow you to inspect the state of your objects.
Options are:
Don't link
Link Framework SDKs only (default)
Link All
LLVM
When enabled, uses the LLVM optimized compiler. This can be set for individually
for both Debug and Release modes.
Float operations
Symbols
When enabled, strips native debugging symbols from the output. This is enabled
by default.
Garbage collector
When enabled, uses the concurrent garbage collector. This is disabled by default.
Additional arguments
Optimization
Bundle Signing
These settings are related to generating and signing the app bundle.
Scheme
Configures the signing scheme for the bundle. It can be set to one of the following
values:
Manual provisioning : With this value, you'll be responsible for setting
provisioning profiles and signing certificates yourself.
Automatic provisioning : (default) With this value, Visual Studio will set
provisioning profiles and signing certificates for you, which simplifies app
deployment when testing on a device.
Signing identity
A signing identity is the certificate and private key pair that's used for code-signing
app bundle using Apple's codesign utility.
Developer (automatic) (default)
Distribution (automatic)
Provisioning profile
Provisioning profiles are a way of tying together a team of developers with an App
ID and, potentially, a list of test devices. The provisioning profiles list is filtered to
only show provisioning profiles that match both the chosen identity and the App
ID (aka bundle identifier) set in the Info.plist. If the provisioning profile that you're
looking for isn't in the list, make sure that you've chosen a compatible identity and
double-check that the bundle identifier set in your Info.plist is correct.
Custom Entitlements
The plist file to use for entitlements. For more information, see Entitlements.
The plist file containing custom rules used by Apple's codesign utility.
7 Note
As of Mac OSX 10.10, Apple has deprecated the use of custom resource rules.
So, this setting should be avoided unless absolutely necessary.
Additional arguments
Debug
These are settings related to debugging.
Debugging
When enabled, turns on debugging. The default is based on the current profile.
Debug profiles enable debugging, while Release profiles disable debugging.
Profiling
On Demand Resources
Settings related to on-demand resources. For more information, see Apple Developer
Documentation - On-Demand Resources Essentials .
Initial Tags
The tags of the on-demand resources that are downloaded at the same time the
app is downloaded from the app store. Separate tags with a semicolon ; .
Pre-fetch Order
The tags of the on-demand resources that are downloaded after the app is
installed. Separate tags with a semicolon ; .
Embed
When enabled, embeds on-demand resources in the app bundle. This is enabled
by default. Disable this setting to use the Web server.
Web server
Run Options
Options related to running the app on an iOS or macOS device.
Execution mode
This setting determines how the app is run on the target device.
Start arguments
Additional command line arguments to be passed to the app when it's started on
the device.
Environment variables
Name-value pairs of environment variables to set when the app is run on the
device.
Publish a .NET MAUI app for Android
Article • 04/05/2023 • 2 minutes to read
The final step in the development of a .NET MAUI app is to publish it. Publishing is the
process of creating a package that contains the app and is ready for users to install on
their devices. Packaging and deployment involve two essential tasks:
Preparing for deployment. A release version of the app is created that can be
deployed to Android devices.
Distribution. The release version of an app is made available through one or more
of the various distribution channels.
The following diagram illustrates the steps involved with publishing a .NET MAUI
Android app:
As can be seen in the diagram above, preparing for deployment is identical regardless
of the distribution method that's used. There are several ways that an Android app can
be released to users:
Through a market – There are multiple Android marketplaces that exist for
distribution, with the most well known being Google Play .
Via a website – A .NET MAUI app can be made available for download on a
website, from which users may then install the app by clicking on a link.
Via a file share – Similar to a website, as long as the app package is available to
the user, they can side-load it on their device.
Multiple channels can distribute a .NET MAUI app simultaneously. For example, an app
could be published on Google Play, and also be downloaded from a web server.
Making your app available for direct download is most useful for a controlled subset of
users, such as an enterprise environment or an app that is only meant for a small or
well-specified set of users. Server and email distribution are also simpler publishing
models, requiring less preparation to publish an app, though apps may be blocked as an
email attachment.
Google Play is the most comprehensive and popular marketplace for Android apps.
Google Play allows users to discover, download, rate, and pay for apps by clicking a
single icon either on their device or on their computer. Google Play also provides tools
to help in the analysis of sales and market trends and to control which devices and users
may download an app.
) Important
When distributing a Blazor Hybrid app, the host platform must have a WebView.
For more information, see Keep the Web View current in deployed Blazor Hybrid
apps.
See also
Publishing on Google Play
Google Application Licensing
Publish a .NET MAUI app for Android
with the CLI
Article • 03/23/2023 • 4 minutes to read
When distributing your .NET Multi-platform App UI (.NET MAUI) app for Android, you
generate an apk (Android Package) or an aab (Android App Bundle) file. The apk is used
for installing your app to an Android device, and the aab is used to publish your app to
an Android store.
With just a few configuration changes to your project, your app can be packaged for
distribution.
project was created. Just validate that they exist and are set to valid values:
XML
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ApplicationId>com.companyname.myproject</ApplicationId>
<ApplicationVersion>1</ApplicationVersion>
</PropertyGroup>
</Project>
Tip
Some settings are available in the Project Properties editor in Visual Studio to
change values. Right-click on the project in the Solution Explorer pane and choose
Properties. For more information, see Project configuration in .NET MAUI.
The following table describes how each project setting maps to the manifest file:
Here's an example of an automatically generated manifest file with the package and
version information specified:
XML
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
package="com.hi.companyname.myproject"
android:versionName="1.0.0">
</manifest>
For more information about the manifest, see Google Android App Manifest
Overview .
Tip
If Visual Studio is open, use the View > Terminal menu to open a terminal at
the location of the solution or project. Navigate to the project folder.
Console
The tool generates a myapp.keystore file, which should be located in the same
folder as your project.
tool: key.
<AndroidSigningKeyPass> – The password you provided when creating the keystore
file.
<AndroidSigningStorePass> – The password you provided when creating the
keystore file.
For security reasons, you don't want to supply a value for <AndroidSigningKeyPass> and
<AndroidSigningStorePass> in the project file. You can provide these values on the
command line when you publish the app. An example of providing the password is in
the Publish section.
XML
<AndroidSigningKeyStore>myapp.keystore</AndroidSigningKeyStore>
<AndroidSigningKeyAlias>key</AndroidSigningKeyAlias>
<AndroidSigningKeyPass></AndroidSigningKeyPass>
<AndroidSigningStorePass></AndroidSigningStorePass>
</PropertyGroup>
The example <PropertyGroup> above adds a condition check, preventing those settings
from being processed unless the condition check passes. The condition check looks for
two things:
If either of those conditions fail, the settings aren't processed. More importantly, the
<AndroidKeyStore> setting isn't set, preventing the app from being signed.
Publish
At this time, publishing is only supported through the .NET command line interface.
To publish your app, open a terminal and navigate to the folder for your .NET MAUI app
project. Run the dotnet publish command, providing the following parameters:
Parameter Value
2 Warning
Attempting to publish a .NET MAUI solution will result in the dotnet publish
command attempting to publish each project in the solution individually, which can
cause issues when you've added other project types to your solution. Therefore, the
dotnet publish command should be scoped to your .NET MAUI app project.
For example:
Console
Publishing builds the app, and then copies the aab and apk files to the
bin\Release\net7.0-android\publish folder. There are two aab files, one unsigned and
another signed. The signed variant has -signed in the file name.
For more information about the dotnet publish command, see dotnet publish.
To learn how to upload a signed Android App Bundle to the Google Play Store, see
Upload your app to the Play Console .
See also
GitHub discussion and feedback: .NET MAUI Android target publishing/archiving
Android Developers: About Android App Bundles
Android Developers: Meet Google Play's target API level requirement
Publish a .NET MAUI app for iOS
Article • 03/23/2023 • 2 minutes to read
Once a .NET Multi-platform App UI (.NET MAUI) iOS app has been developed and
tested, it can be packaged for distribution as an .ipa file. An .ipa file is an iOS app archive
file that stores an iOS app. The following diagram shows the steps required to produce
the app package for distribution:
Publishing a .NET MAUI app for iOS builds on top of Apple's provisioning process, which
requires you to have:
Created an Apple ID. For more information, see Create Your Apple ID .
Enrolled your Apple ID in the Apple Developer Program, which you have to pay to
join. Enrolling in the Apple Developer Program enables you to create a provisioning
profile, which contains code signing information.
A Mac on which you can build your app.
7 Note
To register for either of these programs, you must first have an Apple ID . Then
you can visit the Apple Developer Program to register for a program.
App Store. This is the main approach for distributing iOS apps to users. Apps are
submitted to the App Store through on online tool called App Store Connect. Only
developers who belong to the Apple Developer Program have access to this tool.
Members of the Apple Developer Enterprise Program do not have access. All apps
submitted to the App Store require approval from Apple. For more information,
see Publish an iOS app for App Store distribution.
In-house. This distribution mechanism is also known as enterprise distribution. It
enables members of the Apple Developer Enterprise Program to distribute apps
internally to other members of the same organization. This has the advantage of
not requiring an App Store review, and has no limit on the number of devices on
which an app can be installed. However, members of the Apple Developer
Enterprise Program don't have access to App Store Connect, and therefore the
licensee is responsible for distributing the app. For more information, see Publish
an iOS app for in-house distribution.
Ad-hoc. iOS apps can be user-tested via ad-hoc distribution, which is available for
the Apple Developer Program and the Apple Developer Enterprise Program. It
allows an app to be deployed on up to 100 devices, for testing. A use case for ad-
hoc distribution is distribution within a company when App Store Connect is not an
option. For more information, see Publish an iOS app for ad-hoc distribution.
Custom apps for business. Apple allows custom distribution of apps to businesses
and education. For more information, see Distributing Custom Apps on
developer.apple.com and Apple Business Manager User Guide on
support.apple.com.
All approaches require that apps are provisioned using an appropriate provisioning
profile. Provisioning profiles contain code signing and app identity information, as well
as the intended distribution mechanism. For non-App Store distribution, they also
contain information about the devices the app can be deployed to.
) Important
When distributing a Blazor Hybrid app, the host platform must have a WebView.
For more information, see Keep the Web View current in deployed Blazor Hybrid
apps.
Publish an iOS app for App Store
distribution
Article • 03/23/2023 • 13 minutes to read
The most common approach to distributing iOS apps to users is through the App Store.
Apps are submitted to the App Store through an online tool called App Store Connect.
Only developers who belong to the Apple Developer Program have access to this tool.
Members of the Apple Developer Enterprise Program do not have access. All apps
submitted to the App Store require approval from Apple.
Distributing an iOS app requires that the app is provisioned using a provisioning profile.
Provisioning profiles are files that contain code signing information, as well as the
identity of the app and its intended distribution mechanism.
To distribute a .NET Multi-platform App UI (.NET MAUI) iOS app, you'll need to build a
distribution provisioning profile specific to it. This profile enables the app to be digitally
signed for release so that it can be installed on an iOS device. A distribution
provisioning profile contains an App ID and a distribution certificate. You can use the
same App ID that you used when deploying your app to a device for testing. However,
you'll need to create a distribution certificate to identify yourself or your organization, if
you don't already have one.
The process for creating an App Store distribution provisioning profile is as follows:
You only need to create a distribution certificate if you don't already one. The
distribution certificate must be created using the Apple ID for your Apple Developer
Account.
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts.
2. In the Apple Developer Accounts dialog, select a team and click the View
Details... button.
3. In the Details dialog, click Create Certificate and select iOS Distribution. A
new signing identity will be created and will sync with Apple provided that you
have the correct permissions.
) Important
The private key and certificate that make up your signing identity will also be
exported to Keychain Access on your Mac build host, provided that the IDE is
paired to it. For more information, see Pair to Mac for iOS development.
2 Warning
Losing the certificate and associated keys can be incredibly disruptive, as it will
require revoking existing certificates and re-creating provisioning profiles.
Create an App ID
An App ID is required to identify the app that you are distributing. An App ID is similar
to a reverse-DNS string, that uniquely identifies an app, and should be identical to the
bundle identifier for your app. You can use the same App ID that you used when
deploying your app to a device for testing.
Wildcard. A wildcard App ID allows you to use a single App ID to match multiple
apps, and typically takes the form com.domainname.* . A wildcard App ID can be
used to distribute multiple apps, and should be used for apps that do not enable
app-specific capabilities.
Explicit. An explicit App ID is unique to a single app, and typically takes the form
com.domainname.myid . An explicit App ID allows the distribution of one app, with a
matching bundle identifier. Explicit App IDs are typically used for apps that enable
app-specific capabilities such as Apple Pay, or Game Center. For more information
about capabilities, see Capabilities.
2. On the Certificates, Identifiers & Profiles page, select the Identifiers tab.
3. On the Identifiers page, click the + button to create a new App ID.
4. On the Register a new identifier page, select the App IDs radio button before
clicking the Continue button:
5. On the Register a new identifier page, select App before clicking the Continue
button:
6. On the Register an App ID page, enter a description, and select either the Explicit
or Wildcard Bundle ID radio button. Then, enter the Bundle ID for your app in
reverse DS format:
) Important
The Bundle ID you enter must correspond to the Bundle identifier in the
Info.plist file in your app project.
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
7. On the Register an App ID page, select any capabilities that the app uses. Any
capabilities must be configured both on this page and in the Entitlements.plist file
in your app project. For more information see Capabilities and Entitlements.
1. In the Certificates, Identifiers & Profiles page of your Apple Developer Account,
select the Profiles tab.
3. In the Register a New Provisioning Profile page, select the App Store radio button
before clicking the Continue button:
4. In the Generate a Provisioning Profile page, in the App ID drop-down, select the
App ID that you previously created before clicking the Continue button:
5. In the Generate a Provisioning Profile page, select the radio button that
corresponds to your distribution certificate before clicking the Continue button:
6. In the Generate a Provisioning Profile page, enter a name for the provisioning
profile before clicking the Generate button:
7 Note
7. In the Generate a Provisioning Profile page, optionally click the Download button
to download your provisioning profile.
7 Note
It's not necessary to download your provisioning profile now. Instead, you will
do this in Visual Studio.
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts.
2. In the Apple Developer Accounts dialog, select your team and click the View
Details... button.
3. In the Details dialog, verify that the new profile appears in the Provisioning
Profiles list. You may need to restart Visual Studio to refresh the list.
4. In the Details dialog, click the Download All Profiles button.
Create a record for the app you want to upload to the App Store in App Store
Connect. This record includes all the information about the app as it will appear in
the App Store and all of the information needed to manage the app through the
distribution process. For more information, see Create an app record on
developer.apple.com.
Create an app-specific password. For information about generating an app-specific
password, see Sign in to apps with your Apple ID using app-specific passwords
on support.apple.com.
For information about publishing an iOS app using the Command Line Interface (CLI),
see Publish an iOS app using the command line.
Visual Studio
1. In Visual Studio, ensure that the IDE is paired to a Mac Build host. For more
information, see Pair to Mac for iOS development.
2. In the Visual Studio toolbar, use the Debug Target drop-down to select iOS
Remote Devices and then Remote Device:
3. In the Visual Studio toolbar, use the Solutions Configuration drop-down to
change from the debug configuration to the release configuration:
4. In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the iOS Bundle Signing tab and ensure that
Scheme is set to Manual Provisioning, Signing identity is set to Distribution
(Automatic), and Provisioning profile is set to Automatic:
These settings will ensure that Visual Studio will select the correct provisioning
profile based on the bundle identifier in Info.plist (which is identical to the
value of the Application ID property in your project file). Alternatively, set the
Signing identity to the appropriate distribution certificate and Provisioning
profile to the profile you created in your Apple Developer Account.
5. In Solution Explorer, right-click on your .NET MAUI app project and select
Publish...:
The Archive Manager will open and Visual Studio will begin to archive your
app bundle:
The archiving process signs the app with the certificate and provisioning
profiles that you specified in the iOS Bundle Signing tab, for the selected
solution configuration.
7. In the Distribute - Select Channel dialog, select the App Store button:
8. In the Distribute - Signing Identity dialog, select your signing identity and
provisioning profile:
7 Note
You should use the signing identity and provisioning profile that were
created for your app and the selected distribution channel.
9. In the Distribute - Signing Identity dialog, select either the Save As button or
the Upload to Store button. The Save As button will enable you to enter a
filename, before re-signing your app and publishing it to an .ipa file on your
file system for later upload to the App Store through an app such as
Transporter . The Upload to Store button will re-sign your app and publish it
to an .ipa file, before uploading it to the App Store.
a. If you select the Upload to Store button, the Upload to store credentials
dialog will appear after Visual Studio has created the .ipa file. In the Upload
to store credentials dialog enter your Apple ID and app-specific password
and select the OK button:
Visual Studio will validate your app package and upload it to the App Store,
provided you've created an application record in App Store Connect.
Troubleshoot
Transporter can be used to help identify errors with app packages that stop successful
submission to the App Store.
Publish an iOS app for in-house
distribution
Article • 03/23/2023 • 11 minutes to read
Distributing an iOS app requires that the app is provisioned using a provisioning profile.
Provisioning profiles are files that contain code signing information, as well as the
identity of the app and its intended distribution mechanism.
To distribute a .NET Multi-platform App UI (.NET MAUI) iOS app, you'll need to build a
distribution provisioning profile specific to it. This profile enables the app to be digitally
signed for release so that it can be installed on an iOS device. An in-house distribution
provisioning profile contains an App ID and a distribution certificate. You can use the
same App ID that you used when deploying your app to a device for testing. However,
you'll need to create a distribution certificate to identify your organization, if you don't
already have one.
You only need to create a distribution certificate if you don't already one. The
distribution certificate must be created using the Apple ID for your Apple Developer
Account.
To create a distribution certificate:
Visual Studio
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts.
2. In the Apple Developer Accounts dialog, select a team and click the View
Details... button.
3. In the Details dialog, click Create Certificate and select iOS Distribution. A
new signing identity will be created and will sync with Apple provided that you
have the correct permissions.
) Important
The private key and certificate that make up your signing identity will also be
exported to Keychain Access on your Mac build host, provided that the IDE is
paired to it. For more information, see Pair to Mac for iOS development.
2 Warning
Losing the certificate and associated keys can be incredibly disruptive, as it will
require revoking existing certificates and re-creating provisioning profiles.
Wildcard. A wildcard App ID allows you to use a single App ID to match multiple
apps, and typically takes the form com.domainname.* . A wildcard App ID can be
used to distribute multiple apps, and should be used for apps that do not enable
app-specific capabilities.
Explicit. An explicit App ID is unique to a single app, and typically takes the form
com.domainname.myid . An explicit App ID allows the distribution of one app, with a
matching bundle identifier.. Explicit App IDs are typically used for apps that enable
app-specific capabilities such as Apple Pay, or Game Center.
2. On the Certificates, Identifiers & Profiles page, select the Identifiers tab.
3. On the Identifiers page, click the + button to create a new App ID.
4. On the Register a new identifier page, select the App IDs radio button before
clicking the Continue button:
5. On the Register an App ID page, enter a description, and select either the Explicit
or Wildcard Bundle ID radio button. Then, enter the Bundle ID for your app in
reverse DNS format:
) Important
The Bundle ID you enter must correspond to the Bundle identifier in the
Info.plist file in your app project.
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
6. On the Register an App ID page, select any capabilities that the app uses. Any
capabilities must be configured both on this page and in the Entitlements.plist file
in your app project. For more information see Capabilities and Entitlements.
8. On the Register an App ID page, enter your deployment details if you have them,
prior to clicking the Continue button.
1. In the Certificates, Identifiers & Profiles page of your Apple Developer Account,
select the Profiles tab.
3. In the Register a New Provisioning Profile page, select the In House radio button
before clicking the Continue button:
4. In the Generate a Provisioning Profile page, in the App ID drop-down, select the
App ID that you previously created before clicking the Continue button:
5. In the Generate a Provisioning Profile page, select the radio button that
corresponds to your distribution certificate before clicking the Continue button:
6. In the Generate a Provisioning Profile page, enter a name for the provisioning
profile before clicking the Generate button:
7 Note
7. In the Generate a Provisioning Profile page, optionally click the Download button
to download your provisioning profile.
7 Note
It's not necessary to download your provisioning profile now. Instead, you will
do this in Visual Studio.
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts.
2. In the Apple Developer Accounts dialog, select your team and click the View
Details... button.
3. In the Details dialog, verify that the new profile appears in the Provisioning
Profiles list. You may need to restart Visual Studio to refresh the list.
4. In the Details dialog, click the Download All Profiles button.
Visual Studio
1. In Visual Studio, ensure that the IDE is paired to a Mac Build host. For more
information, see Pair to Mac for iOS development.
2. In the Visual Studio toolbar, use the Debug Target drop-down to select iOS
Remote Devices and then Remote Device:
3. In the Visual Studio toolbar, use the Solutions Configuration drop-down to
change from the debug configuration to the release configuration:
4. In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the iOS Bundle Signing tab and ensure that
Scheme is set to Manual Provisioning, Signing identity is set to Distribution
(Automatic), and Provisioning profile is set to Automatic:
These settings will ensure that Visual Studio will select the correct provisioning
profile based on the bundle identifier in Info.plist (which is identical to the
value of the Application ID property in your project file). Alternatively, set the
Signing identity to the appropriate distribution certificate and Provisioning
profile to the profile you created in your Apple Developer Account.
5. In Solution Explorer, right-click on your .NET MAUI app project and select
Publish...:
The Archive Manager will open and Visual Studio will begin to archive your
app bundle:
The archiving process signs the app with the certificate and provisioning
profiles that you specified in the iOS Bundle Signing tab, for the selected
solution configuration.
8. In the Distribute - Signing Identity dialog, select your signing identity and
provisioning profile:
7 Note
You should use the signing identity and provisioning profile that were
created for your app and the selected distribution channel.
9. In the Distribute - Signing Identity dialog, select the Save As button and
enter a filename. Your app will then be re-signed and published to an .ipa file
on your file system.
In-house apps can be distributed via a secure website, or via Mobile Device
Management (MDM). Both of these approaches require the app to be prepared for
distribution, which includes the preparation of a manifest. For more information, see
Distribute proprietary in-house apps to Apple devices on support.apple.com.
Publish an iOS app for ad-hoc
distribution
Article • 03/23/2023 • 13 minutes to read
Ad-hoc distribution is primarily used for testing apps within a wide group of people, and
is available for the Apple Developer Program and the Apple Developer Enterprise
Program. Another use case for ad-hoc distribution is distribution within a company
when App Store Connect isn't an option.
Ad-hoc distribution has the advantage of not requiring App Store approval, with apps
being installed with Apple Configurator . However, it's limited to 100 devices per
membership year, for both development and distribution, and the devices must be
added to your Apple Developer Account.
Distributing an iOS app requires that the app is provisioned using a provisioning profile.
Provisioning profiles are files that contain code signing information, as well as the
identity of the app and its intended distribution mechanism.
To distribute a .NET Multi-platform App UI (.NET MAUI) iOS app, you'll need to build a
distribution provisioning profile specific to it. This profile enables the app to be digitally
signed for release so that it can be installed on an iOS device. An ad-hoc distribution
provisioning profile contains an App ID, a distribution certificate, and a list of the devices
that can install the app. You can use the same App ID that you used when deploying
your app to a device for testing. However, you'll need to create a distribution certificate
to identify yourself or your organization, if you don't already have one.
You only need to create a distribution certificate if you don't already one. The
distribution certificate must be created using the Apple ID for your Apple Developer
Account.
Visual Studio
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts.
2. In the Apple Developer Accounts dialog, select a team and click the View
Details... button.
3. In the Details dialog, click Create Certificate and select iOS Distribution. A
new signing identity will be created and will sync with Apple provided that you
have the correct permissions.
) Important
The private key and certificate that make up your signing identity will also be
exported to Keychain Access on your Mac build host, provided that the IDE is
paired to it. For more information, see Pair to Mac for iOS development.
2 Warning
Losing the certificate and associated keys can be incredibly disruptive, as it will
require revoking existing certificates and re-creating provisioning profiles.
Add a device
When creating a provisioning profile, the profile must include which devices can run the
app. Before selecting a device to be added to a provisioning profile you must first add
the device to your Apple Developer Account. This can be achieved with the following
steps:
1. Connect the device to be provisioned to your local Mac with a USB cable.
3. In Xcode, select the Devices tab, and select the device from the list of connected
devices.
6. In the Register a New Device page, set the correct Platform and provide a name
for the new device. Then paste the identifier from the clipboard into the Device ID
(UDID) field, and click the Continue button:
7. In the Register a New Device page, review the information and then click the
Register button.
Repeat the above steps for any iOS device that you want to deploy a .NET MAUI iOS app
onto.
Create an App ID
An App ID is required to identify the app that you are distributing. An App ID is similar
to a reverse-DNS string, that uniquely identifies an app, and should be identical to the
bundle identifier for your app. You can use the same App ID that you used when
deploying your app to a device for testing.
Wildcard. A wildcard App ID allows you to use a single App ID to match multiple
apps, and typically takes the form com.domainname.* . A wildcard App ID can be
used to distribute multiple apps, and should be used for apps that do not enable
app-specific capabilities.
Explicit. An explicit App ID is unique to a single app, and typically takes the form
com.domainname.myid . An explicit App ID allows the distribution of one app, with a
matching bundle identifier. Explicit App IDs are typically used for apps that enable
app-specific capabilities such as Apple Pay, or Game Center. For more information
about capabilities, see Capabilities.
2. On the Certificates, Identifiers & Profiles page, select the Identifiers tab.
3. On the Identifiers page, click the + button to create a new App ID.
4. On the Register a new identifier page, select the App IDs radio button before
clicking the Continue button:
5. On the Register a new identifier page, select App before clicking the Continue
button:
6. On the Register an App ID page, enter a description, and select either the Explicit
or Wildcard Bundle ID radio button. Then, enter the Bundle ID for your app in
reverse DS format:
) Important
The Bundle ID you enter must correspond to the Bundle identifier in the
Info.plist file in your app project.
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
In Visual Studio, in Solution Explorer right-click on your .NET MAUI app
project and select Properties. Then, navigate to the MAUI Shared >
General tab. The Application ID field lists the bundle identifier.
In Visual Studio for Mac, in the Solution Window, right-click on your
.NET MAUI app project and select Properties. Then, in the Project
Properties window, select the Build > App Info tab. The Application ID
field lists the bundle identifier.
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
7. On the Register an App ID page, select any capabilities that the app uses. Any
capabilities must be configured both on this page and in the Entitlements.plist file
in your app project. For more information see Capabilities and Entitlements.
1. In the Certificates, Identifiers & Profiles page of your Apple Developer Account,
select the Profiles tab.
3. In the Register a New Provisioning Profile page, select the Ad Hoc radio button
before clicking the Continue button:
4. In the Generate a Provisioning Profile page, in the App ID drop-down, select the
App ID that you previously created, and choose whether to create an offline
profile, before clicking the Continue button:
5. In the Generate a Provisioning Profile page, select the radio button that
corresponds to your distribution certificate before clicking the Continue button:
6. In the Generate a Provisioning Profile page, select the devices that the app will be
installed on and then click the Continue button.
7. In the Generate a Provisioning Profile page, enter a name for the provisioning
profile before clicking the Generate button:
7 Note
8. In the Generate a Provisioning Profile page, optionally click the Download button
to download your provisioning profile.
7 Note
It's not necessary to download your provisioning profile now. Instead, you will
do this in Visual Studio.
Download provisioning profiles in Visual Studio
After creating a distribution provisioning profile in your Apple Developer Account, Visual
Studio can download it so that it's available for signing your app.
Visual Studio
1. In Visual Studio, go to Tools > Options > Xamarin > Apple Accounts.
2. In the Apple Developer Accounts dialog, select your team and click the View
Details... button.
3. In the Details dialog, verify that the new profile appears in the Provisioning
Profiles list. You may need to restart Visual Studio to refresh the list.
4. In the Details dialog, click the Download All Profiles button.
Visual Studio
1. In Visual Studio, ensure that the IDE is paired to a Mac Build host. For more
information, see Pair to Mac for iOS development.
2. In the Visual Studio toolbar, use the Debug Target drop-down to select iOS
Remote Devices and then Remote Device:
3. In the Visual Studio toolbar, use the Solutions Configuration drop-down to
change from the debug configuration to the release configuration:
4. In Solution Explorer, right-click on your .NET MAUI app project and select
Properties. Then, navigate to the iOS Bundle Signing tab and ensure that
Scheme is set to Manual Provisioning, Signing identity is set to Distribution
(Automatic), and Provisioning profile is set to Automatic:
These settings will ensure that Visual Studio will select the correct provisioning
profile based on the bundle identifier in Info.plist (which is identical to the
value of the Application ID property in your project file). Alternatively, set the
Signing identity to the appropriate distribution certificate and Provisioning
profile to the profile you created in your Apple Developer Account.
5. In Solution Explorer, right-click on your .NET MAUI app project and select
Publish...:
The Archive Manager will open and Visual Studio will begin to archive your
app bundle:
The archiving process signs the app with the certificate and provisioning
profiles that you specified in the iOS Bundle Signing tab, for the selected
solution configuration.
8. In the Distribute - Signing Identity dialog, select your signing identity and
provisioning profile:
7 Note
You should use the signing identity and provisioning profile that were
created for your app and the selected distribution channel.
9. In the Distribute - Signing Identity dialog, select the Save As button and
enter a filename. Your app will then be re-signed and published to an .ipa file
on your file system.
The app can then be distributed using Apple Configurator . For more information, see
Apple Configurator user guide on support.apple.com.
Publish an iOS app using the command
line
Article • 03/23/2023 • 7 minutes to read
To publish your app from the command line on a Mac, open a terminal and navigate to
the folder for your .NET MAUI app project. Run the dotnet publish command, providing
the following parameters:
Parameter Value
2 Warning
Attempting to publish a .NET MAUI solution will result in the dotnet publish
command attempting to publish each project in the solution individually, which can
cause issues when you've added other project types to your solution. Therefore, the
dotnet publish command should be scoped to your .NET MAUI app project.
Additional build parameters can be specified on the command line, if they aren't
provided in a <PropertyGroup> in your project file. The following table lists some of the
common parameters:
Parameter Value
/p:ApplicationVersion The version of the build that identifies an iteration of the app.
/p:RuntimeIdentifier The runtime identifier (RID) for the project. Use ios-arm64 .
/p:CodesignProvision The provisioning profile to use when signing the app bundle.
/p:CodesignEntitlements The path to the entitlements file that specifies the entitlements
the app requires.
) Important
Values for these parameters don't have to be provided on the command line. They
can also be provided in the project file. When a parameter is provided on the
command line and in the project file, the command line parameter takes
precedence. For more information about providing build properties in your project
file, see Define build properties in your project file.
For example, use the following command to build and sign an .ipa on a Mac:
.NET CLI
Publishing builds and signs the app, and then copies the .ipa to the bin/Release/net7.0-
ios/ios-arm64/publish/ folder. The distribution channel for the app is specified in the
distribution certificate contained within the provisioning profile. For information about
creating provisioning profiles for the different distribution channels, see Publish an iOS
app for App Store distribution, Publish an iOS app for ad-hoc distribution, and Publish
an iOS app for in-house distribution.
For more information about the dotnet publish command, see dotnet publish.
Runtime identifiers
If the RuntimeIdentifier parameter isn't specified on the command line, or in the
project file, the build process will default to a simulator runtime identifier. In addition,
passing the runtime identifier on the command line can result in the build failing to
restore if there are multiple target frameworks in the project file. For more information,
see Specifying both -f and -r to dotnet build fails to restore if multiple frameworks are
present in the project file .
One solution to these issues is to add the following <PropertyGroup> to your project file:
XML
<RuntimeIdentifier>ios-arm64</RuntimeIdentifier>
</PropertyGroup>
.NET CLI
XML
<RuntimeIdentifier>ios-arm64</RuntimeIdentifier>
</PropertyGroup>
.NET CLI
Parameter Value
<ApplicationVersion> The version of the build that identifies an iteration of the app.
<RuntimeIdentifier> The runtime identifier (RID) for the project. Set to ios-arm64 .
<CodesignProvision> The provisioning profile to use when signing the app bundle.
<CodesignEntitlements> The path to the entitlements file that specifies the entitlements
the app requires.
) Important
Values for these build properties don't have to be provided in the project file. They
can also be provided on the command line when you publish the app. This enables
you to omit specific values from your project file.
The following example shows a typical property group for building and signing your iOS
app with its provisioning profile:
XML
<CodesignProvision>MyMauiApp</CodesignProvision>
<ArchiveOnBuild>true</ArchiveOnBuild>
</PropertyGroup>
This example <PropertyGroup> adds a condition check, preventing the settings from
being processed unless the condition check passes. The condition check looks for two
items:
7 Note
The first time Pair to Mac logs into a Mac build host from Visual Studio 2022, it sets
up SSH keys. With these keys, future logins will not require a username or
password.
To publish your app from the command line on Windows, open a terminal and navigate
to the folder for your .NET MAUI app project. Run the dotnet publish command,
providing the same command line parameters, or build properties in your project file,
that you'd provide when publishing from a Mac. In addition, you must provide the
following command line parameters:
Parameter Value
/p:ServerUser The username to use when logging into your Mac build host.
Use your system username rather than your full name.
/p:ServerPassword The password for the username used to log into your Mac build
host.
/p:TcpPort The TCP port to use to communicate with your Mac build host,
which is 58181.
/p:_DotNetRootRemoteDirectory The folder on your Mac build host that contains the .NET SDK.
Use /Users/{macOS
username}/Library/Caches/Xamarin/XMA/SDKs/dotnet/ .
) Important
Values for these parameters can also be provided in the project file as build
properties. However, values for <ServerAddress> , <ServerUser> , <ServerPassword> ,
and <_DotNetRootRemoteDirectory> will typically be provided on the command line
for security reasons.
For example, use the following command to build and sign an .ipa from Windows:
.NET CLI
7 Note
Publishing builds and signs the app, and then copies the .ipa to the bin\Release\net7.0-
ios\ios-arm64\publish folder on your Windows machine. The distribution channel for the
app is specified in the distribution certificate contained within the provisioning profile.
For information about creating distribution provisioning profiles for the different
distribution channels, see Publish an iOS app for App Store distribution, Publish an iOS
app for ad-hoc distribution, and Publish an iOS app for in-house distribution
During the publishing process it maybe necessary to allow codesign to run on your
paired Mac:
Distribute the app
The .ipa file can be distributed with one of the following approaches:
Ad-hoc apps can be distributed using Apple Configurator . For more information,
see Apple Configurator user guide on support.apple.com. |
App Store apps can be uploaded to the App Store through an app such as
Transporter . This will require you to have created a record for the app in App
Store Connect, and to create an app-specific password. For more information, see
Create an app record on developer.apple.com, and Sign in to apps with your
Apple ID using app-specific passwords on support.apple.com.
In-house apps can be distributed via a secure website, or via Mobile Device
Management (MDM). Both of these approaches require the app to be prepared for
distribution, which includes the preparation of a manifest. For more information,
see Distribute proprietary in-house apps to Apple devices on support.apple.com.
Publish a .NET MAUI Mac Catalyst app
Article • 03/23/2023 • 2 minutes to read
Once a .NET Multi-platform App UI (.NET MAUI) Mac Catalyst app has been developed
and tested, it can be packaged for distribution as an .app or a .pkg file. An .app file is a
self-contained app that can be run without installation, whereas a .pkg is an app
packaged in an installer. The following diagram shows the steps required to produce an
app package for distribution:
Publishing a .NET MAUI Mac Catalyst app builds on top of Apple's provisioning process,
which requires you to have:
Created an Apple ID. For more information, see Create Your Apple ID .
Enrolled your Apple ID in the Apple Developer Program, which you have to pay to
join. Enrolling in the Apple Developer Program enables you to create a provisioning
profile, which contains code signing information.
A Mac on which you can build your app.
7 Note
To register for either of these programs, you must first have an Apple ID . Then
you can visit the Apple Developer Program to register for a program.
Mac App Store. This is the main approach for distributing Mac Catalyst apps to
users. Apps are submitted to the Mac App Store through on online tool called App
Store Connect. Only developers who belong to the Apple Developer Program have
access to this tool. Members of the Apple Developer Enterprise Program do not
have access. All apps submitted to the Mac App Store require approval from Apple.
For more information, see Publish a Mac Catalyst app for Mac App Store
distribution.
Outside the Mac App Store. This distribution mechanism enables Mac Catalyst apps
to be distributed outside the Mac App Store. It's available for the Apple Developer
Program and the Apple Developer Enterprise Program, and enables your Mac
Catalyst app to be downloaded from a location of your choosing. For more
information, see Publish a Mac Catalyst app for distribution outside the Mac App
Store.
Ad-hoc. Mac Catalyst apps can be user-tested via ad-hoc distribution, which is
available for the Apple Developer Program and the Apple Developer Enterprise
Program. It allows an app to be deployed on up to 100 devices, for testing. For
more information, see Publish a Mac Catalyst app for ad-hoc distribution.
All approaches require that apps are provisioned using an appropriate provisioning
profile. Provisioning profiles contain code signing and app identity information, as well
as the intended distribution mechanism. For ad-hoc distribution, they also contain
information about the devices the app can be deployed to. In addition, Mac Catalyst
apps that are distributed outside the Mac App Store must be notarized by Apple.
) Important
When distributing a Blazor Hybrid app, the host platform must have a WebView.
For more information, see Keep the Web View current in deployed Blazor Hybrid
apps.
Publish an unsigned .NET MAUI Mac
Catalyst app
Article • 03/23/2023 • 2 minutes to read
To publish an unsigned .NET Multi-platform App UI (.NET MAUI) Mac Catalyst app, open
a terminal and navigate to the folder for your app project. Run the dotnet publish
command, providing the following parameters:
Parameter Value
/p:MtouchLink The link mode for the project, which can be None , SdkOnly , or Full .
/p:CreatePackage An optional parameter that controls whether to create an .app or a .pkg. Use
false for an .app.
2 Warning
Attempting to publish a .NET MAUI solution will result in the dotnet publish
command attempting to publish each project in the solution individually, which can
cause issues when you've added other project types to your solution. Therefore, the
dotnet publish command should be scoped to your .NET MAUI app project.
Additional build parameters can be specified on the command line. The following table
lists some of the common parameters:
Parameter Value
/p:ApplicationVersion The version of the build that identifies an iteration of the app.
/p:RuntimeIdentifier The runtime identifier (RID) for the project. Release builds of
.NET MAUI Mac Catalyst apps default to using maccatalyst-x64
and maccatalyst-arm64 as runtime identifiers, to support
universal apps. To support only a single architecture, specify
maccatalyst-x64 or maccatalyst-arm64 .
.NET CLI
.NET CLI
Publishing builds the app, and then copies the .app to the bin/Release/net7.0-
maccatalyst/ folder or the .pkg to the bin/Release/net7.0-maccatalyst/publish/ folder. If
you publish the app using only a single architecture, the .app will be published to the
bin/Release/net7.0-maccatalyst/{architecture}/ folder while the .pkg will be published to
the bin/Release/net7.0-maccatalyst/{architecture}/publish/ folder.
For more information about the dotnet publish command, see dotnet publish.
To ensure that a .pkg installs the app to your Applications folder, copy the .pkg to outside
of your build artifacts folder and delete the bin and obj folders before double-clicking
on the .pkg.
See also
Open apps safely on your Mac
Publish a Mac Catalyst app for Mac App
Store distribution
Article • 03/23/2023 • 18 minutes to read
The most common approach to distributing Mac Catalyst apps to users is through the
Mac App Store. Apps are submitted to the Mac App Store through an online tool called
App Store Connect. Only developers who belong to the Apple Developer Program have
access to this tool. Members of the Apple Developer Enterprise Program do not have
access. All apps submitted to the Mac App Store require approval from Apple.
Distributing a Mac Catalyst app requires that the app is provisioned using a provisioning
profile. Provisioning profiles are files that contain code signing information, as well as
the identity of the app and its intended distribution mechanism.
To distribute a .NET Multi-platform App UI (.NET MAUI) Mac Catalyst app, you'll need to
build a distribution provisioning profile specific to it. This profile enables the app to be
digitally signed for release so that it can be installed on Macs. A distribution
provisioning profile contains an App ID and a distribution certificate. You'll need to
create a distribution certificate to identify yourself or your organization, if you don't
already have one. In addition, you'll need to create a Mac installer certificate to sign
your app's installer package for submission to the Mac App Store.
The process for provisioning a .NET MAUI Mac Catalyst app for distribution through the
Mac App Store is as follows:
1. Create a certificate signing request. For more information, see Create a certificate
signing request.
2. Create a distribution certificate. For more information, see Create a distribution
certificate.
3. Create an installer certificate. For more information, see Create an installer
certificate.
4. Create an App ID. For more information, see Create an App ID.
5. Configure the App ID. For more information, see Configure the App ID.
6. Create a provisioning profile. For more information, see Create a provisioning
profile.
7. Download your provisioning profile. For more information, see Download your
provisioning profile in Xcode.
Then, once provisioning is complete you should prepare your app for publishing and
then publish it with the following process:
1. Add required entitlements to your app. For more information, see Add
entitlements.
2. Update the app's Info.plist file. For more information, see Update Info.plist.
3. Publish your app using the command line. For more information, see Publish using
the command line.
2. In Keychain Access, select the Keychain Access > Certificate Assistant > Request a
Certificate from a Certificate Authority... menu item.
3. In the Certificate Assistant dialog, enter an email address in the User Email
Address field.
4. In the Certificate Assistant dialog, enter a name for the key in the Common Name
field.
5. In the Certificate Assistant dialog, leave the CA Email Address field empty.
6. In the Certificate Assistant dialog, choose the Saved to disk radio button and
select Continue:
7. Save the certificate signing request to a known location.
2. In your Apple Developer Account, select the Certificates, IDs & Profiles tab.
3. On the Certificates, Identifiers & Profiles page, select the + button to create a new
certificate.
4. On the Create a New Certificate page, select the Apple Distribution radio button
before selecting the Continue button:
5. On the Create a New Certificate page, select Choose File:
6. In the Choose Files to Upload dialog, select the certificate request file you
previously created (a file with a .certSigningRequest file extension) and then select
Upload.
The certificate file (a file with a .cer extension) will be downloaded to your chosen
location.
9. On your Mac, double-click the downloaded certificate file to install the certificate
to your keychain. The certificate appears in the My Certificates category in
Keychain Access, and begins with Apple Distribution:
7 Note
Make a note of the full certificate name in Keychain Access. It will be required
when signing your app.
1. In your Apple Developer Account, select the Certificates, IDs & Profiles tab.
2. On the Certificates, Identifiers & Profiles page, select the + button to create a new
certificate.
3. On the Create a New Certificate page, select the Mac Installer Distribution radio
button before selecting the Continue button:
4. On the Create a New Certificate page, select Choose File:
5. In the Choose Files to Upload dialog, select the certificate request file you
previously created (a file with a .certSigningRequest file extension) and then select
Upload.
8. On your Mac, double-click the downloaded certificate file to install the certificate
to your keychain. The certificate appears in the My Certificates category in
Keychain Access, and begins with 3rd Party Mac Developer Installer:
7 Note
Make a note of the full certificate name in Keychain Access. It will be required
when signing your app.
Wildcard. A wildcard App ID allows you to use a single App ID to match multiple
apps, and typically takes the form com.domainname.* . A wildcard App ID can be
used to distribute multiple apps, and should be used for apps that do not enable
app-specific capabilities.
Explicit. An explicit App ID is unique to a single app, and typically takes the form
com.domainname.myid . An explicit App ID allows the distribution of one app, with a
matching bundle identifier. Explicit App IDs are typically used for apps that enable
app-specific capabilities such as Apple Pay, or Game Center. For more information
about capabilities, see Capabilities.
2. On the Certificates, Identifiers & Profiles page, select the Identifiers tab.
3. On the Identifiers page, select the + button to create a new App ID.
4. On the Register a new identifier page, select the App IDs radio button before
selecting the Continue button:
5. On the Register a new identifier page, select App before selecting the Continue
button:
6. On the Register an App ID page, enter a description, and select either the Explicit
or Wildcard Bundle ID radio button. Then, enter the Bundle ID for your app in
reverse DS format:
) Important
The Bundle ID you enter must correspond to the Bundle identifier in the
Info.plist file in your app project.
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
7. On the Register an App ID page, select any capabilities that the app uses. Any
capabilities must be configured both on this page and in the Entitlements.plist file
in your app project. For more information see Capabilities and Entitlements.
2. On the Certificates, Identifiers & Profiles page, select the Identifiers tab.
4. On the Edit your App ID Configuration page, scroll to the bottom of the page and
enable the Mac Catalyst capability check-box. Then select the Configure button:
5. In the Configure Bundle ID for Mac Catalyst popup, select the Use existing Mac
App ID radio button. In the App ID drop-down, select either the App ID for your
Mac Catalyst's partner iOS app, or the the App ID you've created if you're offering
the Mac Catalyst app as a separate product. Then, select the Save button:
6. In the Edit your App ID Configuration page, select the Save button:
1. In the Certificates, Identifiers & Profiles page of your Apple Developer Account,
select the Profiles tab.
3. In the Register a New Provisioning Profile page, select the Mac App Store radio
button before clicking the Continue button:
4. In the Generate a Provisioning Profile page, select the Mac Catalyst radio button.
Then, in the App ID drop-down, select the App ID that you previously created
before clicking the Continue button:
7 Note
The App ID will be in the Enabled App IDs with an associated application
identifier section.
5. In the Generate a Provisioning Profile page, select the radio button that
corresponds to your distribution certificate before clicking the Continue button:
6. In the Generate a Provisioning Profile page, enter a name for the provisioning
profile before clicking the Generate button:
7 Note
7. In the Generate a Provisioning Profile page, optionally click the Download button
to download your provisioning profile.
7 Note
It's not necessary to download your provisioning profile now. Instead, you will
do this in Xcode.
4. In the Accounts tab, click the + button to add your Apple Developer Account to
Xcode:
5. In the account type popup, select Apple ID and then click the Continue button:
6. In the sign in popup, enter your Apple ID and click the Next button.
7. In the sign in popup, enter your Apple ID password and click the Next button:
8. In the Accounts tab, click the Manage Certificates... button to ensure that your
distribution certificate has been downloaded.
9. In the Accounts tab, click the Download Manual Profiles button to download your
provisioning profiles:
10. Wait for the download to complete and then close Xcode.
Add entitlements
Apple's App Sandbox restricts access to system resources and user data in Mac apps, to
contain damage if an app becomes compromised. It must be enabled for Mac Catalyst
apps that are distributed through the Mac App Store.
XML
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
If your app opens outgoing network connections, you'll also need to add the
com.apple.security.network.client key, of type boolean , to your Entitlements.plist file:
XML
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
Update Info.plist
Before publishing your app, you should update its Info.plist file with additional
information to ensure that the app can be uploaded to the Mac App Store, and to help
ensure a smooth Mac App Store review process.
The iPad user interface idiom tells macOS to scale the app's user interface to match
the Mac display environment while preserving iPad-like appearance.
The Mac user interface idiom doesn't scale the app's user interface to match the
Mac display environment. Some controls change their size and appearance, and
interacting with them feels identical to interacting with AppKit controls.
By default, .NET MAUI Mac Catalyst apps use the iPad user interface idiom. If this is your
desired behavior, ensure that the app's Info.plist file only specifies 2 as the value of the
UIDeviceFamily key:
XML
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
To adopt the Mac user interface idiom, update the app's Info.plist file to specify 6 as the
value of the UIDeviceFamily key:
XML
<key>UIDeviceFamily</key>
<array>
<integer>6</integer>
</array>
For more information about Mac Catalyst user interface idioms, see Specify the UI idiom
for your Mac Catalyst app.
XML
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
The value of the key should be a language designator, with an optional region
designator. For more information, see CFBundleDevelopmentRegion on
developer.apple.com.
XML
<key>NSHumanReadableCopyright</key>
<string>MyMauiApp © 2023</string>
XML
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
7 Note
Your app's primary category must match the primary category you set in App Store
Connect.
XML
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
Parameter Value
/p:MtouchLink The link mode for the project, which can be None , SdkOnly , or Full .
/p:CreatePackage Set to true so that a package (.pkg) is created for the app at the end
of the build.
/p:EnablePackageSigning Set to true so that the package that's created gets signed.
/p:CodesignKey The name of the code signing key. Set to the name of your
distribution certificate, as displayed in Keychain Access.
/p:CodesignProvision The provisioning profile to use when signing the app bundle.
/p:CodesignEntitlements The path to the entitlements file that specifies the entitlements the
app requires. Set to Platforms\MacCatalyst\Entitlements.plist .
/p:PackageSigningKey The package signing key to use when signing the package. Set to the
name of your installer certificate, as displayed in Keychain Access.
2 Warning
Attempting to publish a .NET MAUI solution will result in the dotnet publish
command attempting to publish each project in the solution individually, which can
cause issues when you've added other project types to your solution. Therefore, the
dotnet publish command should be scoped to your .NET MAUI app project.
Additional build parameters can be specified on the command line, if they aren't
provided in a <PropertyGroup> in your project file. The following table lists some of the
common parameters:
Parameter Value
Parameter Value
/p:ApplicationVersion The version of the build that identifies an iteration of the app.
) Important
Values for all of these parameters don't have to be provided on the command line.
They can also be provided in the project file. When a parameter is provided on the
command line and in the project file, the command line parameter takes
precedence. For more information about providing build properties in your project
file, see Define build properties in your project file.
For example, use the following command to build and sign a .pkg on a Mac, for
distribution through the Mac App Store:
.NET CLI
Publishing builds, signs, and packages the app, and then copies the .pkg to the
bin/Release/net7.0-maccatalyst/publish/ folder. If you publish the app using only a single
architecture, it will be published to the bin/Release/net7.0-
maccatalyst/{architecture}/publish/ folder.
During the signing process it maybe necessary to enter your login password and allow
codesign and productbuild to run:
For more information about the dotnet publish command, see dotnet publish.
Property Value
<ApplicationVersion> The version of the build that identifies an iteration of the app.
<CodesignKey> The name of the code signing key. Set to the name of your
distribution certificate, as displayed in Keychain Access.
<CodesignEntitlements> The path to the entitlements file that specifies the entitlements
the app requires. Set to
Platforms\MacCatalyst\Entitlements.plist .
<CodesignProvision> The provisioning profile to use when signing the app bundle.
<CreatePackage> Set to true so that a package (.pkg) is created for the app at the
end of the build.
Property Value
<EnablePackageSigning> Set to true so that the package that's created gets signed.
<MtouchLink> The link mode for the project, which can be None , SdkOnly , or
Full .
<PackageSigningKey> The package signing key to use when signing the package. Set to
the name of your installer certificate, as displayed in Keychain
Access.
) Important
Values for these build properties don't have to be provided in the project file. They
can also be provided on the command line when you publish the app. This enables
you to omit specific values from your project file.
The following example shows a typical property group for building and signing your
Mac Catalyst app for Mac App Store distribution:
XML
<PropertyGroup
Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.
0-maccatalyst|AnyCPU'">
<MtouchLink>SdkOnly</MtouchLink>
<EnableCodeSigning>True</EnableCodeSigning>
<EnablePackageSigning>true</EnablePackageSigning>
<CreatePackage>true</CreatePackage>
<CodesignProvision>MyMauiApp</CodesignProvision>
<CodesignEntitlements>Platforms\MacCatalyst\Entitlements.plist</CodesignEnti
tlements>
</PropertyGroup>
This example <PropertyGroup> adds a condition check, preventing the settings from
being processed unless the condition check passes. The condition check looks for two
items:
If any of these conditions fail, the settings aren't processed. More importantly, the
<CodesignKey> , <CodesignProvision> , and <PackageSigningKey> settings aren't set,
preventing the app from being signed.
After adding the above property group, the app can be published from the command
line on a Mac by opening a terminal and navigating to the folder for your .NET MAUI
app project. Then, run the following command:
.NET CLI
Publishing builds, signs, and packages the app, and then copies the .pkg to the
bin/Release/net7.0-maccatalyst/publish/ folder.
To distribute your app through the Mac App Store, or TestFlight, you'll need to create an
app record in App Store Connect. This record includes all the information about the app
as it will appear in the App Store and all of the information needed to manage the app
throughout the distribution process. For more information, see Create an app record
on developer.apple.com.
Transporter can be used to submit your app to the Mac App Store. It will also help to
identify errors with app packages that stop successful submission.
See also
Preparing your app for distribution on developer.apple.com
Publish a Mac Catalyst app for
distribution outside the Mac App Store
Article • 03/23/2023 • 21 minutes to read
An alternative to distributing Mac Catalyst apps through the Mac App Store is to
distribute them outside the Mac App Store. With this approach, your Mac Catalyst app
can be hosted at a location of your choosing for download. Members of the Apple
Developer Program and the Apple Developer Enterprise Program can use this
distribution approach.
Distributing a Mac Catalyst app requires that the app is provisioned using a provisioning
profile. Provisioning profiles are files that contain code signing information, as well as
the identity of the app and its intended distribution mechanism.
To distribute a .NET Multi-platform App UI (.NET MAUI) Mac Catalyst app outside the
Mac App Store, you'll need to build a distribution provisioning profile specific to it. This
profile enables the app to be digitally signed for release so that it can be installed on
Macs. A distribution provisioning profile contains an App ID and a developer ID
application certificate. You'll need to create a developer ID application certificate to
identify yourself or your organization, if you don't already have one. In addition, you'll
need to create a developer ID installer certificate to sign your app's installer package.
The process for provisioning a .NET MAUI Mac Catalyst app for distribution outside the
Mac App Store is as follows:
1. Create two certificate signing requests. For more information, see Create a
certificate signing request.
2. Create a developer ID application certificate. For more information, see Create a
developer ID application certificate.
3. Create a developer ID installer certificate. For more information, see Create a
developer ID installer certificate.
4. Create an App ID. For more information, see Create an App ID.
5. Create a provisioning profile. For more information, see Create a provisioning
profile.
6. Download your provisioning profile. For more information, see Download your
provisioning profile in Xcode.
) Important
This article shows provisioning for distribution outside the Mac App Store using an
Apple Developer Account that's part of the Apple Developer Program. It can also
be used for an Apple Developer Account that's part of the Apple Developer
Enterprise Program, as the steps required are largely identical.
Then, once provisioning is complete you should prepare your app for publishing,
publish it, and notarize it with the following process:
1. Optionally add entitlements to your app. For more information, see Add
entitlements.
2. Update the app's Info.plist file. For more information, see Update Info.plist.
3. Disable code signature verification for your app. For more information, see Disable
code signature verification.
4. Publish your app using the command line. For more information, see Publish using
the command line.
5. Notarize your app. For more information, see Notarize your app.
2. In Keychain Access, select the Keychain Access > Certificate Assistant > Request a
Certificate from a Certificate Authority... menu item.
3. In the Certificate Assistant dialog, enter an email address in the User Email
Address field.
4. In the Certificate Assistant dialog, enter a name for the key in the Common Name
field.
5. In the Certificate Assistant dialog, leave the CA Email Address field empty.
6. In the Certificate Assistant dialog, choose the Saved to disk radio button and
select Continue:
7. Save the certificate signing request to a known location.
Repeat this process to create a second certificate signing request. The first CSR will be
used to create a developer ID application certificate. The second will be used to create a
developer ID installer certificate.
) Important
You can't use the same certificate signing request to create a developer ID
application certificate and a developer ID installer certificate.
2. In your Apple Developer Account, select the Certificates, IDs & Profiles tab.
3. On the Certificates, Identifiers & Profiles page, select the + button to create a new
certificate.
4. On the Create a New Certificate page, select the Developer ID Application radio
button before selecting the Continue button:
5. On the Create a New Certificate page, select the G2 Sub-CA radio button, and
then select Choose File:
6. In the Choose Files to Upload dialog, select the first certificate request file you
previously created (a file with a .certSigningRequest file extension) and then select
Upload.
The certificate file (a file with a .cer extension) will be downloaded to your chosen
location.
9. On your Mac, double-click the downloaded certificate file to install the certificate
to your keychain. The certificate appears in the My Certificates category in
Keychain Access, and begins with Developer ID Application:
7 Note
Make a note of the full certificate name in Keychain Access. It will be required
when signing your app.
1. In your Apple Developer Account, select the Certificates, IDs & Profiles tab.
2. On the Certificates, Identifiers & Profiles page, select the + button to create a new
certificate.
3. On the Create a New Certificate page, select the Developer ID Installer radio
button before selecting the Continue button:
4. On the Create a New Certificate page, select the G2 Sub-CA radio button, and
then select Choose File:
5. In the Choose Files to Upload dialog, select the second certificate request file you
previously created (a file with a .certSigningRequest file extension) and then select
Upload.
The certificate file (a file with a .cer extension) will be downloaded to your chosen
location.
8. On your Mac, double-click the downloaded certificate file to install the certificate
to your keychain. The certificate appears in the My Certificates category in
Keychain Access, and begins with Developer ID Installer:
7 Note
Make a note of the full certificate name in Keychain Access. It will be required
when signing your app.
Create an App ID
An App ID is required to identify the app that you are distributing. An App ID is similar
to a reverse-DNS string, that uniquely identifies an app, and should be identical to the
bundle identifier for your app. You can use the same App ID that you used when
deploying your app to a device for testing.
Wildcard. A wildcard App ID allows you to use a single App ID to match multiple
apps, and typically takes the form com.domainname.* . A wildcard App ID can be
used to distribute multiple apps, and should be used for apps that do not enable
app-specific capabilities.
Explicit. An explicit App ID is unique to a single app, and typically takes the form
com.domainname.myid . An explicit App ID allows the distribution of one app, with a
matching bundle identifier. Explicit App IDs are typically used for apps that enable
app-specific capabilities such as Apple Pay, or Game Center. For more information
about capabilities, see Capabilities.
2. On the Certificates, Identifiers & Profiles page, select the Identifiers tab.
3. On the Identifiers page, select the + button to create a new App ID.
4. On the Register a new identifier page, select the App IDs radio button before
selecting the Continue button:
5. On the Register a new identifier page, select App before selecting the Continue
button:
6. On the Register an App ID page, enter a description, and select either the Explicit
or Wildcard Bundle ID radio button. Then, enter the Bundle ID for your app in
reverse DS format:
) Important
The Bundle ID you enter must correspond to the Bundle identifier in the
Info.plist file in your app project.
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
7. On the Register an App ID page, select any capabilities that the app uses. Any
capabilities must be configured both on this page and in the Entitlements.plist file
in your app project. For more information see Capabilities and Entitlements.
1. In the Certificates, Identifiers & Profiles page of your Apple Developer Account,
select the Profiles tab.
3. In the Register a New Provisioning Profile page, select the Developer ID radio
button before clicking the Continue button:
4. In the Generate a Provisioning Profile page, select the Mac Catalyst radio button.
Then, in the App ID drop-down, select the App ID that you previously created
before clicking the Continue button:
5. In the Generate a Provisioning Profile page, select the radio button that
corresponds to your distribution certificate before clicking the Continue button:
6. In the Generate a Provisioning Profile page, enter a name for the provisioning
profile before clicking the Generate button:
7 Note
7. In the Generate a Provisioning Profile page, optionally click the Download button
to download your provisioning profile.
7 Note
It's not necessary to download your provisioning profile now. Instead, you will
do this in Xcode.
4. In the Accounts tab, click the + button to add your Apple Developer Account to
Xcode:
5. In the account type popup, select Apple ID and then click the Continue button:
6. In the sign in popup, enter your Apple ID and click the Next button.
7. In the sign in popup, enter your Apple ID password and click the Next button:
8. In the Accounts tab, click the Manage Certificates... button to ensure that your
distribution certificate has been downloaded.
9. In the Accounts tab, click the Download Manual Profiles button to download your
provisioning profiles:
10. Wait for the download to complete and then close Xcode.
Add entitlements
Apple's App Sandbox restricts access to system resources and user data in Mac apps, to
contain damage if an app becomes compromised. It can optionally be enabled for Mac
Catalyst apps that are distributed outside the Mac App Store.
This can be accomplished by adding an Entitlements.plist file to the
Platforms/MacCatalyst folder of your .NET MAUI app project:
XML
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
If your app opens outgoing network connections, you'll also need to add the
com.apple.security.network.client key, of type boolean , to your Entitlements.plist file:
XML
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
Update Info.plist
Before publishing your app, you should update its Info.plist file with additional
information.
Specify the user interface idiom
A Mac Catalyst app can run in the iPad or Mac user interface idiom:
The iPad user interface idiom tells macOS to scale the app's user interface to match
the Mac display environment while preserving iPad-like appearance.
The Mac user interface idiom doesn't scale the app's user interface to match the
Mac display environment. Some controls change their size and appearance, and
interacting with them feels identical to interacting with AppKit controls.
By default, .NET MAUI Mac Catalyst apps use the iPad user interface idiom. If this is your
desired behavior, ensure that the app's Info.plist file only specifies 2 as the value of the
UIDeviceFamily key:
XML
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
To adopt the Mac user interface idiom, update the app's Info.plist file to specify 6 as the
value of the UIDeviceFamily key:
XML
<key>UIDeviceFamily</key>
<array>
<integer>6</integer>
</array>
For more information about Mac Catalyst user interface idioms, see Specify the UI idiom
for your Mac Catalyst app.
XML
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
The value of the key should be a language designator, with an optional region
designator. For more information, see CFBundleDevelopmentRegion on
developer.apple.com.
XML
<key>NSHumanReadableCopyright</key>
<string>MyMauiApp © 2023</string>
XML
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
While codesign succeeds in signing your app, the _CodesignVerify target fails to verify
the code signature:
Therefore, it's currently necessary to add the following build target to the end of your
project file to disable verification of the code signature:
XML
<Project Sdk="Microsoft.NET.Sdk">
...
<PropertyGroup>
<_RequireCodeSigning>false</_RequireCodeSigning>
</PropertyGroup>
</Target>
</Project>
Parameter Value
/p:MtouchLink The link mode for the project, which can be None , SdkOnly , or Full .
Parameter Value
/p:CreatePackage Set to true so that a package (.pkg) is created for the app at the end
of the build.
/p:EnablePackageSigning Set to true so that the package that's created gets signed.
/p:CodesignKey The name of the code signing key. Set to the name of your
distribution certificate, as displayed in Keychain Access.
/p:CodesignProvision The provisioning profile to use when signing the app bundle.
/p:CodesignEntitlements The path to the entitlements file that specifies the entitlements the
app requires. Set to Platforms\MacCatalyst\Entitlements.plist .
/p:PackageSigningKey The package signing key to use when signing the package. Set to the
name of your installer certificate, as displayed in Keychain Access.
/p:UseHardenedRuntime Set to true to enable the hardened runtime, which is required for
Mac Catalyst apps that are distributed outside of the Mac App Store.
2 Warning
Attempting to publish a .NET MAUI solution will result in the dotnet publish
command attempting to publish each project in the solution individually, which can
cause issues when you've added other project types to your solution. Therefore, the
dotnet publish command should be scoped to your .NET MAUI app project.
Additional build parameters can be specified on the command line, if they aren't
provided in a <PropertyGroup> in your project file. The following table lists some of the
common parameters:
Parameter Value
/p:ApplicationVersion The version of the build that identifies an iteration of the app.
/p:RuntimeIdentifier The runtime identifier (RID) for the project. Release builds of
.NET MAUI Mac Catalyst apps default to using maccatalyst-x64
and maccatalyst-arm64 as runtime identifiers, to support
universal apps. To support only a single architecture, specify
maccatalyst-x64 or maccatalyst-arm64 .
) Important
Values for all of these parameters don't have to be provided on the command line.
They can also be provided in the project file. When a parameter is provided on the
command line and in the project file, the command line parameter takes
precedence. For more information about providing build properties in your project
file, see Define build properties in your project file.
For example, use the following command to build and sign a .pkg on a Mac, for
distribution outside the Mac App Store:
.NET CLI
Publishing builds, signs, and packages the app, and then copies the .pkg to the
bin/Release/net7.0-maccatalyst/publish/ folder. If you publish the app using only a single
architecture, it will be published to the bin/Release/net7.0-
maccatalyst/{architecture}/publish/ folder.
During the signing process it maybe necessary to enter your login password and allow
codesign and productbuild to run:
For more information about the dotnet publish command, see dotnet publish.
Property Value
<ApplicationVersion> The version of the build that identifies an iteration of the app.
<CodesignKey> The name of the code signing key. Set to the name of your
distribution certificate, as displayed in Keychain Access.
<CodesignEntitlements> The path to the entitlements file that specifies the entitlements
the app requires. Set to
Platforms\MacCatalyst\Entitlements.plist .
<CodesignProvision> The provisioning profile to use when signing the app bundle.
<CreatePackage> Set to true so that a package (.pkg) is created for the app at the
end of the build.
Property Value
<EnablePackageSigning> Set to true so that the package that's created gets signed.
<MtouchLink> The link mode for the project, which can be None , SdkOnly , or
Full .
<PackageSigningKey> The package signing key to use when signing the package. Set to
the name of your installer certificate, as displayed in Keychain
Access.
<RuntimeIdentifier> The runtime identifier (RID) for the project. Release builds of .NET
MAUI Mac Catalyst apps default to using maccatalyst-x64 and
maccatalyst-arm64 as runtime identifiers, to support universal
apps. To support only a single architecture, specify maccatalyst-
x64 or maccatalyst-arm64 .
<UseHardenedRuntime> Set to true to enable the hardened runtime, which is required for
Mac Catalyst apps that are distributed outside of the Mac App
Store.
) Important
Values for these build properties don't have to be provided in the project file. They
can also be provided on the command line when you publish the app. This enables
you to omit specific values from your project file.
The following example shows a typical property group for building and signing your
Mac Catalyst app for distribution outside the Mac App Store:
XML
<PropertyGroup
Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.
0-maccatalyst|AnyCPU'">
<MtouchLink>SdkOnly</MtouchLink>
<EnableCodeSigning>True</EnableCodeSigning>
<EnablePackageSigning>true</EnablePackageSigning>
<CreatePackage>true</CreatePackage>
<CodesignEntitlements>Platforms\MacCatalyst\Entitlements.plist</CodesignEnti
tlements>
<UseHardenedRuntime>true</UseHardenedRuntime>
</PropertyGroup>
This example <PropertyGroup> adds a condition check, preventing the settings from
being processed unless the condition check passes. The condition check looks for two
items:
If any of these conditions fail, the settings aren't processed. More importantly, the
<CodesignKey> , <CodesignProvision> , and <PackageSigningKey> settings aren't set,
After adding the above property group, the app can be published from the command
line on a Mac by opening a terminal and navigating to the folder for your .NET MAUI
app project. Then, run the following command:
.NET CLI
Publishing builds, signs, and packages the app, and then copies the .pkg to the
bin/Release/net7.0-maccatalyst/publish/ folder.
Apps can be submitted to Apple's notary service with the notarytool command line
tool:
zsh
An example of using the notarytool tool to sign a .NET MAUI Mac Catalyst .pkg file is
shown below:
zsh
The wait flag tells notarytool to exit only after the notary service finishes processing
the submission, therefore removing the need to poll the service for its status. The apple-
id , password , and team-id arguments are used to supply your App Store Connect
credentials. Because App Store Connect requires two-factor authentication, you must
create an app-specific password for notarytool . For information about creating an app-
specific password, see Sign in to apps with your Apple ID using app-specific
passwords on developer.apple.com.
After you submit your app to Apple, the notarization process typically takes less than an
hour.
7 Note
Notarization completes for most apps within 5 minutes. For information about
avoiding long response times from the notary service, see Avoid long notarization
response times and size limits on developer.apple.com.
Provided that notarization succeeds, the notary service generates a ticket for the signed
installer package and the app bundle inside it.
For more information about notarization, see Notarizing macOS software before
distribution . For other approaches to submitting your app to Apple's notary service,
see Upload your app to the notarization service on developer.apple.com. For
information about resolving common notarization issues, see Resolving common
notarization issues .
After notarization has succeeded, you should attach the ticket to your app using the
stapler tool. This ensures that Gatekeeper can find the ticket even when a network
connection isn't available. Run the following command to attach the ticket to your .pkg:
zsh
The stapler tool retrieves the ticket and attaches it to the installer package. You'll
receive a message telling you that the staple and validate action worked, provided that
stapling succeeds.
Validate notarization
If you want to validate notarization, you can do so with the following command:
zsh
For information about safely opening apps on a Mac, see Open apps safely on your
Mac on support.apple.com.
See also
Preparing your app for distribution on developer.apple.com
Hardened Runtime on developer.apple.com
Customizing the notarization workflow on developer.apple.com
Publish a Mac Catalyst app for ad-hoc
distribution
Article • 03/23/2023 • 17 minutes to read
When distributing Mac Catalyst apps outside the Mac App Store, you can also choose to
distribute your app to a limited number of users on registered devices. This is known as
ad-hoc distribution, and is primarily used for testing apps within a group of people.
However, it's limited to 100 devices per membership year, and the devices must be
added to your Apple Developer Account. Members of the Apple Developer Program and
the Apple Developer Enterprise Program can use this distribution approach.
Distributing a Mac Catalyst app requires that the app is provisioned using a provisioning
profile. Provisioning profiles are files that contain code signing information, as well as
the identity of the app and its intended distribution mechanism.
To distribute a .NET Multi-platform App UI (.NET MAUI) Mac Catalyst app outside the
Mac App Store with ad-hoc distribution, you'll need to build a development provisioning
profile specific to it. This profile enables the app to be digitally signed for release so that
it can be installed on Macs. An ad-hoc development provisioning profile contains an
App ID, a development certificate, and a list of the devices that can install the app. You'll
need to create a development certificate to identify yourself or your organization, if you
don't already have one.
The process for provisioning a .NET MAUI Mac Catalyst app for ad-hoc distribution is as
follows:
1. Create a certificate signing request. For more information, see Create a certificate
signing request.
2. Create a development certificate. For more information, see Create a development
certificate.
3. Create an App ID. For more information, see Create an App ID.
4. Add devices to your Apple Developer Account. For more information, see Add
devices.
5. Create a provisioning profile. For more information, see Create a provisioning
profile.
6. Download your provisioning profile. For more information, see Download your
provisioning profile in Xcode.
) Important
This article shows provisioning for ad-hoc distribution using an Apple Developer
Account that's part of the Apple Developer Program. It can also be used for an
Apple Developer Account that's part of the Apple Developer Enterprise Program, as
the steps required are largely identical.
Then, once provisioning is complete you should prepare your app for publishing, and
then publish it with the following process:
1. Optionally add entitlements to your app. For more information, see Add
entitlements.
2. Update the app's Info.plist file. For more information, see Update Info.plist.
3. Publish your app using the command line. For more information, see Publish using
the command line.
2. In Keychain Access, select the Keychain Access > Certificate Assistant > Request a
Certificate from a Certificate Authority... menu item.
3. In the Certificate Assistant dialog, enter an email address in the User Email
Address field.
4. In the Certificate Assistant dialog, enter a name for the key in the Common Name
field.
5. In the Certificate Assistant dialog, leave the CA Email Address field empty.
6. In the Certificate Assistant dialog, choose the Saved to disk radio button and
select Continue:
7. Save the certificate signing request to a known location.
2. In your Apple Developer Account, select the Certificates, IDs & Profiles tab.
3. On the Certificates, Identifiers & Profiles page, select the + button to create a new
certificate.
4. On the Create a New Certificate page, select the Apple Development radio button
before selecting the Continue button:
5. On the Create a New Certificate page, select Choose File:
6. In the Choose Files to Upload dialog, select the certificate request file you
previously created (a file with a .certSigningRequest file extension) and then select
Upload.
The certificate file (a file with a .cer extension) will be downloaded to your chosen
location.
9. On your Mac, double-click the downloaded certificate file to install the certificate
to your keychain. The certificate appears in the My Certificates category in
Keychain Access, and begins with Apple Development:
7 Note
Make a note of the full certificate name in Keychain Access. It will be required
when signing your app.
Create an App ID
An App ID is required to identify the app that you are distributing. An App ID is similar
to a reverse-DNS string, that uniquely identifies an app, and should be identical to the
bundle identifier for your app. You can use the same App ID that you used when
deploying your app to a device for testing.
matching bundle identifier. Explicit App IDs are typically used for apps that enable
app-specific capabilities such as Apple Pay, or Game Center. For more information
about capabilities, see Capabilities.
2. On the Certificates, Identifiers & Profiles page, select the Identifiers tab.
3. On the Identifiers page, select the + button to create a new App ID.
4. On the Register a new identifier page, select the App IDs radio button before
selecting the Continue button:
5. On the Register a new identifier page, select App before selecting the Continue
button:
6. On the Register an App ID page, enter a description, and select either the Explicit
or Wildcard Bundle ID radio button. Then, enter the Bundle ID for your app in
reverse DS format:
) Important
The Bundle ID you enter must correspond to the Bundle identifier in the
Info.plist file in your app project.
The bundle identifier for a .NET MAUI app is stored in the project file as the
Application ID property:
When the value of the Application ID field is updated, the value of the Bundle
identifier in the Info.plist will be automatically updated.
7. On the Register an App ID page, select any capabilities that the app uses. Any
capabilities must be configured both on this page and in the Entitlements.plist file
in your app project. For more information see Capabilities and Entitlements.
Add devices
When creating a provisioning profile for ad-hoc distribution, the profile must include
which devices can run the app. Before selecting the devices to be added to the
provisioning profile you must first add devices to your Apple Developer Account. This
can be achieved with the following steps:
1. Select the Apple > About this Mac menu item.
3. In the System Report, select the Hardware expander to see the hardware overview.
The report displays the universally unique identifier (UUID) as Hardware UUID in
macOS 10.15 and later, or Provisioning UDID in macOS 11.0 and later.
4. Select the Hardware UUID or Provisioning UDID value and copy it to the
clipboard.
6. In the Register a New Device page, set the Platform to macOS and provide a
name for the new device. Then paste the identifier from the clipboard into the
Device ID (UUID) field, and click the Continue button:
7. In the Register a New Device page, review the information and then click the
Register button.
Repeat the above steps for any Macs that you want to deploy your .NET MAUI Mac
Catalyst app to using ad-hoc distribution.
1. In the Certificates, Identifiers & Profiles page of your Apple Developer Account,
select the Profiles tab.
3. In the Register a New Provisioning Profile page, select the macOS App
Development radio button before clicking the Continue button:
4. In the Generate a Provisioning Profile page, select the Mac Catalyst radio button.
Then, in the App ID drop-down, select the App ID that you previously created
before clicking the Continue button:
5. In the Generate a Provisioning Profile page, select the check box that corresponds
to your development certificate before clicking the Continue button:
6. In the Generate a Provisioning Profile page, select the devices that the app will be
installed on and then click the Continue button.
7. In the Generate a Provisioning Profile page, enter a name for the provisioning
profile before clicking the Generate button:
7 Note
7 Note
It's not necessary to download your provisioning profile now. Instead, you will
do this in Xcode.
4. In the Accounts tab, click the + button to add your Apple Developer Account to
Xcode:
5. In the account type popup, select Apple ID and then click the Continue button:
6. In the sign in popup, enter your Apple ID and click the Next button.
7. In the sign in popup, enter your Apple ID password and click the Next button:
8. In the Accounts tab, click the Manage Certificates... button to ensure that your
distribution certificate has been downloaded.
9. In the Accounts tab, click the Download Manual Profiles button to download your
provisioning profiles:
10. Wait for the download to complete and then close Xcode.
Add entitlements
Apple's App Sandbox restricts access to system resources and user data in Mac apps, to
contain damage if an app becomes compromised. It must be enabled for Mac Catalyst
apps that are distributed through the Mac App Store, and can optionally be enabled for
Mac Catalyst apps that are distributed outside the Mac App Store.
XML
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
If your app opens outgoing network connections, you'll also need to add the
com.apple.security.network.client key, of type boolean , to your Entitlements.plist file:
XML
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
Update Info.plist
Before distributing your app, you should update its Info.plist file with additional
information.
7 Note
While it's not a requirement to update your app's Info.plist file when distributing it
for testing, these updates will be required when distributing the final tested version
of your app. Therefore, it's best practice to perform these updates.
The iPad user interface idiom tells macOS to scale the app's user interface to match
the Mac display environment while preserving iPad-like appearance.
The Mac user interface idiom doesn't scale the app's user interface to match the
Mac display environment. Some controls change their size and appearance, and
interacting with them feels identical to interacting with AppKit controls.
By default, .NET MAUI Mac Catalyst apps use the iPad user interface idiom. If this is your
desired behavior, ensure that the app's Info.plist file only specifies 2 as the value of the
UIDeviceFamily key:
XML
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
To adopt the Mac user interface idiom, update the app's Info.plist file to specify 6 as the
value of the UIDeviceFamily key:
XML
<key>UIDeviceFamily</key>
<array>
<integer>6</integer>
</array>
For more information about Mac Catalyst user interface idioms, see Specify the UI idiom
for your Mac Catalyst app.
XML
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
The value of the key should be a language designator, with an optional region
designator. For more information, see CFBundleDevelopmentRegion on
developer.apple.com.
<key>NSHumanReadableCopyright</key>
<string>MyMauiApp © 2023</string>
XML
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
Parameter Value
/p:MtouchLink The link mode for the project, which can be None , SdkOnly , or Full .
/p:CreatePackage Set to true so that a package (.pkg) is created for the app at the end
of the build.
/p:CodesignKey The name of the code signing key. Set to the name of your
distribution certificate, as displayed in Keychain Access.
/p:CodesignProvision The provisioning profile to use when signing the app bundle.
/p:CodesignEntitlements The path to the entitlements file that specifies the entitlements the
app requires. Set to Platforms\MacCatalyst\Entitlements.plist .
/p:UseHardenedRuntime Set to true to enable the hardened runtime, which is required for
Mac Catalyst apps that are distributed outside of the Mac App Store.
2 Warning
Attempting to publish a .NET MAUI solution will result in the dotnet publish
command attempting to publish each project in the solution individually, which can
cause issues when you've added other project types to your solution. Therefore, the
dotnet publish command should be scoped to your .NET MAUI app project.
Additional build parameters can be specified on the command line, if they aren't
provided in a <PropertyGroup> in your project file. The following table lists some of the
common parameters:
Parameter Value
/p:ApplicationVersion The version of the build that identifies an iteration of the app.
/p:RuntimeIdentifier The runtime identifier (RID) for the project. Release builds of
.NET MAUI Mac Catalyst apps default to using maccatalyst-x64
and maccatalyst-arm64 as runtime identifiers, to support
universal apps. To support only a single architecture, specify
maccatalyst-x64 or maccatalyst-arm64 .
) Important
Values for all of these parameters don't have to be provided on the command line.
They can also be provided in the project file. When a parameter is provided on the
command line and in the project file, the command line parameter takes
precedence. For more information about providing build properties in your project
file, see Define build properties in your project file.
For example, use the following command to build and sign a .pkg on a Mac, for ad-hoc
distribution to users on registered devices:
.NET CLI
Publishing builds, signs, and packages the app, and then copies the .pkg to the
bin/Release/net7.0-maccatalyst/publish/ folder. If you publish the app using only a single
architecture, it will be published to the bin/Release/net7.0-
maccatalyst/{architecture}/publish/ folder.
During the signing process it maybe necessary to enter your login password and allow
codesign to run:
For more information about the dotnet publish command, see dotnet publish.
Property Value
Property Value
<ApplicationVersion> The version of the build that identifies an iteration of the app.
<CodesignKey> The name of the code signing key. Set to the name of your
distribution certificate, as displayed in Keychain Access.
<CodesignEntitlements> The path to the entitlements file that specifies the entitlements
the app requires. Set to
Platforms\MacCatalyst\Entitlements.plist .
<CodesignProvision> The provisioning profile to use when signing the app bundle.
<CreatePackage> Set to true so that a package (.pkg) is created for the app at the
end of the build.
<MtouchLink> The link mode for the project, which can be None , SdkOnly , or
Full .
<RuntimeIdentifier> The runtime identifier (RID) for the project. Release builds of .NET
MAUI Mac Catalyst apps default to using maccatalyst-x64 and
maccatalyst-arm64 as runtime identifiers, to support universal
apps. To support only a single architecture, specify maccatalyst-
x64 or maccatalyst-arm64 .
<UseHardenedRuntime> Set to true to enable the hardened runtime, which is required for
Mac Catalyst apps that are distributed outside of the Mac App
Store.
) Important
Values for these build properties don't have to be provided in the project file. They
can also be provided on the command line when you publish the app. This enables
you to omit specific values from your project file.
The following example shows a typical property group for building and signing your
Mac Catalyst app for ad-hoc distribution to users on registered devices:
XML
<PropertyGroup
Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.
0-maccatalyst|AnyCPU'">
<MtouchLink>SdkOnly</MtouchLink>
<EnableCodeSigning>True</EnableCodeSigning>
<CreatePackage>true</CreatePackage>
<CodesignProvision>MyMauiApp (Ad-hoc)</CodesignProvision>
<CodesignEntitlements>Platforms\MacCatalyst\Entitlements.plist</CodesignEnti
tlements>
<UseHardenedRuntime>true</UseHardenedRuntime>
</PropertyGroup>
This example <PropertyGroup> adds a condition check, preventing the settings from
being processed unless the condition check passes. The condition check looks for two
items:
If any of these conditions fail, the settings aren't processed. More importantly, the
<CodesignKey> and <CodesignProvision> settings aren't set, preventing the app from
being signed.
After adding the above property group, the app can be published from the command
line on a Mac by opening a terminal and navigating to the folder for your .NET MAUI
app project. Then, run the following command:
.NET CLI
Publishing builds, signs, and packages the app, and then copies the .pkg to the
bin/Release/net7.0-maccatalyst/publish/ folder.
See also
Distributing your app to registered devices on developer.apple.com
Preparing your app for distribution on developer.apple.com
Hardened Runtime on developer.apple.com
Publish a .NET MAUI app for Windows
Article • 03/23/2023 • 2 minutes to read
When distributing your .NET Multi-platform App UI (.NET MAUI) app for Windows, you
can publish the app and its dependencies to a folder for deployment to another system.
Publishing a .NET MAUI app for Windows creates an MSIX app package, which has
numerous benefits for the users installing your app. For more information about the
benefits of MSIX, see What is MSIX?.
.NET MAUI currently only allows publishing an MSIX package. You can't yet publish a
Windows executable file for distribution.
) Important
Blazor Hybrid apps require a WebView on the host platform. For more information,
see Keep the Web View current in deployed Blazor Hybrid apps.
Configuration
The MSIX package is configured by the Platforms\Windows\Package.appxmanifest (the
manifest) file in your project. The manifest is used by the MSIX installer, the Microsoft
store, and by Windows, to configure and display your app. .NET MAUI does use some
shared settings across platforms, such as the app name and icon, which is set in the
manifest at build-time. Besides those few settings, you'll need to edit the manifest to
configure the app package to create a nice installer experience. The Microsoft Store has
its own requirements, set in the manifest, when submitting your app.
You can use the Manifest Designer feature of Visual Studio to visually edit the
Package.appxmanifest file, which affects how the app is displayed in the Microsoft Store
and in Windows. You can also edit the Package.appxmanifest file using the XML editor.
To use the Manifest Designer, find the Solution Explorer pane, then right-click
Platforms\Windows\Package.appxmanifest > Properties.
To use the XML editor, find the Solution Explorer pane, then right-click
Platforms\Windows\Package.appxmanifest > View Code.
) Important
The Manifest Designer for .NET MAUI projects can't edit app capabilities. For the
time being, you'll need to use the XML editor.
For more information on specific app manifest settings, see App manifest schema
reference.
When distributing your .NET Multi-platform App UI (.NET MAUI) app for Windows, you
can publish the app and its dependencies to a folder for deployment to another system.
You can also package the app into an MSIX package, which has numerous benefits for
the users installing your app. For more information about the benefits of MSIX, see
What is MSIX?
.NET MAUI currently only allows publishing an MSIX package. You can't yet publish a
Windows executable file for distribution.
7 Note
When you create and use a self-signed certificate only users who install and trust
your certificate can run your app. This is easy to implement for testing but it may
prevent additional users from installing your app. When you are ready to publish
your app we recommend that you use a certificate issued by a trusted source. This
system of centralized trust helps to ensure that the app ecosystem has levels of
verification to protect users from malicious actors.
1. Open a PowerShell terminal and navigate to the directory with your project.
The <PublisherName> value is displayed to the user when they install your app,
supply your own value and omit the < > characters. You can set the FriendlyName
parameter to any string of text you want.
PowerShell
-Subject "CN=<PublisherName>" `
-KeyUsage DigitalSignature `
-CertStoreLocation "Cert:\CurrentUser\My" `
-TextExtension @("2.5.29.37=
{text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}")
3. Use the following PowerShell command to query the certificate store for the
certificate that was created:
PowerShell
text
Thumbprint Subject
FriendlyName
---------- -------
------------
DE8B962E7BF797CB48CCF66C8BCACE65C6585E2F CN=1f23fa36-2a2f-475e-a69e-
3a14fe56ed4
A6CA34FD0BA6B439787391F51C87B1AD0C9E7FAE CN=someone@microsoft.com
94D93DBC97D4F7E4364A215F15C6ACFEFC71E569 CN=localhost
ASP.NET Core HTTPS development certificate
F14211566DACE867DA0BF9C2F9C47C01E3CF1D9B CN=john
568027317BE8EE5E6AACDE5079D2DE76EC46EB88 CN=e1f823e2-4674-03d2-aaad-
21ab23ad84ae
07AD38F3B646F5AAC16F2F2570CAE40F4842BBE0 CN=Contoso
My temp dev cert
4. The Thumbprint of your certificate will be used later, copy it to your clipboard. It's
the Thumbprint value whose entry matches the Subject and FriendlyName of your
certificate.
Add the following <PropertyGroup> node to your project file. This property group is only
processed when the target framework is Windows and the configuration is set to
Release . This config section runs whenever a build or publish in Release mode.
XML
<PropertyGroup
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
== 'windows' and '$(Configuration)' == 'Release'">
<AppxPackageSigningEnabled>true</AppxPackageSigningEnabled>
<PackageCertificateThumbprint>A10612AF095FD8F8255F4C6691D88F79EF2B135E</Pack
ageCertificateThumbprint>
</PropertyGroup>
<PropertyGroup
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
== 'windows' and '$(RuntimeIdentifierOverride)' != ''">
<RuntimeIdentifier>$(RuntimeIdentifierOverride)</RuntimeIdentifier>
</PropertyGroup>
The second <PropertyGroup> in the example is required to work around a bug in the
Windows SDK. For more information about the bug, see WindowsAppSDK Issue
#2940 .
Publish
To publish your app, open the Developer Command Prompt for VS 2022 terminal and
navigate to the folder for your .NET MAUI app project. Run the dotnet publish
command, providing the following parameters:
Parameter Value
2 Warning
Attempting to publish a .NET MAUI solution will result in the dotnet publish
command attempting to publish each project in the solution individually, which can
cause issues when you've added other project types to your solution. Therefore, the
dotnet publish command should be scoped to your .NET MAUI app project.
For example:
Console
Publishing builds and packages the app, copying the signed package to the
bin\Release\net7.0-windows10.0.19041.0\win10-x64\AppPackages\<appname>\ folder.
<appname> is a folder named after both your project and version. In this folder, there's
an msix file, and that's the app package.
For more information about the dotnet publish command, see dotnet publish.
If you're prompted by User Account Control to Do you want to allow this app to
make changes to your device?, select Yes.
7. In the Certificate Import Wizard window, select Place all certificates in the
following store.
8. Select Browse... and then choose the Trusted People store. Select OK to close the
dialog.
9. Select Next and then Finish. You should see a dialog that says: The import was
successful.
10. Select OK on any window opened as part of this process, to close them all.
Now, try opening the package file again to install the app. You should see a dialog
similar to the following, with the Publisher correctly displayed:
Select the Install button if you would like to install the app.
Current limitations
The following list describes the current limitations with publishing and packaging:
The published app doesn't work if you try to run it directly with the executable file
out of the publish folder.
The way to run the app is to first install it through the packaged MSIX file.
Publish a .NET MAUI app for Windows
with Visual Studio
Article • 12/02/2022 • 3 minutes to read
This article describes how to use Visual Studio to publish your .NET MAUI app for
Windows. .NET MAUI apps are packaged into an MSIX package, which is used for
installing in Windows or for submission to the Microsoft Store. For more information
about the benefits of MSIX, see What is MSIX?.
Tip
.NET MAUI currently only allows publishing an MSIX package. You can't yet publish
a Windows executable file for distribution.
2. In the Create App Packages dialog, select Sideloading, and then select Next.
a. Select Create.
You can create a temporary self-signed certificate for testing. This certificate
shouldn't be used to distribute your app package, it should only be used for
testing your app's installation process.
b. In the Create a self-signed test certificate dialog box, enter a company name
used to represent the publisher of your app. Next, type in a password for the
certificate, and enter the same password into the Confirm your password box.
c. Select OK to return to the previous dialog.
Once you've selected a certificate, you should see the certificate's information
displayed in the dialog box. Select the Next button to move on to the next dialog.
4. In the Select and configure packages dialog, you can select a version for the app
package or leave it at its default of 0.0.0.0 . The Automatically increment
checkbox determines if the version of the package is increased everytime it's
published.
6. The next dialog displayed is the Configure update settings dialog. This is where
you configure the installer location for your app, and how often the app should
check for updates.
Whenever you publish an updated version of the app, it overwrites the previous
version of the app at the Installer location. When users run your app, and based
on how often your app checks for updates, the app checks this location for an
updated version, and if found, installs it.
Once you select an Installer location, select Create.
7. After pressing Create, the installer is created and the Finished creating package
dialog is displayed, which summarizes your package.
There may be two options to close the dialog box. If you have the Copy and close
button, select it to copy the package to the Installer location you selected during
the Configure update settings step. Otherwise, select Close to close the dialog
box.
Current limitations
The following list describes the current limitations with publishing and packaging:
The published app doesn't work if you try to run it directly with the executable file
out of the publish folder.
The way to run the app is to first install it through the packaged MSIX file.
Troubleshooting known issues
Article • 03/08/2023 • 4 minutes to read
This article describes some of the known issues with .NET Multi-platform App UI (.NET
MAUI), and how you can solve or work around them. The .NET MAUI repository also
details some known issues.
1. Visual Studio on Windows can install .msi files for each workload pack.
2. dotnet workload install commands.
On Windows, if you run a dotnet workload install after installing .NET MAUI through
the Visual Studio installer, Visual Studio can enter a state where it can't locate the .NET
MAUI workloads. You'll then receive build errors telling you to install the .NET MAUI
workloads, and it's possible to enter a state where the workloads can't be repaired or re-
installed. For more information about this issue, see The CLI and VS do not play nicely .
Windows
The solution to this issue on Windows is to uninstall the .NET MAUI workloads through
the CLI, uninstall any .NET SDKs in Control Panel, and uninstall the .NET MAUI workloads
in Visual Studio. This can be accomplished with the following process:
1. Run dotnet workload uninstall maui if you ever used the dotnet workload
install commands.
2. Uninstall any standalone .NET SDK installers from Control Panel. These will have
names similar to Microsoft .NET SDK 6.0.300 .
3. In every instance of Visual Studio, uninstall the .NET Multi-platform App UI
development, and .NET desktop development Visual Studio workloads.
Then, check if there are additional .msi files that need uninstalling by running the
following command:
This reg query command will list .NET 6+ SDKS that are still installed on your machine,
such as:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\currentversion\uninstall\
{EEC1BB5F-3391-43C2-810E-42D78ADF3140}
InstallSource REG_SZ
C:\ProgramData\Microsoft\VisualStudio\Packages\Microsoft.MacCatalyst.Manifes
t-6.0.300,version=125.179.40883,chip=x64,productarch=neutral\
If you receive similar output, you should copy the GUID for each package and uninstall
the package with the msiexec command:
Then, you should keep executing the reg query command until it doesn't return any
results. Once this happens, and all .NET 6+ SDKs are uninstalled, you should also
consider deleting the following folders:
C:\Program Files\dotnet\sdk-manifests
C:\Program Files\dotnet\metadata
C:\Program Files\dotnet\packs
C:\Program Files\dotnet\library-packs
C:\Program Files\dotnet\template-packs
After following this process, you should be able to re-install .NET MAUI either through
Visual Studio, or by installing your chosen .NET SDK version and running the dotnet
workload install maui command.
Mac
Visual Studio for Mac's installer and updater uses dotnet workload install commands
to install the .NET MAUI .pkg files.
Since .pkg files can't be uninstalled, the simplest approach to uninstalling the workloads
on a Mac is to run the following commands to delete the specified folders:
zsh
rm -r ~/.dotnet/
sudo rm -r /usr/local/share/dotnet/
After executing these commands, you should be able to re-install .NET MAUI either
through Visual Studio for Mac, or by installing your chosen .NET SDK version and
running dotnet workload install maui command.
Platform version is not present for one or more target frameworks, even though
they have specified a platform: net7.0-android, net7.0-ios, net7.0-maccatalyst
This problem typically results from having an x86 and x64 SDK installed, and the x86
version is being used. Visual Studio and .NET MAUI require the x64 .NET SDK. If your
operating system has a system-wide PATH variable that is resolving the x86 SDK first,
you'll need to fix that by either removing the x86 .NET SDK from the PATH variable, or
promoting the x64 .NET SDK so that it resolves first. For more information on
troubleshooting x86 vs x64 SDK resolution, see Install .NET on Windows -
Troubleshooting.
The type or namespace name 'Default' does not exist in the namespace
'Contacts' (are you missing an assembly reference?)
The iOS and macOS platforms contain a root namespace named Contacts . This conflict
causes a conflict for those platforms with the
Microsoft.Maui.ApplicationModel.Communication namespace, which contains a Contacts
C#
zsh