diff --git a/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/UserSettings.cs b/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/UserSettings.cs
new file mode 100644
index 0000000..910c10a
--- /dev/null
+++ b/HorseRacingPredictor/HorseRacingPredictor/HorseRacingPredictor/UserSettings.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace HorseRacingPredictor
+{
+ ///
+ /// User-editable preferences persisted as JSON.
+ /// Replaces the legacy settings.ini key=value format.
+ ///
+ internal sealed class UserSettings
+ {
+ private static readonly string FilePath =
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "usersettings.json");
+
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ };
+
+ // ?? Football ????????????????????????????????????????????
+ public string ApiKey { get; set; } = string.Empty;
+ public string FbExportPath { get; set; } = string.Empty;
+ public string FbPrefix { get; set; } = string.Empty;
+ public string FbSuffix { get; set; } = string.Empty;
+ public bool FbIncludeDate { get; set; } = true;
+ public string FbDateFormat { get; set; } = "yyyy-MM-dd";
+ public string FbFormat { get; set; } = "CSV";
+
+ // ?? Racing ??????????????????????????????????????????????
+ public string RacingApiKey { get; set; } = string.Empty;
+ public string RcExportPath { get; set; } = string.Empty;
+ public string RcPrefix { get; set; } = string.Empty;
+ public string RcSuffix { get; set; } = string.Empty;
+ public bool RcIncludeDate { get; set; } = true;
+ public string RcDateFormat { get; set; } = "yyyy-MM-dd";
+ public string RcFormat { get; set; } = "CSV";
+ public string RcTimezone { get; set; } = "Australia/Sydney";
+ public List RcCountries { get; set; } = new() { "au", "nz" };
+
+ // ?? Persistence ?????????????????????????????????????????
+
+ public static UserSettings Load()
+ {
+ try
+ {
+ if (!File.Exists(FilePath))
+ return MigrateFromIniOrDefault();
+
+ var json = File.ReadAllText(FilePath);
+ return JsonSerializer.Deserialize(json, JsonOptions) ?? new UserSettings();
+ }
+ catch
+ {
+ return new UserSettings();
+ }
+ }
+
+ public void Save()
+ {
+ var json = JsonSerializer.Serialize(this, JsonOptions);
+ File.WriteAllText(FilePath, json);
+ }
+
+ ///
+ /// One-time migration: reads old settings.ini if present, converts to UserSettings,
+ /// saves the new usersettings.json, then deletes the ini file.
+ ///
+ private static UserSettings MigrateFromIniOrDefault()
+ {
+ var iniPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.ini");
+ if (!File.Exists(iniPath))
+ return new UserSettings();
+
+ var settings = new UserSettings();
+ try
+ {
+ foreach (var line in File.ReadAllLines(iniPath))
+ {
+ var idx = line.IndexOf('=');
+ if (idx < 0) continue;
+ var key = line[..idx].Trim();
+ var val = line[(idx + 1)..].Trim();
+
+ switch (key)
+ {
+ case "ApiKey": settings.ApiKey = val; break;
+ case "FbExportPath": settings.FbExportPath = val; break;
+ case "FbPrefix": settings.FbPrefix = val; break;
+ case "FbSuffix": settings.FbSuffix = val; break;
+ case "FbIncludeDate": settings.FbIncludeDate = val is "1" or "true" or "True"; break;
+ case "FbDateFormat": settings.FbDateFormat = val; break;
+ case "FbFormat": settings.FbFormat = val; break;
+ case "RcExportPath": settings.RcExportPath = val; break;
+ case "RcPrefix": settings.RcPrefix = val; break;
+ case "RcSuffix": settings.RcSuffix = val; break;
+ case "RcIncludeDate": settings.RcIncludeDate = val is "1" or "true" or "True"; break;
+ case "RcDateFormat": settings.RcDateFormat = val; break;
+ case "RcFormat": settings.RcFormat = val; break;
+ case "RacingApiKey": settings.RacingApiKey = val; break;
+ case "RcTimezone": settings.RcTimezone = val; break;
+ case "RcCountries":
+ settings.RcCountries = new List(
+ val.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries));
+ break;
+ }
+ }
+
+ // Persist as JSON and remove legacy file
+ settings.Save();
+ File.Delete(iniPath);
+ }
+ catch
+ {
+ // If migration fails, return whatever we parsed
+ }
+
+ return settings;
+ }
+ }
+}
diff --git a/HorseRacingPredictor/HorseRacingPredictor/MainWindow.xaml.cs b/HorseRacingPredictor/HorseRacingPredictor/MainWindow.xaml.cs
index b473ff1..b925c73 100644
--- a/HorseRacingPredictor/HorseRacingPredictor/MainWindow.xaml.cs
+++ b/HorseRacingPredictor/HorseRacingPredictor/MainWindow.xaml.cs
@@ -831,54 +831,35 @@ namespace HorseRacingPredictor
return null;
}
- // ???????????????????? SETTINGS ????????????????????
-
- private string SettingsFilePath =>
- Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.ini");
+ // 覧覧覧覧覧覧覧覧覧 SETTINGS 覧覧覧覧覧覧覧覧覧
private void LoadSettings()
{
try
{
- txtRacingApiKey.Text = DefaultRacingApiKey;
+ var s = UserSettings.Load();
- // Default countries
- SetSelectedCountries(new[] { "au", "nz" });
+ txtApiKey.Text = s.ApiKey;
+ txtFbExportPath.Text = s.FbExportPath;
+ txtFbPrefix.Text = s.FbPrefix;
+ txtFbSuffix.Text = s.FbSuffix;
+ chkFbIncludeDate.IsChecked = s.FbIncludeDate;
+ SetComboBoxSelectionByContent(cmbFbDateFormat, s.FbDateFormat);
+ SetComboBoxSelectionByContent(cmbFbFormat, s.FbFormat);
- if (!File.Exists(SettingsFilePath)) { ApplyRacingSettings(); return; }
- foreach (var line in File.ReadAllLines(SettingsFilePath))
- {
- var idx = line.IndexOf('=');
- if (idx < 0) continue;
- var key = line.Substring(0, idx).Trim();
- var val = line.Substring(idx + 1).Trim();
+ txtRcExportPath.Text = s.RcExportPath;
+ txtRcPrefix.Text = s.RcPrefix;
+ txtRcSuffix.Text = s.RcSuffix;
+ chkRcIncludeDate.IsChecked = s.RcIncludeDate;
+ SetComboBoxSelectionByContent(cmbRcDateFormat, s.RcDateFormat);
+ SetComboBoxSelectionByContent(cmbRcFormat, s.RcFormat);
- if (key == "ApiKey") txtApiKey.Text = val;
- else if (key == "FbExportPath") txtFbExportPath.Text = val;
- else if (key == "FbPrefix") txtFbPrefix.Text = val;
- else if (key == "FbSuffix") txtFbSuffix.Text = val;
- else if (key == "FbIncludeDate") chkFbIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
- else if (key == "FbDateFormat") { try { SetComboBoxSelectionByContent(cmbFbDateFormat, val); } catch { } }
- else if (key == "FbFormat") { try { SetComboBoxSelectionByContent(cmbFbFormat, val); } catch { } }
- else if (key == "RcExportPath") txtRcExportPath.Text = val;
- else if (key == "RcPrefix") txtRcPrefix.Text = val;
- else if (key == "RcSuffix") txtRcSuffix.Text = val;
- else if (key == "RcIncludeDate") chkRcIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
- else if (key == "RcDateFormat") { try { SetComboBoxSelectionByContent(cmbRcDateFormat, val); } catch { } }
- else if (key == "RcFormat") { try { SetComboBoxSelectionByContent(cmbRcFormat, val); } catch { } }
- else if (key == "RacingApiKey") txtRacingApiKey.Text = val;
- else if (key == "RcTimezone" && txtRcTimezone != null) txtRcTimezone.Text = val;
- else if (key == "RcCountries")
- {
- var codes = val.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
- SetSelectedCountries(codes);
- }
- }
+ txtRacingApiKey.Text = string.IsNullOrEmpty(s.RacingApiKey) ? DefaultRacingApiKey : s.RacingApiKey;
+ if (txtRcTimezone != null) txtRcTimezone.Text = s.RcTimezone;
+ SetSelectedCountries(s.RcCountries.ToArray());
- // Update preview UI after loading values
UpdateFbPreview();
UpdateRcPreview();
-
ApplyRacingSettings();
}
catch { }
@@ -1110,29 +1091,29 @@ namespace HorseRacingPredictor
{
try
{
- var sb = new StringBuilder();
- sb.AppendLine($"ApiKey={txtApiKey.Text.Trim()}");
- sb.AppendLine($"FbExportPath={txtFbExportPath.Text.Trim()}");
- sb.AppendLine($"FbPrefix={txtFbPrefix.Text.Trim()}");
- sb.AppendLine($"FbSuffix={txtFbSuffix.Text.Trim()}");
- sb.AppendLine($"FbIncludeDate={(chkFbIncludeDate.IsChecked==true?"1":"0")}");
- sb.AppendLine($"FbDateFormat={(cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
- sb.AppendLine($"FbFormat={(cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
- sb.AppendLine($"RcExportPath={txtRcExportPath.Text.Trim()}");
- sb.AppendLine($"RcPrefix={txtRcPrefix.Text.Trim()}");
- sb.AppendLine($"RcSuffix={txtRcSuffix.Text.Trim()}");
- sb.AppendLine($"RcIncludeDate={(chkRcIncludeDate.IsChecked==true?"1":"0")}");
- sb.AppendLine($"RcDateFormat={(cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
- sb.AppendLine($"RcFormat={(cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
- sb.AppendLine($"RacingApiKey={txtRacingApiKey.Text.Trim()}");
- sb.AppendLine($"RcTimezone={txtRcTimezone?.Text?.Trim() ?? "Australia/Sydney"}");
- sb.AppendLine($"RcCountries={string.Join(",", GetSelectedCountries())}");
- File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8);
+ var s = new UserSettings
+ {
+ ApiKey = txtApiKey.Text.Trim(),
+ FbExportPath = txtFbExportPath.Text.Trim(),
+ FbPrefix = txtFbPrefix.Text.Trim(),
+ FbSuffix = txtFbSuffix.Text.Trim(),
+ FbIncludeDate = chkFbIncludeDate.IsChecked == true,
+ FbDateFormat = (cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
+ FbFormat = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
+ RcExportPath = txtRcExportPath.Text.Trim(),
+ RcPrefix = txtRcPrefix.Text.Trim(),
+ RcSuffix = txtRcSuffix.Text.Trim(),
+ RcIncludeDate = chkRcIncludeDate.IsChecked == true,
+ RcDateFormat = (cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
+ RcFormat = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
+ RacingApiKey = txtRacingApiKey.Text.Trim(),
+ RcTimezone = txtRcTimezone?.Text?.Trim() ?? "Australia/Sydney",
+ RcCountries = new List(GetSelectedCountries())
+ };
+ s.Save();
- // update previews after save
UpdateFbPreview();
UpdateRcPreview();
-
ApplyRacingSettings();
MessageBox.Show("Impostazioni salvate con successo.",