2022年9月6日 星期二

在 WPF 上建立與使用 Lottie 來設計出具有動畫效果

在 WPF 上建立與使用 Lottie 來設計出具有動畫效果

之前有寫過一篇 建立與使用 Lottie 來設計出具有動畫效果的 App 文章,說明如何在 .NET MAUI 專案中,使用 Lottie 檔案,產生出具有動畫效果的應用程式,這兩天聽到有個 WPF 上有這樣類似的需求,就想說是否可以在 WPF 上也能夠做出這樣的效果呢?

抱持這這樣的疑問,便開始先在網路進行搜尋,想要在 WPF 下來使用 Lottie 這樣機制的時候,究竟要使用哪個套件比較好,姊果發現到,現在可以找到的 Lottie 套件僅能夠在 .NET 5 以上的環境上運作。

而甚麼事 Lottie 呢?這是在 2015 年之後才出現的一個功能,根據維基百科上的描述可以得到這方面的說明

Lottie is a file format for vectorial animation, and is named after Charlotte Reiniger, a German pioneer of silhouette animation.

在這個 https://lottiefiles.com/ 網站上,可以看到種 Lottie 做出的動畫效果,而且可以下載到相當多的動畫 JSON 檔案

現在將來嘗試在 WPF 專案下,透過隨便下載的 Lottie JSON 檔案,將這個動畫能夠在 WPF 應用程式下來運行

建立 WPF 專案

  • 開啟 Visual Studio 2022 開發工具

  • 當 [Visual Studio 2022] 對話窗出現的時候

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

  • 現在將看到 [建立新專案] 對話窗

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

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

  • 從清單中找到並選擇 [WPF 應用程式] 這個專案範本 (記得要選擇使用 C# 程式語言)

    此專案可用於建立 .NET WPF 應用程式

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

  • 此時將會看到 [設定新的專案] 對話窗

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

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

  • 最後會看到 [其他資訊] 對話窗

  • 使用預設設定值,也就是 [架構] 為 [.NET 6.0 (長期支援)]

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

加入 LottieSharp 的 NuGet 套件

  • 滑鼠右擊該專案的 [相依性] 節點
  • 從彈出功能表中選擇 [管理 NuGet 套件] 功能選項
  • 此時,[NuGet: wpfLottie] 視窗將會出現
  • 點選 [瀏覽] 標籤頁次
  • 在左上方的搜尋文字輸入盒內輸入 LottieSharp 關鍵字
  • 若你沒有看到 8.0 以上的版本,請勾選 [包括搶鮮版] 檢查盒控制項
  • 現在,將會看到 LottieSharp 套件出現在清單內
  • 點選這個 LottieSharp 套件,並且點選右上方的 [安裝] 按鈕,安裝這個套件到這個專案內。

下載與複製 Lottie 檔案道專案內

  • 下載完成後的檔案名稱將會為 [9945-space-launch.json]
  • 透過檔案總管拖拉這個檔案到剛剛建立的 WPF 專案根目錄下
  • 在方案總管點選這個檔案
  • 在 [屬性] 視窗內
  • 設定 [建置動作] 的屬性值為 [永遠複製]

設計使用 Lottie 元件的視窗

  • 打開 [MainWindow.xaml] 檔案
  • 將底下 XAML 宣告標記覆蓋掉這個檔案的內容
<Window x:Class="wpfLottie.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:lottie="clr-namespace:LottieSharp.WPF;assembly=LottieSharp"
       xmlns:local="clr-namespace:wpfLottie"
        mc:Ignorable="d"
        Title="Lottie 在 WPF 上的使用範例" Height="450" Width="800">
    <Grid>
        <lottie:LottieAnimationView
    Width="200"
    Height="300"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    AutoPlay="True"
    FileName="9945-space-launch.json"
    RepeatCount="-1" />
    </Grid>
</Window>

啟動與執行

  • 底下會是執行後的運作畫面

 




2022年9月3日 星期六

.NET MAUI 可以使用雙指放大與移動的設計效果

.NET MAUI 可以使用雙指放大與移動的設計效果

建立 .NET MAUI 應用程式 專案

  • 開啟 Visual Studio 2022 版本

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

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

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

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

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

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

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

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

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

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

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

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

加入 PropertyChanged.Fody 的 NuGet 套件

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

修正頁面

  • 在根目錄下找到並且打開 MainPage.xaml 這個檔案

  • 在這個 XAML 檔案內的 ContentPage 根節點內,加入一個新的命名空間

    xmlns:pinch="clr-namespace:Bertuzzi.MAUI.PinchZoomImage;assembly=Bertuzzi.MAUI.PinchZoomImage"

  • 將這個頁面的整個 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:pinch="clr-namespace:Bertuzzi.MAUI.PinchZoomImage;assembly=Bertuzzi.MAUI.PinchZoomImage"
             x:Class="mauiPinchZoomImage.MainPage">

    <StackLayout HorizontalOptions="Center" VerticalOptions="Center"
            Spacing="25"
            Padding="30,0">

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

        <pinch:PinchZoom
            Margin="20">
            <Image Source="https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Vervet_Monkey_%28Chlorocebus_pygerythrus%29.jpg/1200px-Vervet_Monkey_%28Chlorocebus_pygerythrus%29.jpg?20181109194254"/>
        </pinch:PinchZoom>
    </StackLayout>

</ContentPage>

在這個頁面內,使用 StackLayout 作為這個頁面的根項目,在最後面使用 pinch:PinchZoon 來加入這個可以縮放的控制項,而在這個控制項內容屬性 Content Property 指定一個 Image 做為其要顯示的內容,這裡將會隨機從網路找到一個圖片 URL ,做為要顯示這個圖片的來源。

現在可以建置與執行看看,將會得到底下的畫面

現在可以使用雙指在猴子圖片上,進行捏合與放開的操作,就可以看到圖片正在即時的放大效果,而,若使用單指在猴子圖片上進行拖拉,則可以移動這個圖片的位置。

 





2022年9月2日 星期五

如何設計有效的 Parallel.ForEachAsync 迴圈 - 需要使用平行計算方式來執行 async Method 非同步方法

如何設計有效的 Parallel.ForEachAsync 迴圈 - 需要使用平行計算方式來執行 async Method 非同步方法

對於要使用 [Parallel.ForEach] 來設計出一個具有平行作業的應用程式,相信對於許多 .NET C# 開發者而言,應該不是個很大的問題,此時,可以參考 如何:撰寫簡單的 Parallel.ForEach 迴圈 這篇文章中的說明與範例程式碼,相信可以很容易得輕鬆上手。

可是對於 Parallel.ForEach 方法 的使用,需要傳入至少一個列舉 IEnumerable 與一個委派方法,不過,在這裡若是設計使用同步方法來進行設計程式碼,相信執行上一切都沒有問題, Parallel.ForEach 方法將會使用平行計算的方式,使用列舉物件內的值,平行執行與傳入到這個委派方法,但是,當這個委派方法已經改成一個非同步方法,也就是在這個委派方法有加入了 async 這個修飾詞,那麼將會發現到,一旦執行到 Parallel.ForEach 之後,將會馬上就執行到下一行敘述,不會等到所有的列舉物件都執行完成後,才會繼續往下執行

對於使用同步委派方法來設計 Parallel.ForEach 的應用,可以參考底下的範例程式碼

#region 若在 Parallel.Foreach 內使用同步方式進行委派方法的設計,會等到所有委派方法都執行完畢後,才會繼續往下執行
Console.WriteLine($"使用 Parallel.Foreach 與同步委派方法 開始 {DateTime.Now}");
Parallel.ForEach(Enumerable.Range(0, 3), async (x, t) =>
{
    Console.WriteLine("  Bpfsync");
    Thread.Sleep(3000);
    Console.WriteLine("  Cpfsync");
});
// 當看到這行敘述,表示 Parallel.ForEach 已經結束執行,不過,將還沒看到所有的 Cpf 文字輸出
Console.WriteLine($"使用 Parallel.Foreach 與同步委派方法 結束 {DateTime.Now}");

在這裡,將會使用 Enumerable.Range(0, 3) 一個列舉物件,且這個列舉物件內有三個值,並使用 Lambda 指定一個委派方法,該方法將會使用 Thread.Sleep(3000) 模擬該方法需要花費三秒鐘的時間來執行某些工作,整個 Lambda 委派方法將會是使用同步方式來進行運行。

從底下的執行結果可以看出,一旦 Parallel.ForEach 開始執行之後,將會平行執行 Enumerable.Range(0, 3) 列舉物件內的每個值,

使用 Parallel.Foreach 與同步委派方法 開始 2000/9/2 上午 09:23:09
  Bpfsync
  Bpfsync
  Bpfsync
  Cpfsync
  Cpfsync
  Cpfsync
使用 Parallel.Foreach 與同步委派方法 結束 2000/9/2 上午 09:23:12

若將程式碼改成如下,將每個平行執行的傳入物件值顯示出來

#region 若在 Parallel.Foreach 內使用同步方式進行委派方法的設計,會等到所有委派方法都執行完畢後,才會繼續往下執行
Console.WriteLine($"使用 Parallel.Foreach 與同步委派方法 開始 {DateTime.Now}");
Parallel.ForEach(Enumerable.Range(0, 3), async (x, t) =>
{
    Console.WriteLine($"  Bpfsync {x}");
    Thread.Sleep(3000);
    Console.WriteLine($"  Cpfsync {x}");
});
// 當看到這行敘述,表示 Parallel.ForEach 已經結束執行,不過,將還沒看到所有的 Cpf 文字輸出
Console.WriteLine($"使用 Parallel.Foreach 與同步委派方法 結束 {DateTime.Now}");
#endregion

這裡將會是上面程式碼執行後的結果,從這裡可以再度驗證與得到一個結論, Parallel.ForEach 確實逐一平行來執行美個委派方法,可以在平行執行作業過程中,是每有說哪個執行緒必須一定要先執行,或者要按著當初啟動的順序來逐一平行執行的慣例或者說法。

使用 Parallel.Foreach 與同步委派方法 開始 2000/9/2 上午 09:34:12
  Bpfsync 0
  Bpfsync 1
  Bpfsync 2
  Cpfsync 0
  Cpfsync 2
  Cpfsync 1
使用 Parallel.Foreach 與同步委派方法 結束 2000/9/2 上午 09:34:15

現在將要平行運行的委派方法改成非同步委派方法,因此,原先使用 Thread.Sleep(3000) 這個敘述,將會改成使用 await Task.Delay(3000) 這樣的敘述,然而,因為使用了 await 運算子,所以,就需要在委派方法前面加上 async 修飾詞,底下將會是這樣的程式碼

#region 若在 Parallel.Foreach 內使用 async 方法,將會立即結束平行敘述,相關程式碼會在背景執行中
Console.WriteLine($"使用 Parallel.Foreach 開始 {DateTime.Now}");
Parallel.ForEach(Enumerable.Range(0, 3), async (x, t) =>
{
    Console.WriteLine($"  Bpf {x}");
    await Task.Delay(3000);
    Console.WriteLine($"  Cpf {x}");
});
// 當看到這行敘述,表示 Parallel.ForEach 已經結束執行,不過,將還沒看到所有的 Cpf 文字輸出
Console.WriteLine($"使用 Parallel.Foreach 結束 {DateTime.Now}");
#endregion


#region 故意休息五秒,等待上述的平行作業全部都結束
Console.WriteLine();
Console.WriteLine($"休息 五秒鐘");
await Task.Delay(5000);
Console.WriteLine();

因為若在 Parallel.Foreach 內使用 async 方法,將會立即結束平行敘述,相關委派方法內程式碼會仍在背景執行中,因此,無法透過 Parallel.ForEach 來得知是否所有的平行運算都已經全部完成了;由於平行計算模擬花費 3 秒計算時間,而這三秒將會在背景下運行,因此,這裡使用 await Task.Delay(5000) 這樣的敘述,故意休息五秒,等待上述的所有背景平行作業全部都結束,因此將會看到底下的輸出結果;從執行結果可以看出,當 [休息 五秒鐘] 文字顯示之後,並且真的讓當前執行緒強制睡眠五秒鐘之後,約在三秒之後,就會看到每個委派方法執行結束的文字輸出。

使用 Parallel.Foreach 開始 2022/9/2 上午 09:34:15
  Bpf 0
  Bpf 2
  Bpf 1
使用 Parallel.Foreach 結束 2022/9/2 上午 09:34:15

休息 五秒鐘
  Cpf 2
  Cpf 0
  Cpf 1

為了解決這樣的應用,在 .NET 6 的 BCL 中,將會提供了 Parallel.ForEachAsync 方法 ,透過這個方法,將會可以做到使用 Parallel 類別提供的功能,並且使用非同步的方法來平行執行委派方法

#region 這裡使用 Parallel.ForEachAsync 來平行非同步方法,將不會有上述問題,全部的非同步作業都平行執行完畢,該行敘述才會繼續往下執行,這可以從時間戳記看出
Console.WriteLine($"使用 Parallel.ForEachAsync 開始 {DateTime.Now}");
await Parallel.ForEachAsync(Enumerable.Range(0, 3), async (x, t) =>
{
    Console.WriteLine($"  Bpfa {x}");
    await Task.Delay(3000);
    Console.WriteLine($"  Cpfa {x}");
});
Console.WriteLine($"使用 Parallel.ForEachAsync 結束 {DateTime.Now}");
#endregion

在這裡將會使用 await Parallel.ForEachAsync 來使用非封鎖方式來等待所有的平行作業都執行完畢,而且這些要採用平行執行的委派方法,都將採用非同步方法(有 async 修飾詞的方法)來設計。

從底下的執行結果,應該是當初需求所期望能夠設計出來的功能

使用 Parallel.ForEachAsync 開始 2000/9/2 上午 09:34:20
  Bpfa 0
  Bpfa 1
  Bpfa 2
  Cpfa 2
  Cpfa 1
  Cpfa 0 

使用 Parallel.ForEachAsync 結束 2000/9/2 上午 09:34:27 





2022年8月29日 星期一

第一次使用 Python 來體驗 Machine Learning 開發,體驗網路各種傳聞

第一次使用 Python 來體驗 Machine Learning 開發,體驗網路各種傳聞

把一部長達 6 小時的 Python 教學影片看完,對其中的機器學習 Machine Learning 部分覺得相當有趣,今天就把這部分過程自己手動作一次,沒想到,要使用 Python 來設計一個使用決策樹應用的 Machine Learning 功能,竟然大約使用到 8 行的 Python 程式碼,就可以完成,而且實際運作結果還相當的不錯。

在此我體驗到了 Python 的容易學習、程式碼精簡、具有相當多應用套件可以使用的情境,但是,在這個手動練習過程中,也發現到了,想要能夠充分發揮 Python 的能力,還需要把相關的套件模組進行深入研究與精通,畢竟發生需求的時候,若能夠知道有那些模組已經有提供相關功能,便可以立即拿來應用;第二個就是,對於現有的模組所提供的功能,若想要做額外的變化或者應用的時候,這需要日後花些時間來進行體驗;最後就是當遇到技術問題的時候,該如何解決,例如,模組更新導致 Breaking Change,找不出發生問題該如何解決等時候,這可能需要以時間換取技術能力來提升吧。

畢竟,我已經知道了,Python 入門門檻不高,可以要能夠成為 Python 精通者,應付不同需求的設計,這部分還是需要有時間與經驗才能夠解決的

準備 Python 開發專案

  • 建立一個要練習 Machine Learning 的目錄

  • 點選做下角的 Windows 視窗圖示

  • 找到 Anaconda Prompt (anaconda3) 這個應用程式

  • 開啟這個應用程式

  • 使用 cd 命令,切換到剛剛建立好的練習目錄下

  • 請繼續輸入 jupyter notebook 開啟 Jupyter 的 IDE

  • 一旦看到底下畫面內容,此時,瀏覽器會自動開啟

  • 點選右上角的 [New] 下拉選單按鈕

  • 從彈出清單中,選擇 Notebook: Python 3 (ipykernel) 這個選項

  • 現在,另外一個網頁頁次將會開啟

  • 將游標移動到左上方得 Untitled 文字處

  • 點選這個文字

  • 在 [Rename Notebook] 對話窗內,輸入這個新筆記本名稱

  • 請輸入 My-First-Python-Machine-Learning

  • 完成後,點選該對話窗右下方的 Rename 按鈕

建立 Machine Learning 需要用到的範例資料

現在可以自己建立,或者從網路上下載這次要做練習會用到的資料檔案

底下是這次要分析的資料

age	gender	genre
20	1	HipHop
23	1	HipHop
25	1	HipHop
26	1	Jazz
29	1	Jazz
30	1	Jazz
31	1	Classical
33	1	Classical
37	1	Classical
20	0	Dance
21	0	Dance
25	0	Dance
26	0	Acoustic
27	0	Acoustic
30	0	Acoustic
31	0	Classical
34	0	Classical
35	0	Classical

從上面的資料可以看出,這是一個不同年齡(欄位1 age)層與不同性別(欄位2 gender)的人,喜歡聽的音樂類型(欄位3 genre)紀錄

  • 請在剛剛建立的目錄下,建立一個 .csv 檔案,該檔案名稱為 music.csv

開始進行 Python 機器學習程式碼開發

  • 在 Jupyter 筆記本中,輸入底下 Python 程式碼
import pandas as pd
music_data = pd.read_csv('music.csv')
music_data

第一行表示匯入 Pandas 這個用於資料分析的工具,這個工具目的可以從網頁說明上看出 pandas is a fast, powerful, flexible and easy to use open source data analysis and manipulation tool, built on top of the Python programming language.

第二行則是讀取剛剛建立的 Sample Data ,也就是 music.csv 這個檔案內容,雖然這個檔案是屬於 .csv 類型的檔案,對於 pandas 工具而言,這是輕而易舉的事情,因為,他可以認識與讀入這個檔案內的相關內容。

想要知道 pandas 看到甚麼樣的資料,可以使用第三行程式碼,顯示內容

  • 按下 [Ctrl] + [Enter] 來執行這三行程式碼

  • 底下將會是執行結果

建立訓練模型來進行預測分析

這個第一個 Python 練習,將會要進行機器學習的預測,因此,需要建立一個輸入與輸出的模型,透過輸入模型的資料,預測可能會產生甚麼樣的輸出資料。

  • 首先,建立輸入模型
import pandas as pd
music_data = pd.read_csv('music.csv')
X = music_data.drop(columns=['genre'])
X

這裡建立一個 X 物件,代表輸入模型,該模型是這個 CSV 檔案內的前兩個欄位,分別代表年紀與性別,所以,在此使用 drop 函數,將原有資料集 data set 的最後一個欄位移除,保留前面兩個欄位,並且將這個物件設定給新的變數 X。

當此用 drop 函數的時候,並不會將原有的資料集刪除,而是產生一個新的資料集出來

最後,使用 X 這個敘述,查看這個新物件的內容

從下圖可以看出,X 物件內有著年紀與性別這兩個欄位,這樣的物件就代表了輸入模型

  • 使用底下程式碼建立輸出模型
import pandas as pd
music_data = pd.read_csv('music.csv')
X = music_data.drop(columns=['genre'])
y = music_data['genre']
y

這裡是執行結果

開始進行學習與預測

在這裡將會練習使用決策樹方法來進行資料行為預測,為了要使用這樣的分析方法,需要藉助於 scikit-learn: machine learning in Python 這個工具

從網站介紹內容,可以看出這個工具提供出相當多的好用功能

使用底下的 Python 敘述,引用 決策樹 這個工具

from sklearn.tree import DecisionTreeClassifier

底下是完成後的程式碼,大約使用到 8 行的 Python 程式碼

import pandas as pd
from sklearn.tree import DecisionTreeClassifier

music_data = pd.read_csv('music.csv')
X = music_data.drop(columns=['genre'])
y = music_data['genre']


model = DecisionTreeClassifier()
model.fit(X,y)
predictions = model.predict([[21,1],[21,0]])
predictions

這裡首先建立一個 model 物件

接著使用了 model.fit(X,y) 敘述,進行模型的訓練 training sample data

接著,使用了 model.predict([[21,1],[21,0]]) 進行預設,希望能夠從現有的數據資料中,了解到年紀在 21 歲的男性,喜歡甚麼類型音樂,而對於 21 歲的女性,喜歡甚麼樣類型的音樂

底下為這次練習的執行結果

區分訓練與測試用的資料

通常來說,所取得的資料,將會使用80%左右的紀錄來進行訓練,並且使用 20% 的紀錄來進行測試,藉此了解到這樣的預測可以達到多少準確程度。

在這裡將會使用 sklearn.metrics 模組來進行量測準確度,底下將會是這樣的練習程式碼

import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

music_data = pd.read_csv('music.csv')
X = music_data.drop(columns=['genre'])
y = music_data['genre']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

model = DecisionTreeClassifier()
model.fit(X_train,y_train)
predictions = model.predict(X_test)

score = accuracy_score(y_test, predictions)
score

透過 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) 敘述,將原有的資料集使用 80% / 20% 的比例分別切割成為不同資料集,接著,使用 model.fit(X_train,y_train) 敘述,將切割出來的紀錄進行訓練,最後,使用 score = accuracy_score(y_test, predictions) 敘述來量測預測準確度

底下是執行結果

補充說明

若想要找到可以練習用的分析資料,可以到 Kaggle 網站內來搜尋

  • 使用瀏覽器打開 Kaggle 網站,從這個網站的說明文字:Kaggle: Your Machine Learning and Data Science Community,可以看到從這裡可以找到相當多機器學習與資料科學上的相關資料或者資訊

  • 在右上方的搜尋文字輸入盒內,輸入 video game sale 文字

  • 此時,網頁畫面將出現如下圖

  • 點選第一個搜尋出來的項目 Video Game Sales 這個連結

  • 將會看到這個 Data Set 的介紹