使用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:Class="ZL.Shudu.Views.FinishGameListPage" 
             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" 
                            Style="{DynamicResource ListItemTextStyle}" 
                            FontSize="16" />
                        <Label Text="{Binding PlayDate}" 
                            LineBreakMode="NoWrap"
                            Style="{DynamicResource ListItemDetailTextStyle}"
                            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:Class="ZL.Shudu.Views.FinishGameDetailPage"
             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

推荐这些技术文章:

使用Xamarin开发移动应用示例——数独游戏(六)使用数据库

项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu 。代码随项目进度更新。
现在我们希望为应用增加更多的功能,比如记录每个完成的游戏,可以让用户自己添加新的数独游戏等等,这些功能需要数据库的支持。我们使用Sqlite数据库保存游戏的数据。Sqlite是基于文件的单机关系型数据库,使用起来非常方便,首先安装程序包sqlite-net-pcl,可以在V...

使用Xamarin开发移动应用示例——数独游戏(二)创建游戏界面

在本系列第一部分,我们创建了程序框架,现在我们创建游戏的界面,项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu 。代码随项目进度更新。
首先在Views目录下添加一个内容页面,名称为Game.xaml:

然后,在AppShell.xaml中增加这个页面导航:
<TabBar>
<ShellContent...

使用Xamarin开发移动应用示例——数独游戏(七)添加新游戏

项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu 。代码随项目进度更新。
现在我们增加添加新游戏的功能,创建一个页面,编辑初始局面,并保存到数据库。
我们首先了解一下Xamarin中页面如何跳转。首先,需要为跳转的页面增加路由,这需要在AppShell中增加下面的代码:
public AppShell()
{
...

04 . Vue组件注册,组件间数据交互,调试工具及组件插槽介绍及使用

vue组件

组件(Component)是 Vue.js 最强大的功能之一。
组件可以扩展 HTML 元素,封装可重用的代码。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:

目标
/*
知道组件化开发思想
知道组件的注册方式
说出组件间的数据交互方式
说出组件插槽的用法
说出Vue调试工具的用法
基...

.Net Core3.0 WebApi 项目框架搭建 十七:使用NewLife.Redis替换掉原来的Redis

 
 .Net Core3.0 WebApi 项目框架搭建:目录
 
介绍

NewLife.Redis主要作者及经验介绍来源:大石头
源码: https://github.com/NewLifeX/NewLife.Redis
Nuget:NewLife.Redis
NewLife.Redis是一个Redis客户端组件,以高性能处理大数据实时计算为目标。
Redis...

让我手把手教你写一个强大、方便使用的 IOC 容器

一、介绍
    1、介绍
        最近无聊,也没什么事做,没事做总是要给自己找点事情做吧,毕竟人的生活在与折腾。于是,决定自己手动写一个 IOC 的框架。我们知道在 NetCore 的版本里面已经内置了 IOC 容器,它就是 ServiceCollection,一般情况下,该容器还是够用的,但是有时候还会有力不从心的时候,比如:我想要实现属性注入或者方法注入,NetCore 内置的框架...

记录一个简单的springboot+vue的项目流程

效果如图
1.前言
根据需求,搭建一个springboot项目,分为登录注册界面,用户管理,角色管理模块
然后因为前后端分离,所以我们使用jwt作为我们用户身份凭证。
2.后端接口开发
2.1环境配置
2.11 创建springboot

2.12 导入相应的依赖+配置tkmapper插件
<!--spring-web-->
<dependenc...

06 . Vue路由简介,原理,实现及嵌套路由,动态路由,编程式导航

路由概念

路由的本质就是一种对应关系,比如说我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源。
那么url地址和真实的资源之间就有一种对应的关系,就是路由。

路由分为前端路由和后端路由
/*
1).后端路由是由服务器端进行实现,并完成资源的分发.
概念: 根据不同的URL请求,返回不同的内容
本质: URL请求...

你真的熟悉ASP.NET MVC的整个生命周期吗?

一、介绍
    我们做开发的,尤其是做微软技术栈的,有一个方向是跳不过去的,那就是MVC开发。我相信大家,做ASP.NET MVC 开发有的有很长时间,当然,也有刚进入这个行业的。无论如何,如果有人问你,你知道ASP.NET MVC的生命周期吗?你知道它的来世今生吗?你知道它和 ASP.NET WEBFORM 有什么区别吗?估计,这些问题,有很多人会答不上来,或者说不清楚。今天,我就把我的理解...

第十三周JSP作业

1.第十二周上机作业(邮件功能)的控制层代码改用为servlet实现。

package com.entity;

import java.util.Date;

public class Email {
private Integer id;
private String fa;
private String shou;
private String title...

文章标题:使用Xamarin开发移动应用示例——数独游戏(八)使用MVVM实现完成游戏列表页面
文章链接:https://www.dianjilingqu.com/51601.html
本文章来源于网络,版权归原作者所有,如果本站文章侵犯了您的权益,请联系我们删除,联系邮箱:saisai#email.cn,感谢支持理解。
THE END
< <上一篇
下一篇>>