2022年10月9日 星期日

使用 CommunityToolkit 提供的 StatusBarBehavior 來進行狀態列的顏色變更

使用 CommunityToolkit 提供的 StatusBarBehavior 來進行狀態列的顏色變更

這幾天看到這篇 Announcing the .NET MAUI Community Toolkit v1.3 文章,其中看到了 StatusBarBehavior 這個行為可以進行底下的操作

StatusBarColor: allows you to specify the color of the status bar background.

StatusBarStyle: allows you to control if the content (text and icons) on the status bar are light, dark or the system default.

這樣的功能太夢幻了,因為有了他,將不再需要進行實作出原生的程式碼來進行狀態列的背景顏色修正

可是,當我開始使用 Prism.maui 專案範本建立一個專案,根據這篇文章 StatusBarBehavior 的說明,修改專案內的 MainPage.xaml 檔案如下所示

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:localBehaviors="clr-namespace:mauiStatusBar.Behaviors"
             Title="{Binding Title}"
             x:Class="mauiStatusBar.Views.MainPage">

    <ContentPage.Behaviors>
        <toolkit:StatusBarBehavior StatusBarColor="Fuchsia" StatusBarStyle="LightContent" />
    </ContentPage.Behaviors>

    <ScrollView>
    <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

      <Image Source="prism.png"
             SemanticProperties.Description="Cute dot net bot waving hi to you!"
             HeightRequest="150"
             HorizontalOptions="Center" />

      <Label Text="Hello, World!"
             SemanticProperties.HeadingLevel="Level1"
             FontSize="32"
             HorizontalOptions="Center" />

      <Label Text="Welcome to Prism for .NET MAUI"
             SemanticProperties.HeadingLevel="Level2"
             SemanticProperties.Description="Welcome to Prism for dot net Multi platform App U I"
             FontSize="18"
             HorizontalOptions="Center" />

      <Button Text="{Binding Text}"
              SemanticProperties.Hint="Counts the number of times you click"
              Command="{Binding CountCommand}"
              HorizontalOptions="Center" />

    </VerticalStackLayout>
  </ScrollView>

</ContentPage>

想說應該可以很輕鬆的來使用這個功能,沒想到卻得到這樣的錯誤訊息

System.NotImplementedException: 'Either set MainPage or override CreateWindow.'

為了確認我的作法沒有錯誤,我使用原生的 MAUI 專案範本,建立一個原生 MAUI 專案,把上面的做法重新實作一遍,沒想到卻是正常的。

想說,好吧,那麼我把這個 StatusBarBehavior 原始碼找出來,寫到 Prism.Maui 建立起來的專案,直接引用我複製的 StatusBarBehavior 行為,底下是這個 StatusBarBehavior 程式碼

using CommunityToolkit.Maui.Core.Platform;
using CommunityToolkit.Maui.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;

namespace mauiStatusBar.Behaviors
{
    /// <summary>
    /// <see cref="PlatformBehavior{TView,TPlatformView}"/> that controls the Status bar color
    /// </summary>
    [UnsupportedOSPlatform("Windows"), UnsupportedOSPlatform("MacCatalyst"), UnsupportedOSPlatform("MacOS")]
    public class StatusBarBehavior : PlatformBehavior<ContentPage>
    {
        /// <summary>
        /// <see cref="BindableProperty"/> that manages the StatusBarColor property.
        /// </summary>
        public static readonly BindableProperty StatusBarColorProperty =
            BindableProperty.Create(nameof(StatusBarColor), typeof(Color), typeof(StatusBarBehavior), Colors.Transparent);


        /// <summary>
        /// <see cref="BindableProperty"/> that manages the StatusBarColor property.
        /// </summary>
        public static readonly BindableProperty StatusBarStyleProperty =
            BindableProperty.Create(nameof(StatusBarStyle), typeof(StatusBarStyle), typeof(StatusBarBehavior), StatusBarStyle.Default);

        /// <summary>
        /// Property that holds the value of the Status bar color. 
        /// </summary>
        public Color StatusBarColor
        {
            get => (Color)GetValue(StatusBarColorProperty);
            set => SetValue(StatusBarColorProperty, value);
        }

        /// <summary>
        /// Property that holds the value of the Status bar color. 
        /// </summary>
        public StatusBarStyle StatusBarStyle
        {
            get => (StatusBarStyle)GetValue(StatusBarStyleProperty);
            set => SetValue(StatusBarStyleProperty, value);
        }

#if !(WINDOWS || MACCATALYST)

        /// <inheritdoc /> 
#if IOS
	protected override void OnAttachedTo(ContentPage bindable, UIKit.UIView platformView)
#elif ANDROID
        protected override void OnAttachedTo(ContentPage bindable, Android.Views.View platformView)
#else
	protected override void OnAttachedTo(Page bindable, object platformView)
#endif
        {
            StatusBar.SetColor(StatusBarColor);
            StatusBar.SetStyle(StatusBarStyle);
        }


        /// <inheritdoc /> 
        protected override void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            if (string.IsNullOrWhiteSpace(propertyName))
            {
                return;
            }

            base.OnPropertyChanged(propertyName);

            if (IsOneOf(propertyName, StatusBarColorProperty, Page.WidthProperty, Page.HeightProperty))
            {
                StatusBar.SetColor(StatusBarColor);
            }
            else if (propertyName == StatusBarStyleProperty.PropertyName)
            {
                StatusBar.SetStyle(StatusBarStyle);
            }
        }
#endif

        public bool IsOneOf(string propertyName, BindableProperty p0, BindableProperty p1, BindableProperty p2)
        {
            return propertyName == p0.PropertyName ||
                propertyName == p1.PropertyName ||
                propertyName == p2.PropertyName;
        }

    }
}

天不從人願,還是得到一樣的錯誤訊息,現在只好到 PrismLibrary / Prism.Maui 發出一個 Issue,也許是我有贊助這個開源專案,所以很快地得到作者的回應,不過,看樣子不是那麼容易的來解決這個問題。

經過幾天的陸續測試與思考,我想到一個做法,我想要在 MainPage 的覆寫方法 OnAppearing 內,直接呼叫這個 CommunityToolkit 所提供的 StatusBar.SetColor 這個方法,不知道是否可以解決這個問題呢?

因此,我將原來的 MainPage.xaml 修改如下

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:localBehaviors="clr-namespace:mauiStatusBar.Behaviors"
             Title="{Binding Title}"
             x:Class="mauiStatusBar.Views.MainPage">

    <!--<ContentPage.Behaviors>
        <toolkit:StatusBarBehavior StatusBarColor="Fuchsia" StatusBarStyle="LightContent" />
    </ContentPage.Behaviors>-->

    <ScrollView>
    <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

      <Image Source="prism.png"
             SemanticProperties.Description="Cute dot net bot waving hi to you!"
             HeightRequest="150"
             HorizontalOptions="Center" />

      <Label Text="Hello, World!"
             SemanticProperties.HeadingLevel="Level1"
             FontSize="32"
             HorizontalOptions="Center" />

      <Label Text="Welcome to Prism for .NET MAUI"
             SemanticProperties.HeadingLevel="Level2"
             SemanticProperties.Description="Welcome to Prism for dot net Multi platform App U I"
             FontSize="18"
             HorizontalOptions="Center" />

      <Button Text="{Binding Text}"
              SemanticProperties.Hint="Counts the number of times you click"
              Command="{Binding CountCommand}"
              HorizontalOptions="Center" />

    </VerticalStackLayout>
  </ScrollView>

</ContentPage>

我不再使用 Behavior 來變更狀態列的顏色,接著,在這個頁面的 Code Behind 內,改成底下的程式碼

using CommunityToolkit.Maui.Core.Platform;

namespace mauiStatusBar.Views;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    protected override void OnAppearing()
    {
        var foo = App.Current.MainPage;
        StatusBar.SetColor(Colors.Red);
    }
}

並且執行這個 Android 專案,得到底下的結果

太好了,這樣的作法是可行的 




2022年9月29日 星期四

在 .NET MAUI 專案內進行讀取 QR Code 二維條碼內容

在 .NET MAUI 專案內進行讀取 QR Code 二維條碼內容

對於如何透過手機 App 來讀取 QRCode 二維條碼這樣的需求,向來是要學習手機 App 開發學員的前三名最想要知道的技術,在這篇文章中,將會來說明如何透過手機鏡頭,讀取 QR Code 二維條碼上的內容是甚麼?

建立 .NET MAUI 應用程式 專案

  • 開啟 Visual Studio 2022 版本

  • 點選螢幕右下角的 [建立新的專案] 按鈕

  • 切換右上角的 [所有專案類型] 下拉選單控制項

  • 找到並且點選 [MAUI] 這個選項

  • 從清單中找到並選擇 [.NET MAUI 應用程式] 這個專案範本

    此專案可用於建立適用於 iOS、Android、Mac Catalyst、Tizen和WinUI 的 .NET MAUI 應用程式

  • 點選右下角的 [下一步] 按鈕

  • 當出現了 [設定新的專案] 對話窗

  • 在 [專案名稱] 欄位內,輸入 mauiScanQRCode

  • 點選右下角的 [下一步] 按鈕

  • 當出現了 [其他資訊] 對話窗

  • 對於 [架構] 的下拉選單控制項,使用預設值

  • 點選右下角的 [建立] 按鈕

加入 PropertyChanged.Fody 的 NuGet 套件

  • 滑鼠右擊該專案的 [相依性] 節點
  • 從彈出功能表中選擇 [管理 NuGet 套件] 功能選項
  • 此時,[NuGet: mauiScanQRCode] 視窗將會出現
  • 點選 [瀏覽] 標籤頁次
  • 在左上方的搜尋文字輸入盒內輸入 ZXing.Net.Maui 關鍵字
  • 現在,將會看到 ZXing.Net.Maui 套件出現在清單內
  • 點選這個 ZXing.Net.Maui 套件,並且點選右上方的 [安裝] 按鈕,安裝這個套件到這個專案內。

進行 ZXing.NET.Maui 套件的服務註冊

  • 在專案根目錄下
  • 找到並且打開 MauiProgram.cs 檔案
  • 找到 .UseMauiApp<App>() 程式碼
  • 在其下方加入 .UseBarcodeReader() Fluent 呼叫程式碼

對 Android 專案進行需要用到權限宣告

  • 在專案內,找到 Platforms\Android 目錄
  • 在其目錄內找到並且打開 MainApplication.cs 檔案
  • 找到 namespace 關鍵字
  • 在其上方加入 [assembly: UsesPermission(Android.Manifest.Permission.Camera)] 敘述,說明這個 Android App 需要使用到 Camera 這個權限,如此,才可以讓這個 Zxing 軟體透過鏡頭來讀取到條碼的圖片

對 Android 專案進行需要用到權限宣告

  • 在專案內,找到 Platforms\iOS 目錄
  • 找到 Info.plist 檔案,並且選擇使用 XML (文字) 編輯器工具,打開這個檔案
  • 找到 </dict> 這個關鍵字
  • 在其上方,加入底下的 XML 宣告
<key>NSCameraUsageDescription</key>
<string>This app uses barcode scanning to...</string>

將 QR Code 讀取檢視控制項放到 XAML 頁面內

  • 在專案根目錄下
  • 找到並且打開 MainPage.xaml 這個檔案
  • 使用底下 XAML 內容將其替換掉
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI"
             x:Class="mauiScanQRCode.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            <Label x:Name="labelResult"
                   FontSize="24" TextColor="Red"/>
            
            <zxing:CameraBarcodeReaderView
                x:Name="cameraBarcodeReaderView"
                BarcodesDetected="BarcodesDetected" />
            
            <Button
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

在上面的 XAML 頁面宣告中,首先加入了一個新的 zxing 命名空間宣告 : xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI" ,如此,在這個頁面中,便可以透過這個 zxing 這個前置詞,存取到 ZXing.Net.MAUI 這個套件所提供的功能與檢視了。

在 Button 按鈕前,加入了 <zxing:CameraBarcodeReaderView x:Name="cameraBarcodeReaderView" BarcodesDetected="BarcodesDetected" /> 這個 CameraBarcodeReaderView 檢視控制項,這個控制項將會把鏡頭看到的影像,顯示到螢幕上,並且會針對每個讀取進來的影像,進行條碼分析,一旦發現到有條碼圖片存在,並且該條碼也被解碼了,將會呼叫觸發這個 BarcodesDetected 事件,並且可以透該事件內提供的事件參數,取得所掃描到的條碼內容。

由於是採用事件觸發方式來設計,所以,需要透過 Code Behind 的方式來設計相關商業邏輯

  • 找到並且打開 MainPage.xaml.cs 檔案
  • 使用底下 C# 程式碼來替換掉這個檔案內容
using ZXing.Net.Maui;

namespace mauiScanQRCode;

public partial class MainPage : ContentPage
{
	int count = 0;

	public MainPage()
	{
		InitializeComponent();

        cameraBarcodeReaderView.Options = new BarcodeReaderOptions
        {
            Formats = BarcodeFormats.All,
            AutoRotate = true,
            Multiple = true
        };
    }

	private void OnCounterClicked(object sender, EventArgs e)
	{
		count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);
	}

	private void BarcodesDetected(object sender, ZXing.Net.Maui.BarcodeDetectionEventArgs e)
	{
		string Result = "";
        foreach (var barcode in e.Results)
            Result += $"Barcodes: {barcode.Format} -> {barcode.Value}  ";

		MainThread.BeginInvokeOnMainThread(() =>
		{
			labelResult.Text = Result;
		});
    }
}

在這個頁面建構式內,首先建立一個 BarcodeReaderOptions 物件,用來宣告這個 QRCode 的掃描行為,這裡使用了 Formats 屬性,宣告要掃描所有類型的一維或者二維條碼圖片,使用 AutoRotate 將會允許旋轉,使用 Multiple 屬性,宣告可以讀取多個條碼內容。完成之後,設定到這個 QR Code 讀取元件內的 cameraBarcodeReaderView.Options 屬性內。

對於一旦條碼讀取到之後,將會觸發這裡的事件委派方法,其中,該事件傳遞進來的參數將會是 ZXing.Net.Maui.BarcodeDetectionEventArgs 型別,由於上面宣告了可以一次讀取一個以上的條碼圖片,因此,在 e.Results 將會有可能擁有多筆條碼內容,所以,在此使用迴圈,將所有讀到的條碼內容,都放到 Result 這個變數內。

當蒐集到所有的條碼內容之後,便可以將這些文字內容,顯示到螢幕上,這裡使用了 labelResult.Text = Result; 這樣的敘述,不過,因為這個觸發所執行的事件方法,不會在 UI (或 主) 執行緒上運行,為了要能夠更新 labelResult 這個 UI 元件的 Text 屬性值,所以,需要透過 MainThread.BeginInvokeOnMainThread 敘述,讓這些程式碼能夠在主執行緒上來運行。

執行與測試

底下影片將會是在實體手機上的執行結果