.NET MAUI

Microsoft純正のクロスプラットフォーム開発フレームワーク。単一コードベースでWindows、macOS、iOS、Android対応アプリを開発可能。Xamarin.Formsの後継として位置付けられ、ネイティブパフォーマンスを実現。

C#フレームワークデスクトップクロスプラットフォームXAMLMicrosoftネイティブ

GitHub概要

dotnet/maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.

ホームページ:https://dot.net/maui
スター22,795
ウォッチ632
フォーク1,858
作成日:2020年5月8日
言語:C#
ライセンス:MIT License

トピックス

androiddesktopdotnethacktoberfestiosmaccatalystmauimicrosoftmobilemulti-platformuser-interfacewinuiwinui3

スター履歴

dotnet/maui Star History
データ取得日時: 2025/7/17 10:32

フレームワーク

.NET MAUI (Multi-platform App UI)

概要

.NET MAUI(Multi-platform App UI)は、Microsoftが開発したクロスプラットフォームUIフレームワークです。単一のC#コードベースでWindows、macOS、Android、iOS向けのネイティブアプリケーションを構築できます。

詳細

.NET MAUI(マウイ)は2022年にリリースされた、Xamarin.Formsの進化版として位置づけられるクロスプラットフォーム開発フレームワークです。C#とXAMLを使用して、Windows、macOS、Android、iOS向けのネイティブアプリケーションを単一のコードベースで開発できます。MAUIの特徴は、各プラットフォーム固有のUIコントロールを使用するため、ネイティブな見た目と動作を提供する点です。Windows App SDKとWinUIフレームワークと統合し、Windowsでは本格的なデスクトップアプリケーションの開発が可能です。macOSではMac Catalystを活用してiOSアプリをmacOSネイティブとして実行します。Blazor WebViewコンポーネントにより、Webテクノロジーとネイティブアプリの融合も実現できます。Hot Reload機能により開発効率が向上し、NuGetパッケージエコシステムとの完全統合により豊富なライブラリを活用できます。

メリット・デメリット

メリット

  • 単一コードベース: C#とXAMLで複数プラットフォーム対応
  • ネイティブパフォーマンス: 各OS固有のUIコントロールを使用
  • 豊富なエコシステム: .NETエコシステムとNuGetパッケージを活用
  • Hot Reload: リアルタイムでのUI変更とデバッグ
  • Microsoft公式サポート: 継続的な開発とエンタープライズサポート
  • Blazor統合: WebベースのUIコンポーネントも利用可能
  • Visual Studio統合: 優れた開発体験とツールサポート

デメリット

  • 学習コスト: XAML、C#、各プラットフォーム固有概念の習得が必要
  • アプリサイズ: .NETランタイムを含むため比較的大きい
  • プラットフォーム制約: 一部の高度なネイティブ機能は直接アクセス困難
  • 新しい技術: Xamarin.Formsからの移行が必要
  • Windows主体: Microsoft技術スタック中心の設計

主要リンク

書き方の例

Hello World アプリケーション

// MainPage.xaml.cs
namespace HelloMaui;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;
        CounterBtn.Text = $"Clicked {count} time";
        if (count == 1)
            CounterBtn.Text += "s";

        SemanticScreenReader.Announce(CounterBtn.Text);
    }

    int count = 0;
}

XAML UIレイアウト

<!-- MainPage.xaml -->
<ContentPage x:Class="HelloMaui.MainPage"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <ScrollView>
        <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center">
            <Image Source="dotnet_bot.png" HeightRequest="185" Aspect="AspectFit" />
            
            <Label x:Name="WelcomeLabel" Text="Hello, World!" FontSize="32" HorizontalOptions="Center" />
            
            <Label Text="Welcome to .NET Multi-platform App UI" FontSize="18" HorizontalOptions="Center" />
            
            <Button x:Name="CounterBtn" Text="Click me" FontSize="18" Clicked="OnCounterClicked" />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

データバインディング

// ViewModelの例
using System.ComponentModel;
using System.Runtime.CompilerServices;

public class MainPageViewModel : INotifyPropertyChanged
{
    private string _name = "World";
    private int _clickCount = 0;

    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }

    public int ClickCount
    {
        get => _clickCount;
        set => SetProperty(ref _clickCount, value);
    }

    public string Greeting => $"Hello, {Name}!";

    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T backingStore, T value,
        [CallerMemberName] string propertyName = "",
        Action onChanged = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingStore, value))
            return false;

        backingStore = value;
        onChanged?.Invoke();
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

プラットフォーム固有コード

// Platforms/Windows/WindowsSpecific.cs
#if WINDOWS
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace HelloMaui.Platforms.Windows
{
    public static class WindowsSpecific
    {
        public static void ConfigureWindow(Window window)
        {
            // Windows固有の設定
            window.Title = "My MAUI App";
            window.ExtendsContentIntoTitleBar = true;
        }
    }
}
#endif

データベースアクセス(SQLite)

// Models/Note.cs
using SQLite;

public class Note
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }
    
    public string Text { get; set; }
    public DateTime Date { get; set; }
}

// Services/NoteDatabase.cs
public class NoteDatabase
{
    SQLiteAsyncConnection Database;

    public NoteDatabase()
    {
    }

    async Task Init()
    {
        if (Database is not null)
            return;

        Database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
        var result = await Database.CreateTableAsync<Note>();
    }

    public async Task<List<Note>> GetNotesAsync()
    {
        await Init();
        return await Database.Table<Note>().ToListAsync();
    }

    public async Task<int> SaveNoteAsync(Note note)
    {
        await Init();
        if (note.Id != 0)
            return await Database.UpdateAsync(note);
        else
            return await Database.InsertAsync(note);
    }
}

HTTP通信とAPI呼び出し

// Services/ApiService.cs
using System.Text.Json;

public class ApiService
{
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;

    public ApiService()
    {
        _httpClient = new HttpClient();
        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
    }

    public async Task<List<Todo>> GetTodosAsync()
    {
        try
        {
            var response = await _httpClient.GetAsync("https://jsonplaceholder.typicode.com/todos");
            response.EnsureSuccessStatusCode();
            
            var json = await response.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize<List<Todo>>(json, _jsonOptions);
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Error: {ex.Message}");
            return new List<Todo>();
        }
    }
}

// ViewModelでの使用例
public class TodosViewModel : ObservableObject
{
    private readonly ApiService _apiService;
    
    public ObservableCollection<Todo> Todos { get; } = new();

    public TodosViewModel(ApiService apiService)
    {
        _apiService = apiService;
        LoadTodosCommand = new AsyncRelayCommand(LoadTodos);
    }

    public IAsyncRelayCommand LoadTodosCommand { get; }

    private async Task LoadTodos()
    {
        var todos = await _apiService.GetTodosAsync();
        Todos.Clear();
        foreach (var todo in todos)
        {
            Todos.Add(todo);
        }
    }
}