Módulo Genérico de Configuração de Parâmetros em C# com Arquivos INI

  1. Design da Janela de Configurações

A interface de configuração deve seguir estas diretrizes para integração com o painel principal da aplicação:

  • Dimensões da jenela devem corresponder exatamente ao tamanho do painel hospedeiro
  • Tipografia e escala devem manter consistência visual com a aplicação principle
  • Remover bordas da janela utilizando FormBorderStyle.None
  1. Estratégia de Gerenciamento de Configurações em Lote

A abordagem utiliza um único arquivo INI com a seguinte organização:

  • Seção: uma única seção denominada "Configuracoes"
  • Chaves: duas chaves principais — "ParametrosBase" e "ParametrosAvancados"
  • Serialização: conversão bidirecional entre objetos C# e strings JSON
  • Reflexão: mapeamento entre nomes de controles na interface e propriedades dos objetos
  • Tag dos controles: utilizada para registrar o histórico de modificações
  1. Implementação do Código

3.1 Biblioteca de Utilitários para Arquivos INI

Esta classe encapsula as chamadas à API do Windows para manipulação de arquivos INI:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace ConfigManager.Utils
{
    public static class IniFileHandler
    {
        private static string _defaultPath = string.Empty;

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, EntryPoint = "WritePrivateProfileString")]
        private static extern long PersistValue(string section, string key, string value, string filePath);

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, EntryPoint = "GetPrivateProfileString")]
        private static extern int FetchValue(string section, string key, string fallback,
            StringBuilder buffer, int bufferSize, string filePath);

        [DllImport("kernel32.dll", EntryPoint = "GetPrivateProfileInt")]
        private static extern int FetchInteger(string section, string key, int fallback, string filePath);

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, EntryPoint = "GetPrivateProfileSectionNames")]
        private static extern int FetchAllSections(IntPtr outputBuffer, int maxLength, string filePath);

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, EntryPoint = "GetPrivateProfileSection")]
        private static extern int FetchSectionContent(string section, byte[] outputBuffer,
            int maxLength, string filePath);

        public static void SetDefaultPath(string path)
        {
            _defaultPath = path;
        }

        public static string RetrieveValue(string section, string key, string fallbackValue)
        {
            return RetrieveValue(section, key, fallbackValue, _defaultPath);
        }

        public static string RetrieveValue(string section, string key, string fallbackValue, string filePath)
        {
            if (!File.Exists(filePath))
                return string.Empty;

            var buffer = new StringBuilder(2048);
            FetchValue(section, key, fallbackValue, buffer, 2048, filePath);
            return buffer.ToString();
        }

        public static bool StoreValue(string section, string key, string value)
        {
            return StoreValue(section, key, value, _defaultPath);
        }

        public static bool StoreValue(string section, string key, string value, string filePath)
        {
            if (!File.Exists(filePath))
                File.Create(filePath).Close();

            long result = PersistValue(section, key, value, filePath);
            return result != 0;
        }

        public static List<string> RetrieveAllSections(string filePath)
        {
            var sectionNames = new List<string>();
            const int maxLen = 32767;
            IntPtr bufferPtr = Marshal.AllocCoTaskMem(maxLen);

            int bytesCount = FetchAllSections(bufferPtr, maxLen, filePath);
            if (bytesCount > 0)
            {
                string raw = Marshal.PtrToStringAnsi(bufferPtr, bytesCount);
                Marshal.FreeCoTaskMem(bufferPtr);
                sectionNames.AddRange(raw.Substring(0, raw.Length - 1).Split('\0'));
            }
            else
            {
                Marshal.FreeCoTaskMem(bufferPtr);
            }

            return sectionNames;
        }

        public static List<string> RetrieveAllKeys(string section, string filePath)
        {
            var keyNames = new List<string>();
            byte[] rawData = new byte[65535];

            FetchSectionContent(section, rawData, rawData.Length, filePath);
            string decoded = Encoding.Default.GetString(rawData);
            string[] entries = decoded.Split((char)0);

            foreach (string entry in entries)
            {
                if (string.IsNullOrEmpty(entry)) continue;

                int separatorIdx = entry.IndexOf('=');
                if (separatorIdx > 0)
                    keyNames.Add(entry.Substring(0, separatorIdx).Trim());
            }

            return keyNames;
        }

        public static Dictionary<string, string> RetrieveAllPairs(string section, string filePath)
        {
            var pairs = new Dictionary<string, string>();
            byte[] rawData = new byte[65535];

            FetchSectionContent(section, rawData, rawData.Length, filePath);
            string decoded = Encoding.Default.GetString(rawData);
            string[] entries = decoded.Split((char)0);

            foreach (string entry in entries)
            {
                if (string.IsNullOrEmpty(entry)) continue;

                int separatorIdx = entry.IndexOf('=');
                if (separatorIdx > 0)
                {
                    string k = entry.Substring(0, separatorIdx).Trim();
                    string v = entry.Substring(separatorIdx + 1).Trim();
                    pairs[k] = v;
                }
            }

            return pairs;
        }

        public static string ReadString(string section, string key, string fallback, string filePath)
        {
            var buffer = new StringBuilder(512);
            FetchValue(section, key, fallback, buffer, 512, filePath);
            return buffer.ToString();
        }

        public static int ReadInt(string section, string key, int fallback, string filePath)
        {
            return FetchInteger(section, key, fallback, filePath);
        }

        public static bool ReadBool(string section, string key, bool fallback, string filePath)
        {
            int defaultVal = fallback ? 1 : 0;
            int result = FetchInteger(section, key, defaultVal, filePath);
            return result != 0;
        }

        public static void WriteString(string section, string key, string value, string filePath)
        {
            PersistValue(section, key, value, filePath);
        }

        public static void WriteInt(string section, string key, int value, string filePath)
        {
            PersistValue(section, key, value.ToString(), filePath);
        }

        public static void WriteBool(string section, string key, bool value, string filePath)
        {
            PersistValue(section, key, value ? "1" : "0", filePath);
        }

        public static void RemoveKey(string section, string key, string filePath)
        {
            PersistValue(section, key, null, filePath);
        }

        public static void ClearSection(string section, string filePath)
        {
            PersistValue(section, null, null, filePath);
        }

        public static bool HasSection(string section, string filePath)
        {
            var sections = RetrieveAllSections(filePath);
            return sections.Contains(section);
        }

        public static bool HasKey(string section, string key, string filePath)
        {
            var keys = RetrieveAllKeys(section, filePath);
            return keys.Contains(key);
        }
    }
}
</string></string></string></string>

3.2 Utilitário de Serialização JSON

using Newtonsoft.Json;

namespace ConfigManager.Utils
{
    public static class JsonSerializerHelper
    {
        public static string Serialize<T>(T instance)
        {
            try
            {
                return JsonConvert.SerializeObject(instance);
            }
            catch
            {
                return string.Empty;
            }
        }

        public static T Deserialize<T>(string jsonContent)
        {
            try
            {
                return (T)JsonConvert.DeserializeObject(jsonContent, typeof(T));
            }
            catch
            {
                return default(T);
            }
        }
    }
}

3.3 Modelos de Dados

Classe de parâmetros básicos:

namespace ConfigManager.Models
{
    public class BaseSettings
    {
        // Coordenadas de pesagem
        public double PesagemCoordX { get; set; }
        public double PesagemCoordY { get; set; }
        public double PesagemCoordZ { get; set; }

        // Coordenadas de recolhimento
        public double RecolherCoordX { get; set; }
        public double RecolherCoordY { get; set; }
        public double RecolherCoordZ { get; set; }

        // Coordenadas de espera
        public double EsperaCoordX { get; set; }
        public double EsperaCoordY { get; set; }
        public double EsperaCoordZ { get; set; }

        // Coordenadas iniciais
        public double OrigemCoordX { get; set; }
        public double OrigemCoordY { get; set; }
        public double OrigemCoordZ { get; set; }

        // Deslocamentos
        public double DeslocX { get; set; }
        public double DeslocY { get; set; }
        public double DeslocZ { get; set; }

        // Velocidades automáticas
        public double VelAutoX { get; set; }
        public double VelAutoY { get; set; }
        public double VelAutoZ { get; set; }

        // Velocidades manuais
        public double VelManualX { get; set; } = 15;
        public double VelManualY { get; set; } = 2;
        public double VelManualZ { get; set; } = 1;

        // Portas seriais
        public string PortaBalanca { get; set; } = "COM1";
        public string PortaLeitor { get; set; } = "COM2";

        // Taxas de transmissão
        public int BaudBalanca { get; set; } = 9600;
        public int BaudLeitor { get; set; } = 9600;

        // Configurações seriais
        public string ParamSerialBalanca { get; set; } = "N81";
        public string ParamSerialLeitor { get; set; } = "N81";

        // Parâmetros de peso
        public double PesoVazio { get; set; } = 12.0;
        public double MargemPeso { get; set; } = 1.0;

        // Limites de operação
        public int MaxEntradas { get; set; } = 100;
        public int MaxSaidas { get; set; } = 3;
        public int IntervaloBloqueio { get; set; } = 100;
    }
}

Classe de parâmetros avançados:

namespace ConfigManager.Models
{
    public class AdvancedSettings
    {
        public short IdentificadorPlaca { get; set; } = 1;
        public short QtdEixos { get; set; } = 4;

        // Mapeamento de eixos
        public short EixoX { get; set; } = 1;
        public short EixoY { get; set; } = 2;
        public short EixoZ { get; set; } = 3;
        public short EixoU { get; set; } = 4;

        // Entradas digitais
        public short BtnArmazenar { get; set; } = 1;
        public short BtnRetirar { get; set; } = 2;
        public short BtnEmergencia { get; set; } = 3;
        public short FimCursoAberto { get; set; } = 4;
        public short FimCursoFechado { get; set; } = 5;

        // Saídas digitais
        public short LedArmazenar { get; set; } = 1;
        public short LedRetirar { get; set; } = 2;
        public short LedModo { get; set; } = 3;
        public short AtuadorGripper { get; set; } = 4;
        public short PortaSupEsq { get; set; } = 5;
        public short PortaSupDir { get; set; } = 6;
        public short PortaSupAtras { get; set; } = 7;
        public short PortaInfFrente { get; set; } = 8;
        public short ControleIluminacao { get; set; } = 9;

        // Dimensões da grade de armazenamento
        public short TotalLinhas { get; set; } = 10;
        public short TotalColunas { get; set; } = 9;

        // Arquivo de configuração do controlador de movimento
        public string ArquivoCfg { get; set; } = "padrao";

        // Fatores de escala (pulsos por milímetro)
        public double FatorEscalaX { get; set; } = 4000;
        public double FatorEscalaY { get; set; } = 4000;
        public double FatorEscalaZ { get; set; } = 4000;

        // Acelerações
        public double AceleracaoX { get; set; } = 0.01;
        public double AceleracaoY { get; set; } = 0.01;
        public double AceleracaoZ { get; set; } = 0.01;
    }
}

Classe de resultado de operação:

namespace ConfigManager.Models
{
    public class ResultadoOperacao
    {
        public bool Sucesso { get; set; }
        public string MensagemErro { get; set; }

        public static ResultadoOperacao Ok()
        {
            return new ResultadoOperacao { Sucesso = true, MensagemErro = "OK" };
        }

        public static ResultadoOperacao Falha(string mensagem = "NG")
        {
            return new ResultadoOperacao { Sucesso = false, MensagemErro = mensagem };
        }
    }
}

3.4 Camada de Gerenciamento de Configurações

using System;
using System.Windows.Forms;
using ConfigManager.Models;
using ConfigManager.Utils;

namespace ConfigManager.Services
{
    public static class ConfigService
    {
        public static BaseSettings SettingsBase { get; private set; } = new BaseSettings();
        public static AdvancedSettings SettingsAvancado { get; private set; } = new AdvancedSettings();

        private static readonly string CaminhoConfig =
            Path.Combine(Application.StartupPath, "Config", "settings.ini");

        public static ResultadoOperacao PersistirConfiguracaoBase()
        {
            string conteudo = JsonSerializerHelper.Serialize(SettingsBase);
            bool ok = IniFileHandler.StoreValue("Configuracoes", "ParametrosBase", conteudo, CaminhoConfig);
            return ok ? ResultadoOperacao.Ok() : ResultadoOperacao.Falha();
        }

        public static ResultadoOperacao PersistirConfiguracaoAvancada()
        {
            string conteudo = JsonSerializerHelper.Serialize(SettingsAvancado);
            bool ok = IniFileHandler.StoreValue("Configuracoes", "ParametrosAvancados", conteudo, CaminhoConfig);
            return ok ? ResultadoOperacao.Ok() : ResultadoOperacao.Falha();
        }

        public static ResultadoOperacao CarregarTodasConfiguracoes()
        {
            try
            {
                string padraoBase = JsonSerializerHelper.Serialize(new BaseSettings());
                string jsonBase = IniFileHandler.RetrieveValue(
                    "Configuracoes", "ParametrosBase", padraoBase, CaminhoConfig);

                if (!string.IsNullOrWhiteSpace(jsonBase))
                    SettingsBase = JsonSerializerHelper.Deserialize<BaseSettings>(jsonBase);

                string padraoAdv = JsonSerializerHelper.Serialize(new AdvancedSettings());
                string jsonAdv = IniFileHandler.RetrieveValue(
                    "Configuracoes", "ParametrosAvancados", padraoAdv, CaminhoConfig);

                if (!string.IsNullOrWhiteSpace(jsonAdv))
                    SettingsAvancado = JsonSerializerHelper.Deserialize<AdvancedSettings>(jsonAdv);
            }
            catch (Exception ex)
            {
                return ResultadoOperacao.Falha(ex.Message);
            }

            return ResultadoOperacao.Ok();
        }
    }
}

3.5 Formulário de Configuração com Reflexão

A interface utiliza reflexão para vincular automaticamente controles da UI às propriedades dos objetos de configuração. Os nomes dos controles devem corresponder exatamente aos nomes das propriedades.

using System;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using ConfigManager.Models;
using ConfigManager.Services;

namespace ConfigManager.UI
{
    public partial class FormConfiguracoes : Form
    {
        public FormConfiguracoes()
        {
            InitializeComponent();
            CarregarValoresNaInterface(tabConfigBase, ConfigService.SettingsBase);
            CarregarValoresNaInterface(tabConfigAvancada, ConfigService.SettingsAvancado);
        }

        private void CarregarValoresNaInterface<T>(TabPage aba, T modelo)
        {
            foreach (Control ctrl in aba.Controls)
            {
                if (ctrl is NumericUpDown numericInput)
                {
                    string nomeProp = numericInput.Name;
                    string valorProp = ObterValorPropriedade(modelo, nomeProp);

                    if (!string.IsNullOrEmpty(valorProp) &&
                        decimal.TryParse(valorProp, out decimal numVal))
                    {
                        numericInput.Value = numVal;
                    }
                }
                else if (ctrl is ComboBox lista)
                {
                    string nomeProp = lista.Name;
                    string valorProp = ObterValorPropriedade(modelo, nomeProp);
                    if (valorProp != null) lista.Text = valorProp;
                }
            }
        }

        private void AplicarAlteracoes<T>(string tipoConfig, T modelo)
        {
            var log = new StringBuilder();
            TabPage abaAtual = tabControlPrincipal.SelectedTab;

            foreach (Control ctrl in abaAtual.Controls)
            {
                string nomeProp = ctrl.Name;
                string valorAtual = ObterValorPropriedade(modelo, nomeProp);

                if (ctrl is NumericUpDown numericInput &&
                    !string.IsNullOrEmpty(valorAtual) &&
                    numericInput.Value.ToString() != valorAtual)
                {
                    log.Append($"{ctrl.Tag} alterado para {numericInput.Value}; ");
                    DefinirValorPropriedade(modelo, nomeProp, numericInput.Value.ToString());
                }
                else if (ctrl is ComboBox lista &&
                         !string.IsNullOrEmpty(valorAtual) &&
                         lista.Text != valorAtual)
                {
                    log.Append($"{ctrl.Tag} alterado para {lista.Text}; ");
                    DefinirValorPropriedade(modelo, nomeProp, lista.Text);
                }
            }

            if (log.Length == 0)
            {
                MessageBox.Show("Nenhuma alteração detectada.", tipoConfig);
                return;
            }

            ResultadoOperacao resultado;
            if (tipoConfig == "Base")
                resultado = ConfigService.PersistirConfiguracaoBase();
            else
                resultado = ConfigService.PersistirConfiguracaoAvancada();

            if (resultado.Sucesso)
                MessageBox.Show($"{tipoConfig} atualizado com sucesso.", "Confirmação");
            else
                MessageBox.Show($"Falha ao salvar {tipoConfig}.", "Erro");
        }

        private string ObterValorPropriedade<T>(T objeto, string nomePropriedade)
        {
            try
            {
                PropertyInfo prop = typeof(T).GetProperty(nomePropriedade);
                return prop?.GetValue(objeto)?.ToString() ?? string.Empty;
            }
            catch
            {
                return null;
            }
        }

        private bool DefinirValorPropriedade<T>(T objeto, string nomePropriedade, string valor)
        {
            try
            {
                PropertyInfo prop = typeof(T).GetProperty(nomePropriedade);
                object convertido = Convert.ChangeType(valor, prop.PropertyType);
                prop.SetValue(objeto, convertido);
                return true;
            }
            catch
            {
                return false;
            }
        }

        private void BtnSalvarBase_Click(object sender, EventArgs e)
        {
            AplicarAlteracoes("Base", ConfigService.SettingsBase);
        }

        private void BtnCancelarBase_Click(object sender, EventArgs e)
        {
            CarregarValoresNaInterface(tabConfigBase, ConfigService.SettingsBase);
        }

        private void BtnSalvarAvancado_Click(object sender, EventArgs e)
        {
            AplicarAlteracoes("Avançado", ConfigService.SettingsAvancado);
        }

        private void BtnCancelarAvancado_Click(object sender, EventArgs e)
        {
            CarregarValoresNaInterface(tabConfigAvancada, ConfigService.SettingsAvancado);
        }
    }
}

3.6 Estrutura do Arquivo de Configuração Resultante

O arquivo INI gerado em Config\settings.ini apresenta a seguinte estrutura:

[Configuracoes]
ParametrosBase={"PesagemCoordX":1.0,"PesagemCoordY":2.0,"PesagemCoordZ":3.0,...,"PortaBalanca":"COM1","BaudBalanca":9600,"PesoVazio":12.0,"MaxEntradas":100,"MaxSaidas":3}
ParametrosAvancados={"IdentificadorPlaca":1,"QtdEixos":4,"EixoX":1,...,"TotalLinhas":10,"TotalColunas":9,"ArquivoCfg":"padrao","FatorEscalaX":4000.0,"AceleracaoX":0.01}

Tags: C# INI Windows-API JSON Reflexão

Publicado em 7-21 22:38