天天看點

将Abp移植進.NET MAUI項目(三):建構UI層

 很開心,終于到了建立頁面的時候了!

我們需要兩個頁面

  • MainPage 首頁面
  • MusicItemPage 條目編輯頁面

編寫首頁面

 建立一個MainPageViewModel.cs,作為MainPage的ViewModel層

public class MainPageViewModel : ViewModelBase
    {
        private readonly IRepository<Song, long> songRepository;

        public MainPageViewModel(IRepository<Song, long> songRepository)
        {
            this.RefreshCommand=new Command(Refresh, (o) => true);
            this.DeleteCommand=new Command(Delete, (o) => true);
            this.songRepository=songRepository;

        }
        private void Delete(object obj)
        {
            songRepository.Delete(obj as Song);
        }
        private async void Refresh(object obj)
        {
            this.IsRefreshing=true;
            var getSongs = this.songRepository.GetAllListAsync();
            await getSongs.ContinueWith(r => IsRefreshing=false);
            var songs = await getSongs;
            this.Songs=new ObservableCollection<Song>(songs);
        }

        private ObservableCollection<Song> songs;

        public ObservableCollection<Song> Songs
        {
            get { return songs; }
            set
            {
                songs = value;
                RaisePropertyChanged();
            }
        }

        private Song currentSong;

        public Song CurrentSong
        {
            get { return currentSong; }
            set
            {
                currentSong = value;
                RaisePropertyChanged();
            }
        }

        private bool _isRefreshing;

        public bool IsRefreshing
        {
            get { return _isRefreshing; }
            set
            {
                _isRefreshing = value;
                RaisePropertyChanged();

            }
        }
        public Command RefreshCommand { get; set; }
        public Command DeleteCommand { get; private set; }
    }
           
将Abp移植進.NET MAUI項目(三):建構UI層

建立一個MainPage頁面

将Abp移植進.NET MAUI項目(三):建構UI層
将Abp移植進.NET MAUI項目(三):建構UI層

編寫Xaml為:

注意這個頁面将繼承MauiBoilerplate.ContentPageBase

<?xml version="1.0" encoding="utf-8" ?>
<mato:ContentPageBase xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:mato="clr-namespace:MauiBoilerplate;assembly=MauiBoilerplate.Core"
             x:Class="MauiBoilerplate.MainPage">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="155"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>

        <Label Text="My Music" FontSize="65"></Label>
        <ListView 
                Grid.Row="1"
                ItemsSource="{Binding Songs,Mode=TwoWay}"
                x:Name="MainListView"
                RowHeight="74" 
                IsPullToRefreshEnabled="True"
                IsRefreshing="{Binding IsRefreshing}"
                RefreshCommand="{Binding RefreshCommand}"
                SelectedItem="{Binding CurrentSong,Mode=TwoWay}">
            <ListView.Header>
                <Grid HeightRequest="96">
                    <Grid.RowDefinitions>
                        <RowDefinition></RowDefinition>
                        <RowDefinition></RowDefinition>
                    </Grid.RowDefinitions>


                    <Button Clicked="AddButton_Clicked"
                            CornerRadius="100"
                            Text=""
                            HeightRequest="44"
                            WidthRequest="200"
                            FontFamily="FontAwesome"
                                ></Button>


                    <StackLayout VerticalOptions="End"
                                 Margin="0,0,0,8"
                                 Grid.Row="1"
                                 HorizontalOptions="Center"
                                 Orientation="Horizontal">
                        <Label HorizontalTextAlignment="Center"
                            FontSize="Small" 
                            Text="{Binding Songs.Count}"></Label>
                        <Label  HorizontalTextAlignment="Center"
                            FontSize="Small" 
                            Text="首歌"></Label>

                    </StackLayout>
                </Grid>
            </ListView.Header>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid x:Name="ModeControlLayout" 
                              VerticalOptions="CenterAndExpand">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>


                            <StackLayout Grid.Column="0" 
                                             HorizontalOptions="Center" 
                                             VerticalOptions="CenterAndExpand">
                                <Label 
                                    Text="{Binding MusicTitle}"                                    
                                    HorizontalOptions="FillAndExpand" 
                                    HorizontalTextAlignment="Center" 
                                    FontSize="Body" 
                                    />
                                <Label
                                    Text="{Binding Artist}" 
                                    HorizontalOptions="FillAndExpand" 
                                    HorizontalTextAlignment="Center" 
                                    FontSize="Body" 
                                    />
                            </StackLayout>
                            <Button 
                                x:Name="MoreButton"
                                HeightRequest="44" 
                                WidthRequest="44" 
                                Margin="10"
                                Text=""
                                Clicked="SongMoreButton_OnClicked"
                                FontFamily="FontAwesome"
                                Grid.Column="1" 
                                CornerRadius="100"
                                HorizontalOptions="Center" />

                        </Grid>

                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</mato:ContentPageBase>
           
将Abp移植進.NET MAUI項目(三):建構UI層

 編寫CodeBehind為:

注意将它繼承ITransientDependency接口

這個頁面之前提到過,已經通過IocManager.Resolve(typeof(MainPage))解析出執行個體并指派給App.MainPage了。

public partial class MainPage : ContentPageBase, ITransientDependency
{
    private readonly MainPageViewModel mainPageViewModel;
    private readonly MusicItemPageViewModel musicItemPageViewModel;
    private readonly MusicItemPage musicItemPage;

    public MainPage(MainPageViewModel mainPageViewModel, MusicItemPageViewModel musicItemPageViewModel, MusicItemPage musicItemPage)
    {
        InitializeComponent();
        this.mainPageViewModel=mainPageViewModel;
        this.musicItemPageViewModel=musicItemPageViewModel;
        this.musicItemPage=musicItemPage;
        BindingContext=this.mainPageViewModel;
       
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        mainPageViewModel.RefreshCommand.Execute(null);

    }

    private async void SongMoreButton_OnClicked(object sender, EventArgs e)
    {
        var currentsong = (sender as BindableObject).BindingContext as Song;
        string action = await DisplayActionSheet(currentsong.MusicTitle, "取消", null, "修改", "删除");
        if (action=="修改")
        {
            musicItemPageViewModel.CurrentSong  = currentsong;
            await Navigation.PushModalAsync(musicItemPage);
        }
        else if (action=="删除")
        {
            mainPageViewModel.DeleteCommand.Execute(currentsong);
            mainPageViewModel.RefreshCommand.Execute(null);
        }
    }

    private async void AddButton_Clicked(object sender, EventArgs e)
    {
        musicItemPageViewModel.CurrentSong  = new Song();
        await Navigation.PushModalAsync(musicItemPage);
    }
}           
将Abp移植進.NET MAUI項目(三):建構UI層

此頁面将顯示一個清單,并在清單條目下可以彈出一個菜單

将Abp移植進.NET MAUI項目(三):建構UI層
将Abp移植進.NET MAUI項目(三):建構UI層

 編寫條目編輯頁面

 建立一個MusicItemPageViewModel.cs,作為MusicItemPage的ViewModel層

public class MusicItemPageViewModel : ViewModelBase
 {
        private readonly IIocResolver iocResolver;
        private readonly IRepository<Song, long> songRepository;

        public event EventHandler OnFinished;

        public MusicItemPageViewModel(
            IIocResolver iocResolver,
            IRepository<Song, long> songRepository)
        {
            this.CommitCommand=new Command(Commit, (o) => CurrentSong!=null);
            this.iocResolver=iocResolver;
            this.songRepository=songRepository;
            this.PropertyChanged+=MusicItemPageViewModel_PropertyChanged;
        }

        private void MusicItemPageViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName==nameof(CurrentSong))
            {
                CommitCommand.ChangeCanExecute();
            }
        }

        private void Commit(object obj)
        {
            songRepository.InsertOrUpdate(currentSong);       
        }

        private Song currentSong;

        public Song CurrentSong
        {
            get { return currentSong; }
            set
            {
                currentSong = value;
                RaisePropertyChanged();
            }
        }
  }           
将Abp移植進.NET MAUI項目(三):建構UI層

建立一個MusicItemPage 頁面

編寫Xaml為:

注意這個頁面将繼承MauiBoilerplate.ContentPageBase

<?xml version="1.0" encoding="utf-8" ?>
<mato:ContentPageBase xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:mato="clr-namespace:MauiBoilerplate;assembly=MauiBoilerplate.Core"
             x:Class="MauiBoilerplate.MusicItemPage">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="155"></RowDefinition>
        </Grid.RowDefinitions>
        <TableView Intent="Form">
            <TableRoot>
                <TableSection Title="基礎">
                    <EntryCell Label="标題"   Text="{Binding CurrentSong.MusicTitle, Mode=TwoWay}"/>
                    <EntryCell  Label="藝術家"  Text="{Binding CurrentSong.Artist, Mode=TwoWay}"/>
                    <EntryCell  Label="專輯"  Text="{Binding CurrentSong.Album, Mode=TwoWay}"/>

                </TableSection>
                <TableSection Title="其他">
                    <EntryCell  Label="時長"  Text="{Binding CurrentSong.Duration}"/>
                    <EntryCell  Label="釋出日期"  Text="{Binding CurrentSong.ReleaseDate}"/>
                </TableSection>

            </TableRoot>
        </TableView>
        <Button x:Name="CommitButton"
                Grid.Row="1"
                CornerRadius="100"
                HeightRequest="44"
                WidthRequest="200"
                Text=""
                Command="{Binding CommitCommand}"
                FontFamily="FontAwesome"             
                HorizontalOptions="Center" />
    </Grid>
</mato:ContentPageBase>
           
将Abp移植進.NET MAUI項目(三):建構UI層

 編寫CodeBehind為:

注意将它繼承ITransientDependency接口

public partial class MusicItemPage : ContentPageBase, ITransientDependency
{
    private readonly MusicItemPageViewModel musicItemPageViewModel;

    public MusicItemPage(MusicItemPageViewModel musicItemPageViewModel)
    {
        InitializeComponent();
        this.musicItemPageViewModel=musicItemPageViewModel;
        this.musicItemPageViewModel.OnValidateErrors+=MusicItemPageViewModel_OnValidateErrors;
        this.musicItemPageViewModel.OnFinished+=MusicItemPageViewModel_OnFinished;
        BindingContext=this.musicItemPageViewModel;
        Unloaded+=MusicItemPage_Unloaded;
    }

    private async void MusicItemPageViewModel_OnFinished(object sender, EventArgs e)
    {
       await this.Navigation.PopModalAsync();
    }

    private void MusicItemPage_Unloaded(object sender, EventArgs e)
    {
        musicItemPageViewModel.CurrentSong = null;
    }

    private async void MusicItemPageViewModel_OnValidateErrors(object sender, List<System.ComponentModel.DataAnnotations.ValidationResult> e)
    {
        var content = string.Join(',', e);
        await DisplayAlert("請注意", content, "好的");
    }
}           
将Abp移植進.NET MAUI項目(三):建構UI層

這個頁面提供歌曲條目新增和編輯的互動功能

将Abp移植進.NET MAUI項目(三):建構UI層
将Abp移植進.NET MAUI項目(三):建構UI層

[可選]使用Abp校驗資料功能

這個部分使用Abp的ValidationConfiguration功能校驗表單資料,以展示Abp功能的使用

首先在MusicItemPageViewModel 構造函數中添加對IValidationConfiguration對象的注入

将Abp移植進.NET MAUI項目(三):建構UI層
将Abp移植進.NET MAUI項目(三):建構UI層

​編輯

 添加OnValidateErrors事件,并且在Page中訂閱這個事件。此事件将在校驗未通過時觸發

MusicItemPageViewModel.cs中:

public event EventHandler<List<ValidationResult>> OnValidateErrors;           
将Abp移植進.NET MAUI項目(三):建構UI層

 MusicItemPage.xaml.cs中:

this.musicItemPageViewModel.OnValidateErrors+=MusicItemPageViewModel_OnValidateErrors;           
将Abp移植進.NET MAUI項目(三):建構UI層
private async void MusicItemPageViewModel_OnValidateErrors(object sender, List<System.ComponentModel.DataAnnotations.ValidationResult> e)
    {
        var content = string.Join(',', e);
        await DisplayAlert("請注意", content, "好的");
    }           
将Abp移植進.NET MAUI項目(三):建構UI層

編寫校驗邏輯代碼

MusicItemPageViewModel.cs中:

protected List<ValidationResult> GetValidationErrors(Song validatingObject)
        {
            List<ValidationResult> validationErrors = new List<ValidationResult>();

            foreach (var validatorType in _configuration.Validators)
            {
                using (var validator = iocResolver.ResolveAsDisposable<IMethodParameterValidator>(validatorType))
                {
                    var validationResults = validator.Object.Validate(validatingObject);
                    validationErrors.AddRange(validationResults);
                }

            }
            return validationErrors;
        }
           
将Abp移植進.NET MAUI項目(三):建構UI層

Commit送出方法,改造如下:

當GetValidationErrors傳回的校驗錯誤清單中有内容時,将OnValidateErrors事件Invoke

private void Commit(object obj)
        {
            var validateErrors = GetValidationErrors(this.CurrentSong);
            if (validateErrors.Count==0)
            {
                songRepository.InsertOrUpdate(currentSong);
                this.OnFinished?.Invoke(this, EventArgs.Empty);

            }
            else
            {
                OnValidateErrors?.Invoke(this, validateErrors);
            }
        }           
将Abp移植進.NET MAUI項目(三):建構UI層

接下來在實體中定義校驗規則,校驗器将按照這些規則傳回校驗結果

public class Song : FullAuditedEntity<long>, IValidatableObject
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public override long Id { get; set; }

        [Required]
        [StringLength(6, ErrorMessage = "歌曲名稱要在6個字以内")]
        public string MusicTitle { get; set; }

        [Required]
        [StringLength(10, ErrorMessage = "歌曲名稱要在10個字以内")]
        public string Artist { get; set; }

        [Required]
        [StringLength(10, ErrorMessage = "歌曲名稱要在10個字以内")]
        public string Album { get; set; }

        public TimeSpan Duration { get; set; }
        public DateTime ReleaseDate { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (ReleaseDate != default && ReleaseDate>DateTime.Now)
            {
                yield return new ValidationResult("ReleaseDate不能大于當天",
                                  new[] { nameof(ReleaseDate) });
            }

        }
    }
           
将Abp移植進.NET MAUI項目(三):建構UI層

運作,建立條目。當我們如下填寫的時候,将會彈出提示框

将Abp移植進.NET MAUI項目(三):建構UI層
将Abp移植進.NET MAUI項目(三):建構UI層

iOS平台也測試通過 

将Abp移植進.NET MAUI項目(三):建構UI層

至此我們完成了所有的工作。

結束語

Abp是一個很好用的.Net開發架構,Abp庫幫助我們抽象了整個項目以及更多的設計模式應用,其名稱Asp Boilerplate,雖然有一個Asp在其中,但其功能不僅僅可以建構AspNet Core應用,

經過我們的探索用Abp建構了跨平台應用,同樣它還可以用于Xamarin,Wpf甚至是WinForms這些基于桌面的應用。

歡迎參與讨論和轉發。

項目位址

jevonsflash/maui-abp-sample (github.com)

繼續閱讀