Fixes to settings

This commit is contained in:
MaddoScientisto 2026-02-04 22:10:16 +01:00
commit fc7175c2f7
6 changed files with 628 additions and 267 deletions

View file

@ -730,6 +730,50 @@ namespace ImageCatalog_2
}
}
private string _bigPhotoSuffix = "";
public string BigPhotoSuffix
{
get => _bigPhotoSuffix;
set
{
_bigPhotoSuffix = value;
NotifyPropertyChanged();
}
}
private bool _addTextToThumbnails;
public bool AddTextToThumbnails
{
get => _addTextToThumbnails;
set
{
_addTextToThumbnails = value;
NotifyPropertyChanged();
}
}
private bool _addRaceTimeToThumbnails;
public bool AddRaceTimeToThumbnails
{
get => _addRaceTimeToThumbnails;
set
{
_addRaceTimeToThumbnails = value;
NotifyPropertyChanged();
}
}
private bool _addNumberAndTimeToThumbnails;
public bool AddNumberAndTimeToThumbnails
{
get => _addNumberAndTimeToThumbnails;
set
{
_addNumberAndTimeToThumbnails = value;
NotifyPropertyChanged();
}
}
private void Test(object parameter)
{
Debug.WriteLine("Yep");

View file

@ -947,6 +947,7 @@ namespace ImageCatalog
//
// TextBox18
//
TextBox18.DataBindings.Add(new Binding("Text", bindingSource1, "TimeLabel", true, DataSourceUpdateMode.OnPropertyChanged));
TextBox18.Location = new Point(502, 384);
TextBox18.Margin = new Padding(6, 8, 6, 8);
TextBox18.Name = "TextBox18";
@ -966,6 +967,7 @@ namespace ImageCatalog
//
// DateTimePicker1
//
DateTimePicker1.DataBindings.Add(new Binding("Value", bindingSource1, "RaceStartDate", true, DataSourceUpdateMode.OnPropertyChanged));
DateTimePicker1.Format = DateTimePickerFormat.Time;
DateTimePicker1.Location = new Point(850, 384);
DateTimePicker1.Margin = new Padding(6, 8, 6, 8);
@ -1154,12 +1156,12 @@ namespace ImageCatalog
//
// TextBox26
//
TextBox26.DataBindings.Add(new Binding("Text", bindingSource1, "BigPhotoSuffix", true, DataSourceUpdateMode.OnPropertyChanged));
TextBox26.Location = new Point(486, 118);
TextBox26.Margin = new Padding(6, 8, 6, 8);
TextBox26.Name = "TextBox26";
TextBox26.Size = new Size(116, 39);
TextBox26.TabIndex = 20;
TextBox26.Text = "TextBox26";
//
// Label37
//
@ -1339,6 +1341,7 @@ namespace ImageCatalog
//
// RadioButton3
//
RadioButton3.DataBindings.Add(new Binding("Checked", bindingSource1, "AddTextToThumbnails", true, DataSourceUpdateMode.OnPropertyChanged));
RadioButton3.AutoSize = true;
RadioButton3.Location = new Point(32, 40);
RadioButton3.Margin = new Padding(6, 8, 6, 8);
@ -1351,6 +1354,7 @@ namespace ImageCatalog
//
// RadioButton7
//
RadioButton7.DataBindings.Add(new Binding("Checked", bindingSource1, "AddNumberAndTimeToThumbnails", true, DataSourceUpdateMode.OnPropertyChanged));
RadioButton7.AutoSize = true;
RadioButton7.Location = new Point(280, 99);
RadioButton7.Margin = new Padding(6, 8, 6, 8);
@ -1387,6 +1391,7 @@ namespace ImageCatalog
//
// RadioButton5
//
RadioButton5.DataBindings.Add(new Binding("Checked", bindingSource1, "AddRaceTimeToThumbnails", true, DataSourceUpdateMode.OnPropertyChanged));
RadioButton5.AutoSize = true;
RadioButton5.Location = new Point(32, 160);
RadioButton5.Margin = new Padding(6, 8, 6, 8);

View file

@ -115,31 +115,26 @@ public partial class MainForm
private void SetDefaults()
{
// Model defaults are already set in DataModel constructor, just bind ComboBoxes
// Bind ComboBoxes to Model using proper data binding
ComboBox1.DataSource = new List<string>(Model.VerticalPositions);
ComboBox1.SelectedItem = Model.VerticalPosition;
ComboBox1.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.VerticalPosition),
false, DataSourceUpdateMode.OnPropertyChanged));
ComboBox2.DataSource = new List<string>(Model.HorizontalAlignments);
ComboBox2.SelectedItem = Model.HorizontalAlignment;
ComboBox2.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.HorizontalAlignment),
false, DataSourceUpdateMode.OnPropertyChanged));
ComboBox3.DataSource = new List<string>(Model.AvailableFonts);
ComboBox3.SelectedItem = Model.FontName;
ComboBox3.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.FontName),
false, DataSourceUpdateMode.OnPropertyChanged));
ComboBox4.DataSource = new List<string>(Model.HorizontalAlignments);
ComboBox4.SelectedItem = Model.LogoHorizontalPosition;
ComboBox4.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoHorizontalPosition),
false, DataSourceUpdateMode.OnPropertyChanged));
ComboBox5.DataSource = new List<string> { "Alto", "Centro", "Basso" };
ComboBox5.SelectedItem = Model.LogoVerticalPosition;
}
private void RefreshComboBoxSelections()
{
// Update ComboBox selections to reflect loaded Model values
ComboBox1.SelectedItem = Model.VerticalPosition;
ComboBox2.SelectedItem = Model.HorizontalAlignment;
ComboBox3.SelectedItem = Model.FontName;
ComboBox4.SelectedItem = Model.LogoHorizontalPosition;
ComboBox5.SelectedItem = Model.LogoVerticalPosition;
ComboBox5.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoVerticalPosition),
false, DataSourceUpdateMode.OnPropertyChanged));
}
@ -289,10 +284,12 @@ public partial class MainForm
if (openDialog.ShowDialog() != DialogResult.OK) return;
try
{
await Model.LoadSettingsFromFileAsync(openDialog.FileName);
// Refresh ComboBox selections to reflect loaded settings
RefreshComboBoxSelections();
// Explicitly ensure UI is enabled after loading
Model.UiEnabled = true;
// Update logo preview if logo file exists
if (File.Exists(Model.LogoFile))
@ -301,6 +298,14 @@ public partial class MainForm
}
Text = "Image Catalog - " + Path.GetFileName(openDialog.FileName);
_logger.LogInformation($"Settings loaded successfully from {openDialog.FileName}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading settings");
MessageBox.Show($"Error loading settings: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void OnSelectColorRequested(object sender, EventArgs e)
@ -380,6 +385,7 @@ public partial class MainForm
_picSettings.LarghezzaBig = Model.PhotoBigWidth;
_picSettings.FotoGrandeDimOrigina = Model.KeepOriginalDimensions;
_picSettings.JpegQuality = Model.JpegQuality;
_picSettings.Codice = Model.BigPhotoSuffix;
// Logo settings from Model
_picSettings.LogoAggiungi = Model.AddLogo;
@ -414,10 +420,10 @@ public partial class MainForm
_picSettings.TestoOrario = Model.TimeLabel;
_picSettings.TestoMin = Model.ShowFileNameOnThumbnails;
// Controls that still need binding (TODO: move to Model)
_picSettings.AggiungiScritteMiniature = RadioButton3.Checked;
_picSettings.AggTempoGaraMin = RadioButton5.Checked;
_picSettings.AggNumTempMin = RadioButton7.Checked;
// Thumbnail text options from Model
_picSettings.AggiungiScritteMiniature = Model.AddTextToThumbnails;
_picSettings.AggTempoGaraMin = Model.AddRaceTimeToThumbnails;
_picSettings.AggNumTempMin = Model.AddNumberAndTimeToThumbnails;
}
private void setLabel18Text(string text)

View file

@ -0,0 +1,231 @@
using System;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
namespace ImageCatalog_2.Models
{
/// <summary>
/// Data Transfer Object for application settings.
/// Uses attributes to control serialization behavior for both XML (legacy) and JSON (future).
/// </summary>
public class SettingsDto
{
// Paths
[JsonPropertyName("SourcePath")]
[XmlElement("DirSorgente")]
public string SourcePath { get; set; }
[JsonPropertyName("DestinationPath")]
[XmlElement("DirDestinazione")]
public string DestinationPath { get; set; }
// Thumbnails
[JsonPropertyName("CreateThumbnails")]
[XmlElement("MiniatureCrea")]
public bool CreateThumbnails { get; set; } = true;
[JsonPropertyName("ThumbnailPrefix")]
[XmlElement("MiniatureSuffisso")]
public string ThumbnailPrefix { get; set; } = "tn_";
[JsonPropertyName("ThumbnailHeight")]
[XmlElement("MiniatureAltezza")]
public int ThumbnailHeight { get; set; } = 350;
[JsonPropertyName("ThumbnailWidth")]
[XmlElement("MiniatureLarghezza")]
public int ThumbnailWidth { get; set; } = 350;
[JsonPropertyName("FontSizeThumbnail")]
[XmlElement("FontDimensioneMiniatura")]
public int FontSizeThumbnail { get; set; } = 50;
[JsonPropertyName("JpegQualityThumbnail")]
[XmlElement("CompressioneJpegMiniatura")]
public int JpegQualityThumbnail { get; set; } = 30;
[JsonPropertyName("AddTimeToThumbnails")]
[XmlElement("MiniatureAddOrario")]
public bool AddTimeToThumbnails { get; set; }
[JsonPropertyName("ShowFileNameOnThumbnails")]
[XmlElement("NomeMiniatura")]
public bool ShowFileNameOnThumbnails { get; set; }
[JsonPropertyName("AddTextToThumbnails")]
[XmlElement("MiniatureAddScritta")]
public bool AddTextToThumbnails { get; set; }
[JsonPropertyName("AddRaceTimeToThumbnails")]
[XmlElement("TempoSmall")]
public bool AddRaceTimeToThumbnails { get; set; }
[JsonPropertyName("AddNumberAndTimeToThumbnails")]
[XmlElement("NumTempoSmall")]
public bool AddNumberAndTimeToThumbnails { get; set; }
// Big photo
[JsonPropertyName("BigPhotoSuffix")]
[XmlElement("FotoCodice")]
public string BigPhotoSuffix { get; set; } = "";
[JsonPropertyName("PhotoBigHeight")]
[XmlElement("FotoAltezza")]
public int PhotoBigHeight { get; set; } = 2240;
[JsonPropertyName("PhotoBigWidth")]
[XmlElement("FotoLarghezza")]
public int PhotoBigWidth { get; set; } = 2240;
[JsonPropertyName("KeepOriginalDimensions")]
[XmlElement("FotoDimOriginali")]
public bool KeepOriginalDimensions { get; set; }
[JsonPropertyName("JpegQuality")]
[XmlElement("CompressioneJpeg")]
public int JpegQuality { get; set; } = 85;
// Font
[JsonPropertyName("FontSize")]
[XmlElement("FontDimensione")]
public int FontSize { get; set; } = 20;
[JsonPropertyName("FontName")]
[XmlElement("FontNome")]
public string FontName { get; set; } = "Arial";
[JsonPropertyName("FontBold")]
[XmlElement("FontBold")]
public bool FontBold { get; set; }
[JsonPropertyName("TextColorRGB")]
[XmlElement("ColoreTestoRGB")]
public string TextColorRGB { get; set; } = "Yellow";
// Text
[JsonPropertyName("HorizontalText")]
[XmlElement("TestoTesto")]
public string HorizontalText { get; set; }
[JsonPropertyName("VerticalText")]
[XmlElement("TestoVerticale")]
public string VerticalText { get; set; }
[JsonPropertyName("TextTransparency")]
[XmlElement("TestoTrasparente")]
public int TextTransparency { get; set; }
[JsonPropertyName("TextMargin")]
[XmlElement("TestoMargine")]
public int TextMargin { get; set; } = 8;
[JsonPropertyName("VerticalPosition")]
[XmlElement("TestoPosizione")]
public string VerticalPosition { get; set; } = "Basso";
[JsonPropertyName("HorizontalAlignment")]
[XmlElement("TestoAllineamento")]
public string HorizontalAlignment { get; set; } = "Centro";
[JsonPropertyName("VerticalTextSize")]
[XmlElement("GrandezzaVerticale")]
public int VerticalTextSize { get; set; } = 20;
[JsonPropertyName("VerticalTextMargin")]
[XmlElement("MargineVerticale")]
public int VerticalTextMargin { get; set; } = 6;
// Logo/Watermark
[JsonPropertyName("LogoFile")]
[XmlElement("MarchioFile")]
public string LogoFile { get; set; } = "";
[JsonPropertyName("LogoHeight")]
[XmlElement("MarchioAltezza")]
public int LogoHeight { get; set; } = 430;
[JsonPropertyName("LogoWidth")]
[XmlElement("MarchioLarghezza")]
public int LogoWidth { get; set; } = 430;
[JsonPropertyName("LogoMargin")]
[XmlElement("MarchioMargine")]
public int LogoMargin { get; set; } = 290;
[JsonPropertyName("LogoHorizontalPosition")]
[XmlElement("MarchioAllOrizzontale")]
public string LogoHorizontalPosition { get; set; } = "Destra";
[JsonPropertyName("LogoVerticalPosition")]
[XmlElement("MarchioAllVerticale")]
public string LogoVerticalPosition { get; set; } = "Basso";
[JsonPropertyName("LogoTransparency")]
[XmlElement("MarchioTrasparenza")]
public int LogoTransparency { get; set; } = 100;
[JsonPropertyName("AddLogo")]
[XmlElement("MarchioAggiungi")]
public bool AddLogo { get; set; }
// Options
[JsonPropertyName("ForceJpeg")]
[XmlElement("GeneraleForzaJpg")]
public bool ForceJpeg { get; set; }
[JsonPropertyName("AutomaticRotation")]
[XmlElement("GeneraleRotazioneAutomatica")]
public bool AutomaticRotation { get; set; }
[JsonPropertyName("AddRaceTime")]
[XmlElement("TempoGara")]
public bool AddRaceTime { get; set; }
[JsonPropertyName("AddTime")]
[XmlElement("Orario")]
public bool AddTime { get; set; }
[JsonPropertyName("TimeLabel")]
[XmlElement("EtichettaOrario")]
public string TimeLabel { get; set; } = "";
[JsonPropertyName("ShowDate")]
[XmlElement("DataFoto")]
public bool ShowDate { get; set; }
[JsonPropertyName("ShowPhotoNumber")]
[XmlElement("NumeroFoto")]
public bool ShowPhotoNumber { get; set; }
[JsonPropertyName("OverwriteImages")]
[XmlElement("GeneraleSovrascriviFile")]
public bool OverwriteImages { get; set; }
// Folder division
[JsonPropertyName("FilesPerFolder")]
[XmlElement("DirDividiNumFile")]
public int FilesPerFolder { get; set; } = 99;
[JsonPropertyName("FolderSuffix")]
[XmlElement("DirDividiSuffisso")]
public string FolderSuffix { get; set; } = "";
[JsonPropertyName("CounterDigits")]
[XmlElement("DirDividiNumCifre")]
public int CounterDigits { get; set; } = 2;
// Processing
[JsonPropertyName("ChunkSize")]
[XmlElement("ChunkSize")]
public int ChunkSize { get; set; }
[JsonPropertyName("ThreadsCount")]
[XmlElement("ThreadsCount")]
public int ThreadsCount { get; set; }
// Race date
[JsonPropertyName("RaceStartDate")]
[XmlElement("DataPartenza")]
public DateTime RaceStartDate { get; set; } = DateTime.Now;
}
}

View file

@ -1,16 +1,35 @@
using System;
using System.Data;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Extensions.Logging;
namespace ImageCatalog
namespace ImageCatalog;
/// <summary>
/// Modern parameter storage service using XML for persistence.
/// Thread-safe key-value store for application settings.
/// </summary>
public class ParametriSetup
{
public class ParametriSetup
{
private DataSet _elencoParametri;
public string NomeFileSetup { get; set; }
private readonly object _lock = new();
private readonly ILogger<ParametriSetup> _logger;
private Dictionary<string, string> _parameters = new(StringComparer.OrdinalIgnoreCase);
public ParametriSetup(string fileSetup)
/// <summary>
/// Gets or sets the XML file path for settings storage.
/// </summary>
public string? NomeFileSetup { get; set; }
/// <summary>
/// Initializes a new instance with the specified settings file.
/// </summary>
/// <param name="fileSetup">Path to the XML settings file.</param>
/// <param name="logger">Optional logger for diagnostics.</param>
public ParametriSetup(string? fileSetup = null, ILogger<ParametriSetup>? logger = null)
{
_elencoParametri = new DataSet();
_logger = logger ?? Microsoft.Extensions.Logging.Abstractions.NullLogger<ParametriSetup>.Instance;
NomeFileSetup = fileSetup;
if (!string.IsNullOrWhiteSpace(fileSetup))
{
@ -18,222 +37,207 @@ namespace ImageCatalog
}
}
public ParametriSetup() : this(string.Empty)
{
}
/// <summary>
/// Loads settings from the XML file.
/// </summary>
public void CaricaParametriSetup()
{
lock (_lock)
{
if (string.IsNullOrEmpty(NomeFileSetup) || !File.Exists(NomeFileSetup))
{
_elencoParametri = new DataSet();
_parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
return;
}
_elencoParametri = LeggiXmlDataSet("Setup", NomeFileSetup, "Nome");
try
{
var doc = XDocument.Load(NomeFileSetup);
var setupElements = doc.Descendants("Setup");
_parameters = setupElements
.Where(e => e.Element("Nome") != null)
.ToDictionary(
e => e.Element("Nome")!.Value,
e => e.Element("Valore")?.Value ?? string.Empty,
StringComparer.OrdinalIgnoreCase
);
_logger.LogInformation("Loaded {Count} parameters from {FilePath}", _parameters.Count, NomeFileSetup);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load settings from {FilePath}", NomeFileSetup);
_parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
}
/// <summary>
/// Saves settings to the XML file.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when NomeFileSetup is not set.</exception>
public void SalvaParametriSetup()
{
if (string.IsNullOrWhiteSpace(NomeFileSetup))
throw new InvalidOperationException("NomeFileSetup is not set.");
// overwrite without FileSystem.Kill
_elencoParametri.WriteXml(NomeFileSetup, XmlWriteMode.WriteSchema);
lock (_lock)
{
try
{
// Ensure directory exists
var directory = Path.GetDirectoryName(NomeFileSetup);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// public string LeggiParametroString(string NomeParametro)
// {
// string Risposta = "";
// try
// {
// var LElenco = _elencoParametri.Tables["Setup"].Select("Nome='" + NomeParametro + "'");
// foreach (var LaRiga in LElenco)
// Risposta = LaRiga["Valore"].ToString();
// }
// catch
// {
// Risposta = "";
// }
//
// return Risposta;
// }
//
// public bool LeggiParametroBoolean(string nomeParametro)
// {
// var risposta = "";
// try
// {
// var lElenco = _elencoParametri.Tables["Setup"].Select("Nome='" + nomeParametro + "'");
// foreach (var laRiga in lElenco)
// risposta = laRiga["Valore"].ToString();
// }
// catch
// {
// risposta = "";
// }
//
// switch (risposta.ToUpper() ?? "")
// {
// case "TRUE":
// case "OK":
// case "SI":
// case "1":
// case "YES":
// case "VERO":
// {
// return true;
// }
//
// default:
// {
// return false;
// }
// }
// }
// Create XML document with DataSet-compatible structure
var doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("NewDataSet",
_parameters.Select(kvp =>
new XElement("Setup",
new XElement("Nome", kvp.Key),
new XElement("Valore", kvp.Value)
)
)
)
);
doc.Save(NomeFileSetup);
_logger.LogInformation("Saved {Count} parameters to {FilePath}", _parameters.Count, NomeFileSetup);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to save settings to {FilePath}", NomeFileSetup);
throw;
}
}
}
/// <summary>
/// Reads a parameter as a string.
/// </summary>
/// <param name="nomeParametro">Parameter name.</param>
/// <returns>Parameter value or empty string if not found.</returns>
public string LeggiParametroString(string nomeParametro)
{
return GetRow(nomeParametro)?["Valore"]?.ToString() ?? string.Empty;
lock (_lock)
{
return _parameters.TryGetValue(nomeParametro, out var value) ? value : string.Empty;
}
}
/// <summary>
/// Reads a parameter as a boolean.
/// </summary>
/// <param name="nomeParametro">Parameter name.</param>
/// <returns>True if value is truthy (TRUE, OK, SI, 1, YES, VERO), false otherwise.</returns>
public bool LeggiParametroBoolean(string nomeParametro)
{
var raw = LeggiParametroString(nomeParametro);
return raw?.ToUpperInvariant() switch
return raw.ToUpperInvariant() switch
{
"TRUE" or "OK" or "SI" or "1" or "YES" or "VERO" => true,
_ => false
};
}
public void AggiornaParametro(string nomeParametro, object valoreParametro)
/// <summary>
/// Updates or creates a parameter.
/// </summary>
/// <param name="nomeParametro">Parameter name.</param>
/// <param name="valoreParametro">Parameter value.</param>
public void AggiornaParametro(string nomeParametro, object? valoreParametro)
{
var table = EnsureSetupTable();
var rows = table.Select($"Nome='{nomeParametro.Replace("'", "''")}'");
if (rows.Length == 0)
lock (_lock)
{
var newRow = table.NewRow();
newRow["Nome"] = nomeParametro;
newRow["Valore"] = valoreParametro?.ToString() ?? string.Empty;
table.Rows.Add(newRow);
}
else
{
rows[0]["Valore"] = valoreParametro?.ToString() ?? string.Empty;
_parameters[nomeParametro] = valoreParametro?.ToString() ?? string.Empty;
}
}
/// <summary>
/// Reads a parameter and converts it to the specified type.
/// </summary>
/// <typeparam name="T">Target type.</typeparam>
/// <param name="nomeParametro">Parameter name.</param>
/// <param name="defaultValue">Default value if conversion fails.</param>
/// <returns>Converted value or default value.</returns>
public T LeggiParametro<T>(string nomeParametro, T defaultValue = default!)
{
var raw = LeggiParametroString(nomeParametro);
if (string.IsNullOrEmpty(raw)) return defaultValue;
if (string.IsNullOrEmpty(raw))
return defaultValue;
try
{
return (T)Convert.ChangeType(raw, typeof(T));
}
catch
// Handle nullable types
var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
// Special handling for common types
if (targetType == typeof(bool))
{
return (T)(object)LeggiParametroBoolean(nomeParametro);
}
return (T)Convert.ChangeType(raw, targetType, CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to convert parameter {ParameterName} to {TypeName}, using default value",
nomeParametro, typeof(T).Name);
return defaultValue;
}
}
private static DataSet LeggiXmlDataSet(string nomeTabella, string nomeFileXml, string nomeColonnaChiave = "")
/// <summary>
/// Checks if a parameter exists.
/// </summary>
/// <param name="nomeParametro">Parameter name.</param>
/// <returns>True if parameter exists, false otherwise.</returns>
public bool ParametroExists(string nomeParametro)
{
var ds = new DataSet();
ds.ReadXml(nomeFileXml);
if (!string.IsNullOrEmpty(nomeColonnaChiave) && ds.Tables.Contains(nomeTabella))
lock (_lock)
{
var table = ds.Tables[nomeTabella];
if (table.Constraints[nomeColonnaChiave] == null)
return _parameters.ContainsKey(nomeParametro);
}
}
/// <summary>
/// Removes a parameter.
/// </summary>
/// <param name="nomeParametro">Parameter name.</param>
/// <returns>True if parameter was removed, false if it didn't exist.</returns>
public bool RimuoviParametro(string nomeParametro)
{
table.Constraints.Add(nomeColonnaChiave, table.Columns[nomeColonnaChiave], true);
}
}
return ds;
}
private DataTable EnsureSetupTable()
lock (_lock)
{
if (!_elencoParametri.Tables.Contains("Setup"))
return _parameters.Remove(nomeParametro);
}
}
/// <summary>
/// Gets all parameter names.
/// </summary>
/// <returns>Collection of parameter names.</returns>
public IReadOnlyCollection<string> GetParameterNames()
{
var table = new DataTable("Setup");
table.Columns.Add("Nome", typeof(string));
table.Columns.Add("Valore", typeof(string));
_elencoParametri.Tables.Add(table);
}
return _elencoParametri.Tables["Setup"];
}
private DataRow? GetRow(string nomeParametro)
lock (_lock)
{
if (!_elencoParametri.Tables.Contains("Setup")) return null;
var rows = _elencoParametri.Tables["Setup"]
.Select($"Nome='{nomeParametro.Replace("'", "''")}'");
return rows.FirstOrDefault();
return _parameters.Keys.ToList();
}
}
// public void AggiornaParametro(string NomeParametro, object ValoreParametro)
// {
// try
// {
// if (_elencoParametri.Tables["Setup"] is null)
// {
// var TabellaTmp = new DataTable("Setup");
// DataRow RigaTmp;
// DataColumn LaColonna;
// LaColonna = TabellaTmp.Columns.Add("Nome", Type.GetType("System.String"));
// LaColonna = TabellaTmp.Columns.Add("Valore", Type.GetType("System.String"));
//
// // * Aggiunge alla tabella tutte le righe
// RigaTmp = TabellaTmp.NewRow();
// RigaTmp["Nome"] = NomeParametro;
// RigaTmp["Valore"] = ValoreParametro;
// TabellaTmp.Rows.Add(RigaTmp);
// _elencoParametri.Tables.Add(TabellaTmp);
// }
// else
// {
// var LElenco = _elencoParametri.Tables["Setup"].Select("Nome='" + NomeParametro + "'");
// if (LElenco.Length == 0)
// {
// DataRow LaRiga;
// LaRiga = _elencoParametri.Tables["Setup"].NewRow();
// LaRiga["Nome"] = NomeParametro;
// LaRiga["Valore"] = ValoreParametro;
// _elencoParametri.Tables["Setup"].Rows.Add(LaRiga);
// }
// else
// {
// LElenco[0]["Valore"] = ValoreParametro;
// }
// }
// }
// catch
// {
// }
// }
// private static DataSet LeggiXmlDataSet(string NomeTabella, string NomeFileXml, string NomeColonnaChiave = "")
// {
// // * Crea e Legge il dataset dal file xml
// var DataSetXml = new DataSet();
// DataSetXml.ReadXml(NomeFileXml);
//
// // * Aggiunge il campo chiave
// if (!string.IsNullOrEmpty(NomeColonnaChiave))
// {
// DataSetXml.Tables[NomeTabella].Constraints.Add(NomeColonnaChiave, DataSetXml.Tables[NomeTabella].Columns[NomeColonnaChiave], true);
// }
//
// // * Restituisce la risposta
// return DataSetXml;
// }
//}
/// <summary>
/// Clears all parameters.
/// </summary>
public void ClearAll()
{
lock (_lock)
{
_parameters.Clear();
}
}
}

View file

@ -1,22 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml.Serialization;
using ImageCatalog;
using ImageCatalog_2.Models;
using Microsoft.Extensions.Logging;
namespace ImageCatalog_2.Services
{
/// <summary>
/// Modern settings service that uses reflection to automatically save/load all properties
/// Modern settings service that uses DTO with attributes for serialization
/// </summary>
public class SettingsService : ISettingsService
{
private readonly ParametriSetup _parametriSetup;
private readonly ILogger<SettingsService> _logger;
public SettingsService(ParametriSetup parametriSetup)
public SettingsService(ParametriSetup parametriSetup, ILogger<SettingsService> logger)
{
_parametriSetup = parametriSetup;
_logger = logger;
}
public Task SaveSettingsAsync(string filePath, object settings)
@ -25,85 +28,153 @@ namespace ImageCatalog_2.Services
{
_parametriSetup.NomeFileSetup = filePath;
// Use reflection to get all properties and save them
var properties = settings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && IsSerializableType(p.PropertyType));
// Convert ViewModel to DTO
var dto = ViewModelToDto(settings);
// Use reflection on DTO properties with XmlElement attribute
var properties = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
var value = prop.GetValue(settings);
_parametriSetup.AggiornaParametro(prop.Name, value);
// Get the XmlElement attribute to determine the XML property name
var xmlAttr = prop.GetCustomAttribute<XmlElementAttribute>();
if (xmlAttr == null)
continue; // Skip properties without XmlElement attribute
var xmlName = xmlAttr.ElementName;
var value = prop.GetValue(dto);
_parametriSetup.AggiornaParametro(xmlName, value);
}
_parametriSetup.SalvaParametriSetup();
});
}
public Task LoadSettingsAsync(string filePath, object settings)
public async Task LoadSettingsAsync(string filePath, object settings)
{
return Task.Run(() =>
// Step 1: Load XML and read into DTO on background thread
var dto = await Task.Run(() =>
{
_parametriSetup.NomeFileSetup = filePath;
_parametriSetup.CaricaParametriSetup();
// Use reflection to get all properties and load them
var properties = settings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite && IsSerializableType(p.PropertyType));
var loadedDto = new SettingsDto();
var properties = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
// Get the XmlElement attribute to determine the XML property name
var xmlAttr = prop.GetCustomAttribute<XmlElementAttribute>();
if (xmlAttr == null)
continue; // Skip properties without XmlElement attribute
var xmlName = xmlAttr.ElementName;
try
{
object value;
if (prop.PropertyType == typeof(string))
{
value = _parametriSetup.LeggiParametroString(prop.Name);
// Don't update if empty string and current value is not empty
if (string.IsNullOrEmpty((string)value))
{
var currentValue = prop.GetValue(settings) as string;
if (!string.IsNullOrEmpty(currentValue))
{
continue; // Skip if no value in settings but there's a default
}
}
value = _parametriSetup.LeggiParametroString(xmlName);
}
else if (prop.PropertyType == typeof(bool))
{
value = _parametriSetup.LeggiParametroBoolean(prop.Name);
value = _parametriSetup.LeggiParametroBoolean(xmlName);
}
else if (prop.PropertyType == typeof(int))
{
value = _parametriSetup.LeggiParametro<int>(prop.Name, (int)(prop.GetValue(settings) ?? 0));
value = _parametriSetup.LeggiParametro<int>(xmlName, (int)(prop.GetValue(loadedDto) ?? 0));
}
else if (prop.PropertyType == typeof(double))
{
value = _parametriSetup.LeggiParametro<double>(prop.Name, (double)(prop.GetValue(settings) ?? 0.0));
value = _parametriSetup.LeggiParametro<double>(xmlName, (double)(prop.GetValue(loadedDto) ?? 0.0));
}
else if (prop.PropertyType == typeof(DateTime))
{
var strValue = _parametriSetup.LeggiParametroString(xmlName);
if (DateTime.TryParse(strValue, out var dateValue))
{
value = dateValue;
}
else
{
continue; // Skip unsupported types
value = (DateTime)(prop.GetValue(loadedDto) ?? DateTime.Now);
}
}
else
{
continue;
}
prop.SetValue(settings, value);
prop.SetValue(loadedDto, value);
}
catch (Exception ex)
{
// Skip properties that can't be loaded
System.Diagnostics.Debug.WriteLine($"Failed to load property {prop.Name}: {ex.Message}");
_logger.LogError(ex, "Failed to read property {XmlName} -> {PropertyName}", xmlName, prop.Name);
}
}
});
}
private bool IsSerializableType(Type type)
return loadedDto;
});
// Step 2: Apply DTO values to ViewModel on UI thread
DtoToViewModel(dto, settings);
}
/// <summary>
/// Converts DataModel (ViewModel) to SettingsDto
/// </summary>
private SettingsDto ViewModelToDto(object viewModel)
{
return type == typeof(string) ||
type == typeof(int) ||
type == typeof(bool) ||
type == typeof(double) ||
type == typeof(float) ||
type == typeof(decimal);
var vmType = viewModel.GetType();
var dto = new SettingsDto();
var dtoProps = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var dtoProp in dtoProps)
{
// Find matching property in ViewModel by name
var vmProp = vmType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance);
if (vmProp != null && vmProp.CanRead)
{
var value = vmProp.GetValue(viewModel);
dtoProp.SetValue(dto, value);
}
}
return dto;
}
/// <summary>
/// Copies values from SettingsDto to DataModel (ViewModel)
/// </summary>
private void DtoToViewModel(SettingsDto dto, object viewModel)
{
var vmType = viewModel.GetType();
var dtoProps = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var dtoProp in dtoProps)
{
// Find matching property in ViewModel by name
var vmProp = vmType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance);
if (vmProp != null && vmProp.CanWrite)
{
var value = dtoProp.GetValue(dto);
// Don't update if empty string and current value is not empty
if (dtoProp.PropertyType == typeof(string) && string.IsNullOrEmpty((string)value))
{
var currentValue = vmProp.GetValue(viewModel) as string;
if (!string.IsNullOrEmpty(currentValue))
{
continue;
}
}
vmProp.SetValue(viewModel, value);
_logger.LogDebug("Set {PropertyName} = {Value}", vmProp.Name, value);
}
}
}
}
}