- 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
- 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
- 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}