はじめに
C#のGUIフレームワークは主にXAMLでUIを記述しロジックをC#でを生業としているMVVMアーキテクチャで構成されています。
しかし、多くのモバイルフレームワークではSwiftUIやFlutterなどでプログラミング言語からUIを作り出す宣言的UIというものが流行っているようです。
.NET MAUIでもCommunity Toolkitを使えばC#で宣言的UIで画面構成をすることが可能になりました。
今回は宣言的UIってどうなんだと思っていたのでちょっと試してみました。
注意
一応言っておきますが、CommunityToolkitを使わなくてもC#でUI画面を作成することはできます。
ただC#は画面UIを作るために作られた言語ではないため、XAMLに比べて管理も難しく、無駄な記述を繰り返し書かないといけないだったり、数多くの不満点があります。
それを解消してできるだけ簡単にC#で画面作成をするための道具がCommunityToolkit.Maui.Markupになります。
CommunityToolkit.Maui.MarkupをNuGet
CommunityToolkit.Maui.MarkupをNuGetします。
「ツール」→「NuGetパッケージマネージャー」→「ソリューションのNuGetパッケージの管理」から検索窓に「CommunityToolkit.Maui.Markup」と入力し検索、指定のパッケージをインストールします。
MauiProgram.cs builderに追加
MauiProgram.csファイルのbuilderの部分に以下のようにコミュニティツールキットを使いますよと指定する。
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkitMarkup()//←この一文を追加して、CommunityToolkitのMarkup機能を使うことを指定する。
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
試してみる
早速新しいC#のクラスを作成して作ってみました。
using CommunityToolkit.Maui.Markup;//←マークアップを書くもの
using Microsoft.Maui.Controls;//←コンポーネント
using static CommunityToolkit.Maui.Markup.GridRowsColumns;//GridRowやCollumns用名前空間
namespace CsharpMarkup
{
public class MainPageMarkup:ContentPage//←ContentPageを継承
{
int count = 0;
public MainPageMarkup()//MainClassになります。
{
Content = new ScrollView//ContentPageには1つしか要素が入らないのでContentとしてます。
{
Content = new StackLayout//ScrollViewの中に一つだけStackLayoutが入るので、Content=
{
Children =//そのContentの中に複数要素が入るのでChildren
{
new Image()//←要素をnewして入れていく
.Source("dotnet_bot.png")//←プロパティ名="値"の形がプロパティ名(値)になっている。Sourceは文字列なので""で囲っている。
.Size(200,200),//サイズはHeight,Widthを個別に指定しなくてもSize(Width,Height)で記述できる
new Label()
.Text("Text")
.FontSize(32)
.CenterHorizontal(),//HorizontalOptions=Centerはこのようになる。StartやEndはどうやるのかよくわからなかった
new Label()
.Text ("HelloWorld")
.TextColor(Colors.BlueViolet)//←TextColorは()の中にColors.色の名前になっている
.CenterHorizontal(),
new Grid
{
RowDefinitions = Rows.Define( //Gridの行の分割
(Row.TextEntry, 36)),//←今回は1行だけ。TextEntryの部分は行に名前を付けているみたい
ColumnDefinitions = Columns.Define( //Gridの列の分割
(Column.Description, Star),//今回の列分割は3つStarは自動調整用の*印と同義
(Column.Input, Stars(2)),//Input列Stars(2)は*2で*の2倍の大きさだよという意味
(Column.Thard, 100)),//Thard列100は列幅の固定でwitdh=100という意味。
Margin=50,
Children =//new したGridに複数の要素が入るためCildrenとしている
{
new Label()
.Text("Code:")
.Row(Row.TextEntry).Column(Column.Description),//TextEntry行,Description列にこの要素が入りますという意味
new Entry
{
Keyboard = Keyboard.Numeric,//AndroidとかiOSでこのエントリーを触ったらキーボードがナンバーモードになります。的な
BackgroundColor = Colors.LightBlue,
}
.Row(Row.TextEntry).Column(Column.Input)//TextEntry行,Input列にこの要素が入りますという意味
.FontSize(15)
.Placeholder("Enter number")
.TextColor(Colors.Black)
.Height(44)
.Margin(5, 5)
.Bind(Entry.TextProperty, nameof(ViewModel.RegistrationCode))//←Bindingはこう書くようだがViewModelクラスにRegistrationCodeプロパティを作ってみたがうまくバインドされず、、、なんかやり方があるのかもしれない。
}
},
new HorizontalStackLayout
{
Children=
{
new Label()
.Text("BindingValue")
.Bind(Label.TextProperty, Slider.ValueProperty.ToString())//下のスライダーの値をバインドしてみたがうまくいかず、、、違う方法があるのかも
.Size(200,200)
.TextColor(Colors.Gold),
new Slider
{
Maximum= 100,//要素固有のプロパティは{}の中で記述する感じでした。
Minimum= 10,
Value=50,
}
.Size(200,200),
new VerticalStackLayout
{
Children=
{
new Label()
.Text("V1")
.FontSize(61)
.Assign(out Label countlabel),//x:NameがAssignになっているみたい。
new Button()
.Invoke(b =>b.Clicked +=(sender,e)=>//Invokeでイベントハンドラー(bはこのボタンですよと簡易に名前を付けた後b.Clickedでイベントハンドラーとしている)
{
count++;
countlabel.Text = count.ToString();//Assingnして名前を付けたものについて代入している。
})
}
}
}
},
}
}
};
}
enum Row { TextEntry }//行の名前 1行目(Grid.Row="0")がTextEntryという名前だよと示す。
enum Column { Description, Input,Thard }//列の名前それぞれ(Grid.Column="0","1","2")を表す。
}
}
一つのコントロールはnew コントロール名()で作り,で区切られ、プロパティは.プロパティ名(値)で追加されるようです。
また、固有のプロパティ名を持つコントロールについてはnew コントロール名{固有プロパティ=値}という形で記述されるみたいです。
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
</Grid>
上のような<コントロール名.〇〇>って書かれるものが{}の中に入ってくるイメージかな?
ContentとCildrenの違い
この違いは前の要素が複数の要素を保持できるかできないかの違いなのかなと思った。
ContentPageは一つのコントロールしか保持できないのでContent=ScrollViewとしていて、ScrollViewもまた、一つのコントロールしか保持できないので、Content=new StackLayoutとしている。
できあがった画面
さいごに
ちょっとテスト的にやってみましたがChild,ChildrenだらけのFlutterよりマシですけど宣言的UIってよくわからないですね。
元々C#とかプログラミング言語ってUIを構成するのは不得意だと思っているので、宣言的UIを使う意味って何のか分からないところがあります。
XAMLを覚えなくてもいいとは思いますが、多分この宣言的UIのルールを覚える方が大変だと思いました。
これをSwiftやFlutterの人は当たり前にやってるので逆にすごいと思いつつXMAL及びC#のGUIフレームワークのすごさにも感銘を受けています。
やはりその言語で得意なことをやらせる方が簡単だし、保守も容易なのかと思いました。
たぶん私はずっとXAMLとC#でMVVMでアプリを作ると思います。