diff --git a/PrimaryFormParts/SongSearch/PrimaryForm.SongSearch.PinyinSearch.cs b/PrimaryFormParts/SongSearch/PrimaryForm.SongSearch.PinyinSearch.cs index 818b3af..a31d9ba 100644 --- a/PrimaryFormParts/SongSearch/PrimaryForm.SongSearch.PinyinSearch.cs +++ b/PrimaryFormParts/SongSearch/PrimaryForm.SongSearch.PinyinSearch.cs @@ -10,15 +10,20 @@ namespace DualScreenDemo { public partial class PrimaryForm { + // 拼音歌曲的 PictureBox private PictureBox pictureBoxPinYinSongs; + // 存放拼音按鈕的陣列 private Button[] letterButtonsForPinYinSongs; + // 特殊功能按鈕(修改、清除、關閉) private Button modifyButtonPinYinSongs; private Button clearButtonPinYinSongs; private Button closeButtonPinYinSongs; + // 用於顯示輸入文字的輸入框 private RichTextBox inputBoxPinYinSongs; - + // 拼音歌曲搜尋按鈕點擊事件 private void PinyinSearchSongsButton_Click(object sender, EventArgs e) { + // 更新搜尋模式按鈕的背景圖 zhuyinSearchSongButton.BackgroundImage = zhuyinSearchSongNormalBackground; englishSearchSongButton.BackgroundImage = englishSearchSongNormalBackground; pinyinSearchSongButton.BackgroundImage = pinyinSearchSongActiveBackground; @@ -26,52 +31,71 @@ namespace DualScreenDemo handWritingSearchSongButton.BackgroundImage = handWritingSearchSongNormalBackground; numberSearchSongButton.BackgroundImage = numberSearchSongNormalBackground; - + // 讀取 config.ini 並獲取拼音圖片的路徑 var configData = LoadConfigData(); string pinyinImagePath = Path.Combine(Application.StartupPath, configData["ImagePaths"]["PinYinSongs"]); - + // 顯示拼音歌曲圖片 ShowImageOnPictureBoxPinYinSongs(Path.Combine(Application.StartupPath, pinyinImagePath)); - + // 設定不同模式的 UI 顯示 SetZhuYinSingersAndButtonsVisibility(false); SetEnglishSingersAndButtonsVisibility(false); SetPinYinSingersAndButtonsVisibility(false); SetPinYinSongsAndButtonsVisibility(true); pictureBoxPinYinSongs.Visible = true; } - + // 初始化拼音按鈕 private void InitializeLetterButtonsForPinYinSongs() { + // 從設定檔 (config.ini) 讀取配置數據 var data = LoadConfigData(); + + // 從配置數據中載入拼音字母按鈕的影像 (包含正常、點擊、滑鼠懸停三種狀態) var buttonImages = LoadButtonImages(data, "PinYinLetterButtonImages", 26); + + // 定義 QWERTY 鍵盤排列的字母順序 string qwertyLayout = "QWERTYUIOPASDFGHJKLZXCVBNM"; + + // 初始化拼音按鈕陣列,長度為 26(對應英文字母) letterButtonsForPinYinSongs = new Button[26]; + // 迴圈遍歷 26 個字母,依序建立按鈕 for (int i = 0; i < 26; i++) { + // 從配置檔案讀取當前按鈕的座標資訊 (X, Y, Width, Height) var coords = data["PinYinLetterButtonCoordinates"][$"button{i}"].Split(','); + + // 建立拼音按鈕,並設定名稱、座標、影像與事件處理函式 letterButtonsForPinYinSongs[i] = CreateButton( - $"letterButton_{qwertyLayout[i]}", - (int.Parse(coords[0]), int.Parse(coords[1]), int.Parse(coords[2]), int.Parse(coords[3])), - buttonImages[$"button{i}"].normal, - buttonImages[$"button{i}"].mouseDown, - buttonImages[$"button{i}"].mouseOver, - LetterButtonPinYinSongs_Click + $"letterButton_{qwertyLayout[i]}", // 按鈕名稱,例如 "letterButton_Q" + (int.Parse(coords[0]), int.Parse(coords[1]), int.Parse(coords[2]), int.Parse(coords[3])), // 解析座標數據 + buttonImages[$"button{i}"].normal, // 正常狀態影像 + buttonImages[$"button{i}"].mouseDown, // 按下狀態影像 + buttonImages[$"button{i}"].mouseOver, // 滑鼠懸停狀態影像 + LetterButtonPinYinSongs_Click // 點擊事件處理函式 ); + + // 設定按鈕的標籤 (Tag) 為對應的字母,例如 Q、W、E... letterButtonsForPinYinSongs[i].Tag = qwertyLayout[i]; + + // 將按鈕新增到表單的控制項集合中,讓其顯示在介面上 this.Controls.Add(letterButtonsForPinYinSongs[i]); } } - + // 處理拼音按鈕點擊事件 private void LetterButtonPinYinSongs_Click(object sender, EventArgs e) { - + // 嘗試將觸發事件的物件轉換為 Button 類型 var button = sender as Button; + + // 檢查按鈕是否為 null,並確保該按鈕的 Tag 屬性不為 null if (button != null && button.Tag != null) { + // 確保輸入框 (inputBoxPinYinSongs) 是可見狀態 if (inputBoxPinYinSongs.Visible) { + // 將按鈕的標籤 (Tag) 轉換為字串,並附加到輸入框的文字內容中 inputBoxPinYinSongs.Text += button.Tag.ToString(); } } @@ -79,235 +103,321 @@ namespace DualScreenDemo private void InitializeButtonsForPinYinSongs() { + // 初始化拼音字母按鈕,根據 QWERTY 鍵盤佈局建立對應的按鈕 InitializeLetterButtonsForPinYinSongs(); + + // 初始化特殊功能按鈕,包括修改、清除和關閉按鈕 InitializeSpecialButtonsForPinYinSongs(); + + // 初始化拼音輸入框,讓使用者可以輸入拼音來搜尋歌曲 InitializeInputBoxPinYinSongs(); } private void InitializeSpecialButtonsForPinYinSongs() { + // 初始化「修改」按鈕,讓使用者可以刪除輸入框中的最後一個字母 InitializeModifyButtonPinYinSongs(); - + // 初始化「清除」按鈕,讓使用者可以清空輸入框的內容 InitializeClearButtonPinYinSongs(); - + // 初始化「關閉」按鈕,讓使用者可以關閉拼音輸入的 UI 元件 InitializeCloseButtonPinYinSongs(); } private void InitializeModifyButtonPinYinSongs() { + // 載入設定檔資料,取得特殊按鈕的相關配置 var data = LoadConfigData(); + + // 從設定檔讀取「修改按鈕」的座標位置與大小 modifyButtonPinYinCoords = LoadSpecialButtonCoordinates(data, "SpecialButtonCoordinates", "modifyButtonPinYinSongs"); + + // 從設定檔讀取「修改按鈕」的不同狀態圖片 (一般、滑鼠懸停、按下) var buttonImages = LoadButtonImages(data, "ModifyButtonImagesPinYin"); + // 創建「修改按鈕」,並綁定點擊事件 modifyButtonPinYinSongs = CreateSpecialButton( - "btnModifyPinYinSongs", - modifyButtonPinYinCoords, - buttonImages.normal, - buttonImages.mouseOver, - buttonImages.mouseDown, - ModifyButtonPinYinSongs_Click + "btnModifyPinYinSongs", // 按鈕名稱 + modifyButtonPinYinCoords, // 設定按鈕的座標與大小 + buttonImages.normal, // 設定按鈕的正常狀態圖片 + buttonImages.mouseOver, // 設定按鈕的滑鼠懸停圖片 + buttonImages.mouseDown, // 設定按鈕的按下狀態圖片 + ModifyButtonPinYinSongs_Click // 綁定按鈕的點擊事件處理函式 ); + } private void ModifyButtonPinYinSongs_Click(object sender, EventArgs e) { + // 檢查 `inputBoxPinYinSongs` 是否已被加入到 `Controls` 集合中 (確保輸入框存在) if (this.Controls.Contains(inputBoxPinYinSongs) && inputBoxPinYinSongs.Text.Length > 0) { + // 若 `inputBoxPinYinSongs` 有內容,刪除最後一個字母 inputBoxPinYinSongs.Text = inputBoxPinYinSongs.Text.Substring(0, inputBoxPinYinSongs.Text.Length - 1); } } private void InitializeClearButtonPinYinSongs() { + // 從設定檔載入資料 var data = LoadConfigData(); + + // 讀取清除按鈕的座標配置 (X, Y, Width, Height) clearButtonPinYinCoords = LoadSpecialButtonCoordinates(data, "SpecialButtonCoordinates", "clearButtonPinYinSongs"); + + // 載入清除按鈕的圖片 (一般狀態、滑鼠懸停、按下時的圖片) var buttonImages = LoadButtonImages(data, "ClearButtonImagesPinYin"); + // 建立「清除按鈕」並指定對應的座標與圖片 clearButtonPinYinSongs = CreateSpecialButton( - "btnClearPinYinSongs", - clearButtonPinYinCoords, - buttonImages.normal, - buttonImages.mouseOver, - buttonImages.mouseDown, - ClearButtonPinYinSongs_Click + "btnClearPinYinSongs", // 按鈕名稱 + clearButtonPinYinCoords, // 按鈕的座標與大小 + buttonImages.normal, // 按鈕的普通狀態圖片 + buttonImages.mouseOver, // 滑鼠懸停時的圖片 + buttonImages.mouseDown, // 滑鼠按下時的圖片 + ClearButtonPinYinSongs_Click // 點擊事件處理函式 ); + } private void ClearButtonPinYinSongs_Click(object sender, EventArgs e) { + // 檢查視窗內是否包含 inputBoxPinYinSongs 控制項,且輸入框內是否有文字 if (this.Controls.Contains(inputBoxPinYinSongs) && inputBoxPinYinSongs.Text.Length > 0) { + // 清空拼音輸入框的內容 inputBoxPinYinSongs.Text = ""; } } private void InitializeCloseButtonPinYinSongs() { + // 讀取設定檔中的按鈕配置數據 var data = LoadConfigData(); + + // 從設定檔中取得「關閉」按鈕的座標 closeButtonPinYinCoords = LoadSpecialButtonCoordinates(data, "SpecialButtonCoordinates", "closeButtonPinYinSongs"); + + // 從設定檔中讀取「關閉」按鈕的圖片 var buttonImages = LoadButtonImages(data, "CloseButtonImagesPinYin"); + // 建立「關閉」按鈕,並設定名稱、座標、按鈕圖片及點擊事件 closeButtonPinYinSongs = CreateSpecialButton( - "btnClosePinYinSongs", - closeButtonPinYinCoords, - buttonImages.normal, - buttonImages.mouseOver, - buttonImages.mouseDown, - CloseButtonPinYinSongs_Click + "btnClosePinYinSongs", // 按鈕名稱 + closeButtonPinYinCoords, // 按鈕座標 (X, Y, Width, Height) + buttonImages.normal, // 按鈕的普通狀態圖片 + buttonImages.mouseOver, // 滑鼠懸停時的圖片 + buttonImages.mouseDown, // 按下時的圖片 + CloseButtonPinYinSongs_Click // 點擊事件處理函式 ); + } private void CloseButtonPinYinSongs_Click(object sender, EventArgs e) { + // 隱藏拼音輸入的背景圖片 (可能是 UI 中的輸入框背景) pictureBoxPinYinSongs.Visible = false; + // 設定拼音輸入框與所有相關按鈕的可見性為 false SetPinYinSongsAndButtonsVisibility(false); + } private void InitializeInputBoxPinYinSongs() { try { + // 創建一個 INI 檔案解析器 var parser = new FileIniDataParser(); - parser.Parser.Configuration.AssigmentSpacer = ""; - parser.Parser.Configuration.CommentString = "#"; - parser.Parser.Configuration.CaseInsensitive = true; - - IniData data; + // 配置解析器的參數 + parser.Parser.Configuration.AssigmentSpacer = ""; // 設定 `=` 兩側沒有空格 + parser.Parser.Configuration.CommentString = "#"; // 使用 `#` 作為註解符號 + parser.Parser.Configuration.CaseInsensitive = true; // 參數名稱不區分大小寫 + + IniData data; // 儲存解析後的 INI 數據 + + // 讀取 `config.ini` 文件,使用 UTF-8 編碼 using (var reader = new StreamReader("config.ini", System.Text.Encoding.UTF8)) { data = parser.ReadData(reader); } - int x = int.Parse(data["InputBoxPinYinSongs"]["X"]); - int y = int.Parse(data["InputBoxPinYinSongs"]["Y"]); - int width = int.Parse(data["InputBoxPinYinSongs"]["Width"]); - int height = int.Parse(data["InputBoxPinYinSongs"]["Height"]); - string fontName = data["InputBoxPinYinSongs"]["FontName"]; - float fontSize = float.Parse(data["InputBoxPinYinSongs"]["FontSize"]); - FontStyle fontStyle = (FontStyle)Enum.Parse(typeof(FontStyle), data["InputBoxPinYinSongs"]["FontStyle"]); - Color foreColor = Color.FromName(data["InputBoxPinYinSongs"]["ForeColor"]); + // 從 INI 檔案讀取拼音輸入框的位置與大小 + int x = int.Parse(data["InputBoxPinYinSongs"]["X"]); // X 座標 + int y = int.Parse(data["InputBoxPinYinSongs"]["Y"]); // Y 座標 + int width = int.Parse(data["InputBoxPinYinSongs"]["Width"]); // 寬度 + int height = int.Parse(data["InputBoxPinYinSongs"]["Height"]); // 高度 + // 讀取字型設定 + string fontName = data["InputBoxPinYinSongs"]["FontName"]; // 字型名稱 + float fontSize = float.Parse(data["InputBoxPinYinSongs"]["FontSize"]); // 字體大小 + FontStyle fontStyle = (FontStyle)Enum.Parse(typeof(FontStyle), data["InputBoxPinYinSongs"]["FontStyle"]); // 字體樣式 + Color foreColor = Color.FromName(data["InputBoxPinYinSongs"]["ForeColor"]); // 文字顏色 + + // 創建拼音輸入框 (`RichTextBox`) inputBoxPinYinSongs = new RichTextBox { - Visible = false, - Name = "inputBoxPinYinSongs", - ForeColor = foreColor, - Font = new Font(fontName, fontSize / 900 * Screen.PrimaryScreen.Bounds.Height, fontStyle) + Visible = false, // 預設為隱藏 + Name = "inputBoxPinYinSongs", // 設定控制項名稱 + ForeColor = foreColor, // 設定文字顏色 + Font = new Font( + fontName, + fontSize / 900 * Screen.PrimaryScreen.Bounds.Height, // 根據螢幕大小調整字體 + fontStyle + ) }; + // 設定輸入框的位置與大小 ResizeAndPositionControl(inputBoxPinYinSongs, x, y, width, height); + // 綁定 `TextChanged` 事件 (當輸入內容改變時觸發搜尋) inputBoxPinYinSongs.TextChanged += (sender, e) => { string searchText = inputBoxPinYinSongs.Text; - var searchResults = allSongs.Where(song => song.PinyinNotation.StartsWith(searchText)).ToList(); - currentPage = 0; - currentSongList = searchResults; - totalPages = (int)Math.Ceiling((double)searchResults.Count / itemsPerPage); + // 根據拼音前綴篩選歌曲 + var searchResults = allSongs.Where(song => song.PinyinNotation.StartsWith(searchText)).ToList(); + + currentPage = 0; // 重置當前頁面索引 + currentSongList = searchResults; // 更新搜尋結果 + totalPages = (int)Math.Ceiling((double)searchResults.Count / itemsPerPage); // 計算總頁數 + + // 更新 UI,顯示搜尋結果 multiPagePanel.currentPageIndex = 0; multiPagePanel.LoadSongs(currentSongList); }; + // 將拼音輸入框加入視窗中 this.Controls.Add(inputBoxPinYinSongs); } catch (Exception ex) { + // 發生錯誤時輸出錯誤訊息 (避免程式崩潰) Console.WriteLine($"An error occurred: {ex.Message}"); } - } + } + // 讀取 PictureBoxPinYinSongs 的座標設定 private (int X, int Y, int Width, int Height) pictureBoxPinYinSongCoords; private void LoadPictureBoxPinYinSongCoordsFromConfig() { + // 創建一個 INI 檔案解析器 var parser = new FileIniDataParser(); + + // 讀取 `config.ini` 文件並解析成 `IniData` 對象 IniData data = parser.ReadFile("config.ini"); + // 取得 `PictureBoxPinYinSongs` 區段的設定值 var coords = data["PictureBoxPinYinSongs"]; + + // 解析 `X`, `Y`, `Width`, `Height`,並存入 `pictureBoxPinYinSongCoords` pictureBoxPinYinSongCoords = ( - int.Parse(coords["X"]), - int.Parse(coords["Y"]), - int.Parse(coords["Width"]), - int.Parse(coords["Height"]) + int.Parse(coords["X"]), // 解析 X 座標 + int.Parse(coords["Y"]), // 解析 Y 座標 + int.Parse(coords["Width"]), // 解析 寬度 + int.Parse(coords["Height"]) // 解析 高度 ); } - + // 顯示拼音歌曲圖片 private void ShowImageOnPictureBoxPinYinSongs(string imagePath) { - - LoadPictureBoxPinYinSongCoordsFromConfig(); + // 從設定檔載入 PictureBox 的座標與大小 + LoadPictureBoxPinYinSongCoordsFromConfig(); - - Bitmap originalImage = new Bitmap(imagePath); + // 使用指定的圖片路徑建立 Bitmap 影像 + Bitmap originalImage = new Bitmap(imagePath); - - Rectangle displayArea = new Rectangle(pictureBoxPinYinSongCoords.X, pictureBoxPinYinSongCoords.Y, pictureBoxPinYinSongCoords.Width, pictureBoxPinYinSongCoords.Height); + // 建立一個矩形,表示 PictureBox 應該顯示的範圍 + Rectangle displayArea = new Rectangle( + pictureBoxPinYinSongCoords.X, // 設定 X 座標 + pictureBoxPinYinSongCoords.Y, // 設定 Y 座標 + pictureBoxPinYinSongCoords.Width, // 設定 寬度 + pictureBoxPinYinSongCoords.Height // 設定 高度 + ); - - pictureBoxPinYinSongs.Image = originalImage; + // 將載入的圖片設定為 `pictureBoxPinYinSongs` 的影像 + pictureBoxPinYinSongs.Image = originalImage; + + // 調整 `PictureBox` 的大小與位置,使其符合 `displayArea` 的設定 + ResizeAndPositionPictureBox( + pictureBoxPinYinSongs, + displayArea.X, + displayArea.Y, + displayArea.Width, + displayArea.Height + ); + + // 顯示 `PictureBox` + pictureBoxPinYinSongs.Visible = true; - - ResizeAndPositionPictureBox(pictureBoxPinYinSongs, displayArea.X, displayArea.Y, displayArea.Width, displayArea.Height); - - pictureBoxPinYinSongs.Visible = true; } - + // 設定拼音模式的 UI 是否可見 private void SetPinYinSongsAndButtonsVisibility(bool isVisible) { + // 定義一個委派 (Action),用於更新 UI 控件的可見性 System.Action action = () => { + // 暫停佈局更新,以防止 UI 閃爍或重繪時出現異常 SuspendLayout(); + // 設定 `pictureBoxPinYinSongs` 的可見性 pictureBoxPinYinSongs.Visible = isVisible; - if (isVisible) pictureBoxPinYinSongs.BringToFront(); + if (isVisible) pictureBoxPinYinSongs.BringToFront(); // 確保顯示時位於最前方 + // 設定所有拼音字母按鈕的可見性 foreach (var button in letterButtonsForPinYinSongs) { button.Visible = isVisible; if (isVisible) button.BringToFront(); } + // 設定 `modifyButtonPinYinSongs` (修改按鈕) 的可見性 if (modifyButtonPinYinSongs != null) { modifyButtonPinYinSongs.Visible = isVisible; if (isVisible) modifyButtonPinYinSongs.BringToFront(); } + // 設定 `clearButtonPinYinSongs` (清除按鈕) 的可見性 if (clearButtonPinYinSongs != null) { clearButtonPinYinSongs.Visible = isVisible; if (isVisible) clearButtonPinYinSongs.BringToFront(); } + // 設定 `closeButtonPinYinSongs` (關閉按鈕) 的可見性 closeButtonPinYinSongs.Visible = isVisible; if (isVisible) closeButtonPinYinSongs.BringToFront(); + // 設定 `inputBoxPinYinSongs` (輸入框) 的可見性 inputBoxPinYinSongs.Visible = isVisible; if (isVisible) inputBoxPinYinSongs.BringToFront(); + // 恢復佈局,允許 UI 更新 ResumeLayout(); PerformLayout(); - + // 刷新 `pictureBoxPinYinSongs`,確保畫面更新 pictureBoxPinYinSongs.Refresh(); + + // 刷新拼音字母按鈕 foreach (var button in letterButtonsForPinYinSongs) { button.Refresh(); } - + // 刷新其他按鈕與輸入框 modifyButtonPinYinSongs.Refresh(); clearButtonPinYinSongs.Refresh(); closeButtonPinYinSongs.Refresh(); inputBoxPinYinSongs.Refresh(); }; + // 如果當前執行緒不是 UI 執行緒,則使用 Invoke 確保執行於 UI 執行緒 if (this.InvokeRequired) { this.Invoke(action); @@ -316,6 +426,7 @@ namespace DualScreenDemo { action(); } + } } } \ No newline at end of file