Criando Windows Forms Dinamicamente com JSON / Dynamic Windows Forms with JSON

Português   English
Photo by Iker Urteaga on Unsplash

Recentemente tive algum trabalho para fazer integração de relatórios com um projeto legado em Windows Forms por conta de a nova versão ter algumas incompatibilidades com versões anteriores.

Meu maior problema era a passagem de parâmetros para a montagem de um relatório a partir das opções que o usuário iria enviar.

Durante o processo notei que se fosse possível criar um formulário dinamicamente para a coleta dos dados esta tarefa seria completada mais facilmente.

Aplicações desktop usando Windows Forms estão por aí há muito tempo e mesmo que estejam começando a decrescer em utilização - dificilmente surgem novos projetos principalmente por conta da adoção em massa de tecnologias voltadas à nuvem - ainda irão continuar pois o .Net Core está preparando o suporte para este tipo de aplicações. Mais sobre isso na seção de links no final do post.

Assim, fiz uns testes para saber se seria possível unir a versatilidade do JSON com Windows Forms para criar formulários dinâmicos para coleta de dados.

Atualmente JSON está por quase toda a parte e existem bibliotecas como a Newtonsoft JSON para .Net que facilita muito o trabalho.

Abaixo uma amostra de um formulário construído usando o projeto (que pode ser baixado no meu GitHUB:).

A linguagem JSON permite que sejam expressos objetos complexos usando uma sintaxe bem simples. É muito fácil criar listas e hierarquias. No meu projeto a ideia foi ter uma lista com os controles mais básicos:

  • Text Box
  • Check Box
  • Date Time Picker
  • Combo box

E expressá-los com JSON:

{
"name":"form1",
"text":"Primeiro formulário",
"type":"OK",
"controls":[
  {
    "name":"txtText1",
    "type":"TextBox",
    "key":"text1",
    "label":"Campo texto",
    "initialValue":"Some text"
  },
  {
    "name":"chkCheck1",
    "type":"CheckBox",
    "key":"check1",
    "label":"Campo Lógico",
    "initialValue":"false"
  },
  {
    "name":"dtDate",
    "type":"DateTime",
    "key":"date1",
    "label":"Data inicial",
    "initialValue":"2019-01-22"
  },
  {
    "name":"cboName",
    "type":"ComboBox",
    "key":"name1",
    "label":"Campo múltipla escolha",
    "initialValue":"1",
    "bindingSource": {
      "connection":"Data source=localhost\\sqlexpress2014; Initial catalog=adventureworks2012; Integrated security=true;",         "sql":"select id, name from programmers order by name",
        "keyValue":"id",
        "displayValue":"name"
  }}]

}

Para todos os controles existirão propriedades comuns usadas para o nome, o tipo do controle, qual o rótulo para o campo e um valor inicial.

Estes controles contidos no JSON são validados pelo projeto ao serem convertidos para objetos no C#.

Para o controle Combo Box foi criada a possibilidade de se usar items de uma lista fixa, onde os valores devem vir separados por ponto e vírgula ou a partir de uma consulta SQL em um banco de dados SQL Server.

Quando for necessário expressar o binding do controle ComboBox com itens de uma lista pré-definida basta fazer a notação como segue:

        {
            "name":"cboName2",
            "type":"ComboBox",
            "key":"options",
            "label":"Tipo do relatório",
            "initialValue":"Geral",
            "bindingSource": {
                "connection":"",
                "sql":"",
                "keyValue":"",
                "displayValue":"",
                "items":"Geral;Parcial;"
            }
        }

Nesta fase não foi dado suporte a outros bancos de dados pois trata-se apenas de uma prova de conceito, um MVP se preferir.

Um dos pontos importantes a se observar ao usar JSON para expressar classes do C# é que usualmente os nomes das propriedades deste são expressos usando "camelCase o" que é diferente do C#.

Não haveria problema em se usar o mesmo tipo de notação do C# no JSON e vice-versa, porém, preferi manter o padrão das duas linguagens.

Para o C# poder fazer o mapeamento correto das propriedades expressas no JSON foi usado o recurso de criar atributos conhecidos como Data Annotations.

Modelagem e preparo para usar JSON:

[Serializable] public class DynamicDialog { #region fields string _dialogType; IEnumerable<string> _validDialogTypes = new List<string>() { "OK", "YesNo" }; DynamicForm _DynamicForm; #endregion #region properties [JsonProperty(PropertyName = "name")] public string FormName { get; set; } [JsonProperty(PropertyName = "text")] public string Text { get; set; } [JsonProperty(PropertyName = "type")] public string DialogType { get { return _dialogType; } set { if (_validDialogTypes.Contains(value)) { _dialogType = value; } else { throw new Exception("Dialog type is not valid.\nCurrent valid types are:\n\n\t\"OK\" and \"YesNo\""); } } } [JsonProperty(PropertyName = "controls")] public List<DynamicControl> DynamicControls { get; set; } #endregion

O código procura manter tudo o mais simples possível. O formulário onde os controles são dispostos é um Windows Forms com um design pré-definido e com os botões que fazem o retorno já colocados e alinhados corretamente.

Dentro deste formulário os controles são aninhados dentro de um componente container Panel que permite usar uma barra de rolagem já que há um limite máximo de altura para o form.

A maior dificuldade encontrada foi fazer o alinhamento dos controles e o controle do seu tamanho. Para isso foram criadas as propriedades:

  • _labelWidth: controla a largura do rótulo dos campos
  • _controlWidth: para a largura dos controles
  • _panelOffset: controla o espaçamento dos controles e ajuda a redimensionar dinamicamente o formulário.
  • _controlOffset: para o espaçamento entre os controles.

Preparo para montar o formulário:

Ao se trabalhar com geração dinâmica dos controles visuais é necessário ir fazendo ajustes para controle do espaçamento entre estes e o ajuste automático da largura e altura do formulário.

No método CreateDynamicControls todos estes cuidados são tomados.

public partial class DynamicForm : Form { public Dictionary<string, string> dicReturnValues { get { return _returnValues; } } List<DynamicControl> _dynamicControls; Dictionary<string, string> _returnValues; int _labelWidth = 150; int _controlWidth = 350; int _panelOffset = 20; int _controlOffset = 2;

Uma vez que os controles estão configurados os valores retornados serão obtidos ao clicar no botão de confirmação (que dá como DialogResut o valor OK). O método FillReturnValues percorre o dictionary que foi criado na configuração.

Cada controle deve possuir uma chave única informada no JSON com o atributo "key". Este nome é armazenado na propriedade "Tag" do controle.

O projeto consiste de uma class library monolítica: WindowsForms (eu sei... Pode dar algum conflito na integração) escrita na versão 4.0 do Framework, o que a torna atraente para a maior parte dos projetos Windows Forms.

Esta possui dependência da biblioteca Newtonsoft JSON .NET que também pode ser facilmente integrada em outros projetos.

Alguns pontos do código merecem uma refinada como no caso do controle combo box, não há verificação de segurança para a instrução SQL que é enviada. Isto pode possibilitar algum tipo de ataque.

Neste estágio se for enviado um JSON incorreto a aplicação simplesmente informa que não foi possível montar o formulário não dando maiores pistas sobre o que houve.

Fique à vontade para examinar o projeto. Se tiver sugestões entre em contato por e-mail que está no meu perfil. Até a próxima.

Links