使用Xamarin开发移动应用示例——数独游戏(八)使用MVVM实现完成游戏列表页面

博客 动态
0 262
羽尘
羽尘 2022-02-09 14:56:10
悬赏:0 积分 收藏

使用Xamarin开发移动应用示例——数独游戏(八)使用MVVM实现完成游戏列表页面

项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu 。代码随项目进度更新。

前面我们已经完成了游戏的大部分功能,玩家可以玩预制的数独游戏,也可以自己添加新的游戏。现在我们实现展示已完成游戏列表页面,显示用户已经完成的游戏列表,从这个列表可以进入详细的复盘页面。

前面的页面我们采用的是传统的事件驱动模型,在XAML文件中定义页面,在后台的cs文件中编写事件响应代码。采用这种模型是因为很多页面需要动态生成控件,然后动态改变这些控件的属性,事件驱动模型在这种场景下比较好理解。现在我们采用MVVM方式编写完成游戏列表页面。

MVVM是将页面绑定到视图模型,所有的操作和事件响应通过视图模型完成。视图模型中没有页面控件的定义,因此和页面是解耦的,可以独立进行测试。在视图模型中我们只关心数据,而不关心展示数据的控件。

首先,我们定义一个视图模型的基类,下一步在改造其它页面时,会用到这个基类:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Runtime.CompilerServices;using System.Text;namespace ZL.Shudu.ViewModels{    public class BaseViewModel : INotifyPropertyChanged    {        bool isBusy = false;        public bool IsBusy        {            get { return isBusy; }            set { SetProperty(ref isBusy, value); }        }        string title = string.Empty;        public string Title        {            get { return title; }            set { SetProperty(ref title, value); }        }        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;        }        #region INotifyPropertyChanged        public event PropertyChangedEventHandler PropertyChanged;        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")        {            var changed = PropertyChanged;            if (changed == null)                return;            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));        }        #endregion    }}

这个基类实现INotifyPropertyChanged接口,帮助实现属性在页面的双向绑定。然后,定义完成列表页面的视图模型:

using System;using System.Collections.Generic;using System.Collections.ObjectModel;using System.Diagnostics;using System.Text;using System.Threading.Tasks;using Xamarin.Forms;using ZL.Shudu.Models;using ZL.Shudu.Views;namespace ZL.Shudu.ViewModels{    public class FinishGameViewModel:BaseViewModel    {        private FinishGame _selectedItem;        public ObservableCollection<FinishGame> Items { get; }        public Command LoadItemsCommand { get; }        public Command<FinishGame> ItemTapped { get; }        public FinishGameViewModel()        {            Title = "完成游戏列表";            Items = new ObservableCollection<FinishGame>();            LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());            ItemTapped = new Command<FinishGame>(OnItemSelected);        }        async Task ExecuteLoadItemsCommand()        {            IsBusy = true;            try            {                Items.Clear();                var items = await App.Database.GetFinishGamesAsync();                foreach (var item in items)                {                    Items.Add(item);                }            }            catch (Exception ex)            {                Debug.WriteLine(ex);            }            finally            {                IsBusy = false;            }        }        public FinishGame SelectedItem        {            get => _selectedItem;            set            {                SetProperty(ref _selectedItem, value);                OnItemSelected(value);            }        }        public void OnAppearing()        {            IsBusy = true;            SelectedItem = null;        }        async void OnItemSelected(FinishGame item)        {            if (item == null)                return;            // This will push the ItemDetailPage onto the navigation stack            await Shell.Current.GoToAsync($"{nameof(FinishGameDetailPage)}?{nameof(FinishGameDetailPage.ItemId)}={item.Id}");        }    }}

主要功能有两个,一是从数据库中读取数据,二是响应选中数据事件并跳转到显示详细信息的页面。
接下来定义页面:

<?xml version="1.0" encoding="utf-8" ?><ContentPage xmlns="http://xamarin.com/schemas/2014/forms"             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:ZL.Shudu.ViewModels" xmlns:model="clr-namespace:ZL.Shudu.Models"             x:              Title="{Binding Title}">    <RefreshView x:DataType="local:FinishGameViewModel" Command="{Binding LoadItemsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">        <CollectionView x:Name="ItemsListView"                ItemsSource="{Binding Items}"                SelectionMode="None">            <CollectionView.ItemTemplate>                <DataTemplate>                    <StackLayout Padding="10" x:DataType="model:FinishGame">                        <Label Text="{Binding Id}"                             LineBreakMode="NoWrap"                                                          FontSize="16" />                        <Label Text="{Binding PlayDate}"                             LineBreakMode="NoWrap"                                                        FontSize="13" />                        <StackLayout.GestureRecognizers>                            <TapGestureRecognizer                                 NumberOfTapsRequired="1"                                Command="{Binding Source={RelativeSource AncestorType={x:Type local:FinishGameViewModel}}, Path=ItemTapped}"		                                CommandParameter="{Binding .}">                            </TapGestureRecognizer>                        </StackLayout.GestureRecognizers>                    </StackLayout>                </DataTemplate>            </CollectionView.ItemTemplate>        </CollectionView>    </RefreshView></ContentPage>

页面后台代码:

using System.Linq;using System.Text;using System.Threading.Tasks;using Xamarin.Forms;using Xamarin.Forms.Xaml;using ZL.Shudu.ViewModels;namespace ZL.Shudu.Views{    [XamlCompilation(XamlCompilationOptions.Compile)]    public partial class FinishGameListPage : ContentPage    {        FinishGameViewModel _viewModel;        public FinishGameListPage()        {            InitializeComponent();            BindingContext = _viewModel = new FinishGameViewModel();        }        protected override void OnAppearing()        {            base.OnAppearing();            _viewModel.OnAppearing();        }    }}

页面后台代码只负责将页面绑定到视图模型,不做其它工作。运行效果如下:

接下来完成游戏的复盘页面。这个页面调入已完成的游戏,可以使用向前和向后复盘游戏的过程,在实现上与游戏页面类似。
XAML页面代码如下:

<?xml version="1.0" encoding="utf-8" ?><ContentPage xmlns="http://xamarin.com/schemas/2014/forms"             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"             x:             Title="游戏记录">    <ContentPage.Content>        <StackLayout>            <Grid x:Name="myGrid" >                <Grid.ColumnDefinitions>                    <ColumnDefinition Width="*" />                    <ColumnDefinition Width="*" />                    <ColumnDefinition Width="*" />                    <ColumnDefinition Width="*" />                    <ColumnDefinition Width="*" />                    <ColumnDefinition Width="*" />                    <ColumnDefinition Width="*" />                    <ColumnDefinition Width="*" />                    <ColumnDefinition Width="*" />                </Grid.ColumnDefinitions>                <Grid.RowDefinitions>                    <RowDefinition Height="25"  />                    <RowDefinition Height="25" />                    <RowDefinition Height="25" />                    <RowDefinition Height="25" />                    <RowDefinition Height="25" />                    <RowDefinition Height="25" />                    <RowDefinition Height="25" />                    <RowDefinition Height="25" />                    <RowDefinition Height="25" />                    <RowDefinition Height="40" />                    <RowDefinition Height="40" />                </Grid.RowDefinitions>                <Button Text="开始" Grid.Row="9" Grid.Column="0"  Grid.ColumnSpan="2" Clicked="btn_Begin_Clicked"></Button>                <Button Text="结束" Grid.Row="9" Grid.Column="2"  Grid.ColumnSpan="2" Clicked="btn_End_Clicked"></Button>                <Button Text="向前" Grid.Row="9" Grid.Column="4"  Grid.ColumnSpan="2" Clicked="btn_Forward_Clicked"></Button>                <Button Text="向后" Grid.Row="9" Grid.Column="6"  Grid.ColumnSpan="2" Clicked="btn_Back_Clicked"></Button>                <Label x:Name="lbTime" Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="2" Text="" ></Label>                <Label x:Name="lbMessage" Grid.Row="10" Grid.Column="3" Grid.ColumnSpan="4" Text=""></Label>            </Grid>        </StackLayout>    </ContentPage.Content></ContentPage>

后台代码如下:

using System;using System.Collections.Generic;using Xamarin.Forms;using Xamarin.Forms.Xaml;using ZL.Shudu.Models;namespace ZL.Shudu.Views{    [QueryProperty(nameof(ItemId), nameof(ItemId))]    [XamlCompilation(XamlCompilationOptions.Compile)]    public partial class FinishGameDetailPage : ContentPage    {        private static int[,] chess = new int[9, 9];        private Button[,] buttons = new Button[9, 9];        int[,] fchess = new int[9, 9];        private List<string> steps = new List<string>();        private int currentsetp = 0;        private long currentDiffer = 0;        private int currentId;        public string ItemId        {            get            {                return currentId.ToString();            }            set            {                currentId = int.Parse(value);                if (currentId > 0)                {                    var game = App.Database.GetFinishGameAsync(currentId).Result;                    if (game != null) OpenHistory(game);                }            }        }        public string Title { get; set; } = "游戏记录";        public FinishGameDetailPage()        {            InitializeComponent();            SetLayout();        }        private void SetResult(bool beginonly = false)        {            currentsetp = beginonly ? 0 : steps.Count - 1;            for (var i = 0; i < 9; i++)            {                for (var j = 0; j < 9; j++)                {                    var btn = buttons[i, j];                    if (chess[i, j] > 0)                    {                        btn.Text = chess[i, j].ToString();                        btn.TextColor = Color.Red;                    }                    else                    {                        btn.Text = beginonly ? "" : fchess[i, j].ToString();                        btn.TextColor = Color.Blue;                    }                }            }        }        private void SetLayout()        {            for (var i = 0; i < 9; i++)            {                for (var j = 0; j < 9; j++)                {                    int m = i / 3;                    int n = j / 3;                    var btn = new Button();                    var c = new Color(0.9, 0.9, 0.9);                    var c1 = Color.Green;                    if ((m + n) % 2 == 0)                    {                        c = new Color(1, 1, 1);                    }                    btn.BackgroundColor = c;                    btn.Padding = 0;                    btn.Margin = 0;                    btn.FontSize = 20;                    myGrid.Children.Add(btn, i, j);                    buttons[i, j] = btn;                }            }        }        private void btn_Begin_Clicked(object sender, EventArgs e)        {            SetResult(true);        }        private void btn_End_Clicked(object sender, EventArgs e)        {            SetResult(false);        }        private void btn_Forward_Clicked(object sender, EventArgs e)        {            SetStep(false);            currentsetp++;        }        private void btn_Back_Clicked(object sender, EventArgs e)        {            SetStep(true);            currentsetp--;        }        private void SetStep(bool isback)        {            if (currentsetp < 0) currentsetp = 0;            if (currentsetp >= steps.Count) currentsetp = steps.Count - 1;            var laststep = steps[currentsetp];            var arr = laststep.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);            int x = int.Parse(arr[0]), y = int.Parse(arr[1]), num = int.Parse(arr[2]);            if (isback) buttons[x, y].Text = "";            else buttons[x, y].Text = fchess[x, y].ToString();        }        private void OpenHistory(FinishGame item)        {            try            {                if (item!=null)                {                                                         for (var i = 0; i < 9; i++)                    {                        for (var j = 0; j < 9; j++)                        {                            chess[i, j] = int.Parse(item.Sudoku.Substring(i * 9 + j, 1));                            fchess[i, j] = int.Parse(item.Result.Substring(i * 9 + j, 1));                        }                    }                    SetResult();                    if (!string.IsNullOrEmpty(item.Steps))                    {                        var steparr = item.Steps.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);                        steps.Clear();                        steps.AddRange(steparr);                    }                                       currentDiffer =item.TotalTime;                                        var diff =  currentDiffer / 10000 / 1000 / 60;                    lbTime.Text = diff + "分钟";                }            }            catch (Exception ex)            {                lbMessage.Text = ex.Message;            }        }    }}

到这里,数独游戏已经基本完成了,当然还有许多需要优化改进的地方,会陆续完成,可以关注https://github.com/zhenl/ZL.Shudu 。

本文来自博客园,作者:寻找无名的特质,转载请注明原文链接:https://www.cnblogs.com/zhenl/p/15851851.html

posted @ 2022-02-09 14:35 寻找无名的特质 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    羽尘

    羽尘 (王者 段位)

    2335 积分 (2)粉丝 (11)源码

     

    温馨提示

    亦奇源码

    最新会员