本文介绍如何使用 .NET MAUI 开发一个电子木鱼应用。以实际的小应用开发为例,通过这个开发过程,介绍了其涉及的 .NET MAUI、Blazor、前端等相关知识点。文章涉及的应用已开源在 Github,大家可前往下载体验: https://github.com/sangyuxiaowu/MuYu

1. 背景

前面我们介绍了 《.NET MAUI 开发电子木鱼(上)》 ,接下来进行设置相关功能的开发。主要包含:敲击计数和自动敲击模式切换。

2. 相关知识点

本篇主要有如下相关知识点:

  1. .NET MAUI 生命周期
  2. .NET MAUI Blazor 在应用关闭前保存数据
  3. Blazor 中的计时器,System.Threading.Timer 的使用和启停

3. 开发过程

3.1 敲击计数

敲击计数这里主要记录当日敲击数和总敲击数,为了满足各路神仙的敲击,这里我们用 long,还是使用 Preferences 存储。

private long AllNum = 0;
private long TodayNum = 0;

在主页面 Index.razor 首次渲染时,我们读取存储的计数信息。

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        AllNum = Preferences.Default.Get("all_num", 0L);
        if (DateTime.Now.ToShortDateString() == Preferences.Default.Get("today",""))
        {
            TodayNum = Preferences.Default.Get("today_num", 0L);
        }
    }
}

需要注意的是: AllNumlong 型,Preferences.Default.Get 传的默认值是为了推断取得的存储内容的类型,所以这里要用 0L 需要标记为 long 型。否则首次启动无错误,当存储过数据后就会报错,数据类型无法转换。

接下来我们只需要实现一个计数的添加和存储功能即可:

void AddNum()
{
    // 当前日期
    var today = DateTime.Now.ToShortDateString();
    // 存储的日期
    string save_today = Preferences.Default.Get("today", "");

    // 天数未发生变更,防的就是半夜敲的
    if (save_today == today)
    {
        TodayNum++;
    }else{
        TodayNum = 1;
        Preferences.Default.Set("today", today);
    }

    AllNum++;

    Preferences.Default.Set("today_num", TodayNum);
    Preferences.Default.Set("all_num", AllNum);
}

3.2 计数问题优化

前面一节虽然实现了敲击计数,但是设计也存在一些不合理的地方:频繁的 Preferences 写入毕竟不好。毕竟这种应用,没必要实时保存了,最好是获取到应用的关闭事件,在应用关闭时保存即可。

但是因为 .NET MAUI Blazor 这种混合开发的模式,情况会稍微复杂一点,走的弯路这里就不再提及了。无论如何处理,我们首先还是需要了解一下 .NET MAUI 应用的生命周期。这里为了方便跨平台处理,我们可以选择跨平台生的命周期事件 Stopped:当窗口不再可见时,将引发此事件。此时我们就可以做一些关键数据存储了。

而对于敲击计数的功能,我们采用一个全局的静态类进行管理,方便在 Blazor 和 App 中访问。在 Data 目录下创建如下类 HitCounter.cs

public static class HitCounter
{
    private static long count;
    private static long todayCount;
    private static string today;

    static HitCounter()
    {
        count = Preferences.Default.Get("all_num", 0L);
        todayCount = 0;
        today = DateTime.Now.ToShortDateString();
        if (today == Preferences.Default.Get("today", ""))
        {
            todayCount = Preferences.Default.Get("today_num", 0L);
        }
    }

    /// <summary>
    /// 敲击后更新计数
    /// </summary>
    public static void Increment()
    {
        if (today != DateTime.Now.ToShortDateString())
        {
            today = DateTime.Now.ToShortDateString();
            todayCount = 0;
        }
        count++;
        todayCount++;
    }

    /// <summary>
    /// 保存数据到 Preferences
    /// </summary>
    public static void Save()
    {
        if (today != DateTime.Now.ToShortDateString())
        {
            today = DateTime.Now.ToShortDateString();
            todayCount = 0;
        }
        Preferences.Default.Set("today_num", todayCount);
        Preferences.Default.Set("today", today);
        Preferences.Default.Set("all_num", count);
    }

    /// <summary>
    /// 总计数
    /// </summary>
    public static long Count
    {
        get { return count; }
    }

    /// <summary>
    /// 当日计数
    /// </summary>
    public static long TodayCount
    {
        get { return todayCount; }
    }
}

要订阅 Window 生命周期事件,则需要在 App.xaml.cs 文件的 App 类中重写 CreateWindow 方法,并在其中添加订阅事件的实例:

protected override Window CreateWindow(IActivationState activationState)
{
    Window window = base.CreateWindow(activationState);

    window.Stopped += (s, e) =>
    {
        Data.HitCounter.Save();
    };

    return window;
}

Index.razor 则不要在重写 OnAfterRenderAsync ,直接赋值即可:

private long AllNum = Data.HitCounter.Count;
private long TodayNum = Data.HitCounter.TodayCount;

但是,此后敲击计数的更新并不是实时的在 Blazor 界面中显示了。在需要更新显示的时候,比如这里的动作是点开设置菜单显示在右上角,则只需要在打开菜单的事件中重新赋值:

void ShowMenu()
{
    ShowSetting = true;
    // 更新数据
    AllNum = Data.HitCounter.Count;
    TodayNum = Data.HitCounter.TodayCount;
}

请添加图片描述

3.3 自动敲击

自动敲击,积攒功德也是电子木鱼的一个重要功能。在 Blazor 中,我们可以使用 System.Threading.TimerJavaScript Interop 来实现计时器。这里选择使用 System.Threading.Timer 来实现。

首先我们需要了解一下 Timer(TimerCallback callback, object state, int dueTime, int period); 的参数:

参数说明
callback委托将会在period时间间隔内重复执行
state参数可以传入想在callback委托中处理的对象
dueTime标识多久后callback开始执行
period标识多久执行一次callback

Index.razor 我们定义并在 OnInitialized 时创建计时器,这里为了控制启动和停止,这里的 dueTimeperiod 设置为 Timeout.InfiniteTimeout.Infinite 是一个用于指定无限长等待时间的常数,这里就可以保证计时器不被触发 callback

private Timer _timer;//自动敲击计时器
private bool _isTimerRunning = false;//计时器启动开关

protected override void OnInitialized()
{
    _timer = new Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite);
    base.OnInitialized();
}

之后我们只需通过下面的方法更改计时参数即可切换计时器的启停状态了:

private void TimerSwitch()
{
    if (_isTimerRunning)
    {
        _timer.Change(Timeout.Infinite, Timeout.Infinite);
        _isTimerRunning = false;
    }
    else
    {
        _timer.Change(0, 1000);
        _isTimerRunning = true;
    }
}

4. 最后

至此,一个 .NET MAUI Blazor 的小应用已开发完毕,在 GitHub 上提供了预编译的 apk 和旁载的 windows_x64 应用, 感兴趣的同学可以前去尝试一下。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐