The DevExpress MAUI MVVM Library ships as part of the DevExpress .NET MAUI Mobile UI component suite.
Use the DevExpress MAUI MVVM Library to leverage MVVM design patterns in your next great MAUI application. Our MVVM library is based on the .NET Community Toolkit.
Requirements
Register your DevExpress NuGet Feed within Visual Studio to restore packages used in this solution. Refer to the following article for additional information: Get Started with DevExpress Mobile UI for .NET MAUI.
Watch the following YouTube video to get started with DevExpress .NET MAUI controls: Setting up a .NET MAUI Project.
What's in This Repository
This repository demonstrates the following basic features available in the DevExpress MAUI MVVM Library:
- Dispatcher
- Navigation Service
- Popup Service
- Validation
- Localization Service
- Print Service
- Save File Picker
- File Picker
- File System
- UI Service
- Dependency Injections
Documentation
More Examples
- Demo App
- Stocks App
- Data Form
- Data Editors
- Data Grid
- Collection View
- Charts
- Scheduler
- Tab Page
- Tab View
- Drawer Page
- Drawer View
- Collection View
- Popup
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using Microsoft.Maui.Dispatching;
namespace MvvmDemo.Modules.DispatcherDemo;
public class DispatcherDemoViewModel : DXObservableObject {
public ObservableCollection<string> Items { get; }
public AsyncRelayCommand GenerateItemsCommand { get; }
IDispatcher Dispatcher { get; }
public DispatcherDemoViewModel(IDispatcher dispatcher) {
Dispatcher = dispatcher;
Items = new();
GenerateItemsCommand = new AsyncRelayCommand(Generate);
}
Task Generate() {
return Task.Factory.StartNew(GenerateCore);
}
void GenerateCore() {
Dispatcher.DispatchAsync(() => Items.Clear());
for (int i = 0; i <= 20; i++) {
string item = "Item " + i;
Dispatcher.DispatchAsync(() => Items.Add(item));
Thread.Sleep(TimeSpan.FromMilliseconds(100));
}
}
}
C#namespace MvvmDemo.Modules.PopupServiceDemo;
public class PopupServiceDemoViewModel : DXObservableObject {
public bool AllowScrim { get => allowScrim; set => SetProperty(ref allowScrim, value); }
public bool CloseOnScrimTap { get => closeOnScrimTap; set => SetProperty(ref closeOnScrimTap, value); }
public string? Result { get => result; private set => SetProperty(ref result, value); }
public ObservableCollection<AlertCustomContentItem> AlertCustomContentItems { get; }
public AsyncRelayCommand ShowAlertWithIconCommand { get; }
public AsyncRelayCommand ShowAlertWithoutIconCommand { get; }
public AsyncRelayCommand ShowAlertWithCustomContentCommand { get; }
public AsyncRelayCommand ShowActionSheetWithIconsCommand { get; }
public AsyncRelayCommand ShowActionSheetWithoutIconsCommand { get; }
public AsyncRelayCommand ShowActionSheetWithoutCancelCommand { get; }
public AsyncRelayCommand ShowOptionSheetWithRadioButtonsCommand { get; }
public AsyncRelayCommand ShowOptionSheetWithCheckBoxesCommand { get; }
public AsyncRelayCommand ShowCustomPopupCommand { get; }
public PopupServiceDemoViewModel(IDXPopupService popupService) {
this.popupService = popupService;
ShowAlertWithIconCommand = new(ShowAlertWithIcon);
ShowAlertWithoutIconCommand = new(ShowAlertWithoutIcon);
ShowAlertWithCustomContentCommand = new(ShowAlertWithCustomContent);
ShowActionSheetWithIconsCommand = new(ShowActionSheetWithIcons);
ShowActionSheetWithoutIconsCommand = new(ShowActionSheetWithoutIcons);
ShowActionSheetWithoutCancelCommand = new(ShowActionSheetWithoutCancel);
ShowOptionSheetWithRadioButtonsCommand = new(ShowOptionSheetWithRadioButtons);
ShowOptionSheetWithCheckBoxesCommand = new(ShowOptionSheetWithCheckBoxes);
ShowCustomPopupCommand = new(ShowCustomPopup);
AlertCustomContentItems = new() {
new AlertCustomContentItem("Cindy_Haneline@example.com", "photo1"),
new AlertCustomContentItem("Bruce_Cambell@example.com", "photo2")
};
}
Task ShowAlertWithIcon() {
return ShowAlertCore(true, null);
}
Task ShowAlertWithoutIcon() {
return ShowAlertCore(false, null);
}
Task ShowAlertWithCustomContent() {
var message = "This action will reset your app preferences back to their default settings. The following accounts will also be signed out:";
return ShowAlertCore(false, "DXPopupAlert.CustomContent.Style", message);
}
async Task ShowAlertCore(bool showIcon, string? styleKey, string? message = null) {
var title = "Reset Settings?";
message ??= "This action will reset your app preferences back to their default settings.";
var icon = showIcon ? "restart" : null;
var res = await popupService.ShowAlert(
settings: new DXPopupSettings() {
Title = title,
Message = message,
TitleIcon = icon,
VerticalAlignment = DXPopupVerticalAlignment.Center,
AllowScrim = AllowScrim,
CloseOnScrimTap = CloseOnScrimTap,
StyleKey = styleKey,
BindingContext = this
},
ok: "Accept",
cancel: "Cancel");
Result = res ? "True" : "False";
}
Task ShowActionSheetWithIcons() {
return ShowActionSheetCore(true);
}
Task ShowActionSheetWithoutIcons() {
return ShowActionSheetCore(false);
}
Task ShowActionSheetWithoutCancel() {
return ShowActionSheetCore(false, false);
}
async Task ShowActionSheetCore(bool showIcons, bool showCancel = true) {
var res = await popupService.ShowActionSheet(
settings: new DXPopupSettings() {
Title = "Actions",
VerticalAlignment = DXPopupVerticalAlignment.Center,
AllowScrim = AllowScrim,
CloseOnScrimTap = CloseOnScrimTap,
},
cancel:
showCancel ? "Cancel" : null,
actionButtons: new[] {
new DXPopupActionInfo(text: "Copy", icon: showIcons ? "copy" : null),
new DXPopupActionInfo(text: "Download", icon: showIcons ? "download" : null),
new DXPopupActionInfo(text: "Share", icon: showIcons ? "share" : null)
}
);
Result = res ?? "NULL";
}
async Task ShowOptionSheetWithRadioButtons() {
var res = await popupService.ShowRadioOptionSheet(
settings: new DXPopupSettings() {
Title = "Phone Ringtone",
AllowScrim = AllowScrim,
CloseOnScrimTap = CloseOnScrimTap,
},
ok: "Accept",
cancel: "Cancel",
optionButtons: new[] {
new DXPopupOptionInfo(text: "Default"),
new DXPopupOptionInfo(text: "None"),
new DXPopupOptionInfo(text: "Andromeda", isChecked: true),
new DXPopupOptionInfo(text: "Aquila"),
new DXPopupOptionInfo(text: "Backroad"),
new DXPopupOptionInfo(text: "Bell phone"),
new DXPopupOptionInfo(text: "Callisto"),
new DXPopupOptionInfo(text: "Ganymede"),
new DXPopupOptionInfo(text: "Luna"),
new DXPopupOptionInfo(text: "Oberon"),
new DXPopupOptionInfo(text: "Phobos"),
new DXPopupOptionInfo(text: "Titania"),
new DXPopupOptionInfo(text: "Triton"),
new DXPopupOptionInfo(text: "Umbriel"),
}
);
Result = res ?? "NULL";
}
async Task ShowOptionSheetWithCheckBoxes() {
var res = await popupService.ShowCheckBoxOptionSheet(
settings: new DXPopupSettings() {
Title = "Label As",
AllowScrim = AllowScrim,
CloseOnScrimTap = CloseOnScrimTap,
},
ok: "Accept",
cancel: "Cancel",
optionButtons: new[] {
new DXPopupOptionInfo(text: "None"),
new DXPopupOptionInfo(text: "Forums"),
new DXPopupOptionInfo(text: "Social", isChecked: true),
new DXPopupOptionInfo(text: "Updates", isChecked: true),
new DXPopupOptionInfo(text: "Promotions"),
new DXPopupOptionInfo(text: "Spam"),
new DXPopupOptionInfo(text: "Work"),
}
);
if(res == null) {
Result = $"Result: NULL";
return;
}
Result = string.Join(", ", res);
}
async Task ShowCustomPopup() {
var vm = await popupService.ShowPopup<LoginPopupViewModel>(x => {
x.AllowScrim = AllowScrim;
x.CloseOnScrimTap = CloseOnScrimTap;
});
if (!vm.Result) {
Result = "NULL";
return;
}
Result = $"Login={vm.Login.Value}, Password={vm.Password.Value}";
}
readonly IDXPopupService popupService;
string? result = "NULL";
bool allowScrim = true;
bool closeOnScrimTap;
}
public class AlertCustomContentItem {
public string Text { get; }
public string Photo { get; }
public AlertCustomContentItem(string text, string photo) {
Text = text;
Photo = photo;
}
}
C#using MvvmDemo.Validation;
namespace MvvmDemo.Modules.PopupServiceDemo;
public class LoginPopupViewModel : DXObservableObject, IDXPopupViewModel {
public bool AllowScrim { get => allowScrim; set => SetProperty(ref allowScrim, value); }
public bool CloseOnScrimTap { get => closeOnScrimTap; set => SetProperty(ref closeOnScrimTap, value); }
public ValidatableObject<string?> Login { get; }
public ValidatableObject<string?> Password { get; }
public bool IsBusy { get => isBusy; private set => SetProperty(ref isBusy, value); }
public bool Result { get; private set; }
public AsyncRelayCommand LoginCommand { get; }
public LoginPopupViewModel(ILoginService loginService) {
this.loginService = loginService;
LoginCommand = new(DoLogin, CanLogin);
Login = new(
ValidationRules.IsNotNullOrWhiteSpace("A login is required."),
() => LoginCommand.NotifyCanExecuteChanged());
Password = new(
ValidationRules.IsNotNullOrWhiteSpace("A password is required."),
() => LoginCommand.NotifyCanExecuteChanged());
}
async Task DoLogin() {
Login.Validate();
Password.Validate();
if(!Login.IsValid || !Password.IsValid)
return;
await loginService.Login(Login.Value, Password.Value);
Result = true;
popup!.Close();
}
bool CanLogin() {
return Login.IsValid && Password.IsValid;
}
IDXPopup IDXPopupViewModel.Popup { set => popup = value; }
readonly ILoginService loginService;
IDXPopup? popup;
bool allowScrim;
bool closeOnScrimTap;
bool isBusy;
}
C#using System.Globalization;
namespace MvvmDemo.Modules.LocalizationDemo;
public class LocalizationDemoViewModel : DXObservableObject {
public LocalizableString SimpleString { get; }
public LocalizableString ParameterizedString { get; }
public string? Parameter { get => parameter; set => SetProperty(ref parameter, value, OnParameterChanged); }
public RelayCommand ChangeLanguageCommand { get; }
ILocalizer Localizer { get; }
public LocalizationDemoViewModel(ILocalizer localizer) {
this.parameter = "John";
Localizer = localizer;
SimpleString = new LocalizableString(StringId.SimpleString);
ParameterizedString = new LocalizableString(
StringId.ParameterizedString,
x => string.Format(x, Parameter));
ChangeLanguageCommand = new RelayCommand(ChangeLanguage);
}
void OnParameterChanged() {
ParameterizedString.Update();
}
void ChangeLanguage() {
var culture = CultureInfo.CurrentCulture == fr ? en : fr;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
Localizer.NotifyCultureChanged();
}
string? parameter;
static readonly CultureInfo en = new CultureInfo("en-us");
static readonly CultureInfo fr = new CultureInfo("fr-FR");
}
C#namespace MvvmDemo.Modules.PrintServiceDemo;
public class PrintServiceDemoViewModel : DXObservableObject {
public AsyncRelayCommand PrintCommand { get; }
IPrintService PrintService { get; }
public PrintServiceDemoViewModel(IPrintService printService) {
PrintService = printService;
PrintCommand = new AsyncRelayCommand(Print);
}
async Task Print() {
await PrintService.PrintAsync("BalanceSheet.pdf");
}
}
C#using System.Text;
namespace MvvmDemo.Modules.SaveFilePickerDemo;
public class SaveFilePickerDemoViewModel : DXObservableObject {
public AsyncRelayCommand SaveCommand { get; }
ISaveFilePicker SaveFilePicker { get; }
public SaveFilePickerDemoViewModel(ISaveFilePicker saveFilePicker) {
SaveFilePicker = saveFilePicker;
SaveCommand = new AsyncRelayCommand(Save);
}
async Task Save() {
var text = "Hello world!";
using(var s = new MemoryStream(Encoding.UTF8.GetBytes(text))) {
await SaveFilePicker.SaveAsync(s, "HelloWorld.txt", PredefinedFileType.Any);
}
}
}
C#using Microsoft.Maui.Storage;
namespace MvvmDemo.Modules.FilePickerDemo;
public class FilePickerDemoViewModel : DXObservableObject {
public string? PickedFile { get => pickedFile; private set => SetProperty(ref pickedFile, value); }
public AsyncRelayCommand OpenCommand { get; }
IFilePicker FilePicker { get; }
public FilePickerDemoViewModel(IFilePicker filePicker) {
FilePicker = filePicker;
OpenCommand = new AsyncRelayCommand(Open);
}
async Task Open() {
var res = await FilePicker.PickAsync();
PickedFile = res?.FileName;
}
string? pickedFile;
}
C#using Microsoft.Maui.Storage;
namespace MvvmDemo.Modules.FileSystemDemo;
public class FileSystemDemoViewModel : DXObservableObject {
public string? AppDataDirectory { get; private set; }
public string? CacheDirectory { get; private set; }
IFileSystem FileSystem { get; }
public FileSystemDemoViewModel(IFileSystem fileSystem) {
FileSystem = fileSystem;
AppDataDirectory = FileSystem.AppDataDirectory;
CacheDirectory = FileSystem.CacheDirectory;
}
}
C#using DevExpress.Maui.Core;
namespace MvvmDemo.Modules.UIServiceDemo;
public class UIServiceDemoViewModel : DXObservableObject, IUIServiceClient {
public List<string> Items { get; }
public RelayCommand ScrollToStartCommand { get; }
public RelayCommand ScrollToEndCommand { get; }
IUIServiceContainer ServiceContainer { get; } = new UIServiceContainer();
IUIServiceContainer IUIServiceClient.ServiceContainer { get => ServiceContainer; }
public UIServiceDemoViewModel() {
var items = new List<string>();
for (int i = 0; i <= 20; i++) {
items.Add($"Item {i}");
}
Items = items;
ScrollToStartCommand = new RelayCommand(ScrollToStart);
ScrollToEndCommand = new RelayCommand(ScrollToEnd);
}
void ScrollToStart() {
var collectionView = ServiceContainer.GetRequiredService<IUIObjectService>();
collectionView.Object.ScrollTo(0, DXScrollToPosition.Start);
}
void ScrollToEnd() {
var collectionView = ServiceContainer.GetRequiredService<ICollectionViewUIService>();
collectionView.ScrollToEnd();
}
}
XAML<ContentPage
x:Class="MvvmDemo.Modules.HomeModule.HomePage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MvvmDemo.Modules.HomeModule;assembly=MvvmDemo"
xmlns:common="clr-namespace:MvvmDemo.Common;assembly=MvvmDemo"
xmlns:dx="http://schemas.devexpress.com/maui"
xmlns:t="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
BindingContext="{dx:Ioc Type={x:Type local:HomeViewModel}}"
Title="{common:ModuleTitle}">
<Grid>
<dx:DXCollectionView
x:Name="collectionView"
Margin="16"
ItemsSource="{Binding Demos}"
UseRippleEffect="False"
ItemSpacing="8">
<dx:DXCollectionView.ItemTemplate>
<DataTemplate>
<dx:DXButton
Padding="16,14"
ButtonType="ToolButton"
UseRippleEffect="True"
CornerRadius="8"
HorizontalContentAlignment="Fill"
VerticalContentAlignment="Center"
BackgroundColor="{dx:ThemeColor Key=SurfaceContainer}"
Command="{Binding Path=BindingContext.ShowDemoCommand, Source={x:Reference Name=collectionView}}"
CommandParameter="{Binding}"
Icon="arrow_forward"
Content="{Binding Title}"
IconPlacement="Right"
FontSize="16"
FontAttributes="None"
TextColor="{dx:ThemeColor Key=OnSurface}"
IconColor="{dx:ThemeColor Key=Outline}" />
</DataTemplate>
</dx:DXCollectionView.ItemTemplate>
</dx:DXCollectionView>
</Grid>
</ContentPage>