项目代码可以从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 。

使用Xamarin开发移动应用示例——数独游戏(八)使用MVVM实现完成游戏列表页面的更多相关文章

  1. 使用Xamarin开发移动应用示例——数独游戏(一)项目的创建与调试

    最近项目中需要移动客户端,由于团队基本上使用.Net产品线,所以决定使用Xmarin进行开发,这样技术路线统一,便于后期维护.官网上是这样介绍的" Xamarin 允许你使用 .NET 代码 ...

  2. 使用Xamarin开发移动应用示例——数独游戏(五)保存游戏进度

    项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu .代码随项目进度更新. 保存进度是移动应用的基本功能,在应用的使用过程中会有各种各样的可能导致使用中 ...

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

    项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu .代码随项目进度更新. 现在我们增加添加新游戏的功能,创建一个页面,编辑初始局面,并保存到数据库. ...

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

    在本系列第一部分,我们创建了程序框架,现在我们创建游戏的界面,项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu .代码随项目进度更新. 首先在View ...

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

    项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu .代码随项目进度更新. 现在我们希望为应用增加更多的功能,比如记录每个完成的游戏,可以让用户自己添加 ...

  6. 使用Xamarin开发移动应用示例——数独游戏(四)产生新游戏算法改进

    项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu .代码随项目进度更新. 前面我们使用一个数组保存预制的游戏,然后随机从中抽取一个游戏作为新游戏,如果 ...

  7. Xamarin 开发过的那些项目

    您可能已经看到类似的统计数据:智能手机用户在手机媒体上花费了89%的时间使用应用程序.或者听说Gartner预测到2017年移动应用程序下载将产生价值770亿美元的收入.很难不考虑这些数字.今天,每个 ...

  8. 老司机学新平台 - Xamarin开发环境及开发框架初探

    随着被微软收购,最近一年间,Xamarin的火爆程度与日俱增.免费.更好的VS2015集成.更好的模拟器,甚至,在windows上运行和调试iOS平台程序,让我这样接触了十几年.NET平台的老司机,即 ...

  9. 【Xamarin开发 Android 系列 4】 Android 基础知识

    原文:[Xamarin开发 Android 系列 4] Android 基础知识 什么是Android? Android一词的本义指“机器人”,同时也是Google于2007年11月5日宣布的基于Li ...

随机推荐

  1. oracle函数listagg使用

    作用 可以实现将多列记录聚合为一列记录,实现数据的压缩 语法结构 listagg(measure_expr,delimiter) within group ( order by order_by_cl ...

  2. MySQL数据库基础(1)数据库基础

    目录 一.数据库简介 二.mysql数据库 三.客户端连接mysql服务 四.Navicat for mysql 一.数据库简介 1.概念 (1)数据:如文字.图形.图像.声音以及学生的档案记录等,这 ...

  3. 【MySQL作业】多字段分组和 having 子句——美和易思分组查询应用习题

    点击打开所使用到的数据库>>> 1.按照商品类型和销售地区分组统计商品数量和平均单价,并按平均单价升序显示. -- 按照商品类型和销售地区分组统计商品数量和平均单价,并按平均单价升序 ...

  4. Oracle 创建数据表以及对数据表、字段、主外键、约束的操作

    选择主键的原则: 最少性 尽量选择使用单个键作为主键 稳定性 尽量选择数值更新少的列作为主键 1.创建数据表(CREATE TABLE) --创建数据表Student create table Stu ...

  5. Api接口测试总是似懂非懂,只因这个原理没搞清楚

    ​ 前言 掌握了http协议,就掌握了接口测试 笔者在网络上看过不少接口测试教程,一上来就开始讲怎么操作工具,而不告诉读者为什么要这么操作.读者可能照猫画虎成功了,也可能操作失败了但不知为何出错. 因 ...

  6. golang 算法题 : 两数相加

    package mainimport "fmt"type ListNode struct { Val int Next *ListNode}func main() { l1 := ...

  7. Centos下查看cpu核数

    1.概念物理CPU:实际Server中插槽上的CPU个数.物理cpu数量:可以数不重复的 physical id 有几个. 2.逻辑CPULinux用户对 /proc/cpuinfo 这个文件肯定不陌 ...

  8. java 方法 compareTo()的正确使用

    总结:(1)如果比较的是数字 则结果大于则为1 等于则为0 小于则为-1(2)如果比较的是字符[串] 则按照从左到右排序找对应不一样的字符第一个字符, 然后将字符装对应的ASCLL码数字,做减法运算, ...

  9. idea 开启 tomcat 热部署 的 具体流程 和 使用方式

    1前言 一直以来,使用idea做web开发修改html.jsp.js文件后,必须手动重新部署tomcat,最少都有等个6 -10 秒, 甚至有时候还提示找不到某个编译文件报错,重新编译整个项目,那得等 ...

  10. textarea换行符转换

    /** * @description textarea换行符转指定字符 * @param str:要放到textarea的字符串 * @param code:要转换成换行的字符,默认为',' */ e ...