Existe algum tipo de estratégia sistemática para projetar e implementar GUIs?


18

Estou usando o Visual Studio para criar um aplicativo GUI em C #. O Toolbox serve como uma paleta de componentes bacana que me permite arrastar e soltar facilmente botões e outros elementos (para maior clareza, direi o botão sempre que eu quero dizer "controle") no meu formulário, o que facilita bastante a execução de formulários estáticos. No entanto, encontro dois problemas:

  • Criar os botões em primeiro lugar é muito trabalhoso. Quando tenho um formulário que não é estático (por exemplo, botões ou outros controles são criados em tempo de execução, de acordo com o que o usuário faz), não consigo usar a paleta. Em vez disso, tenho que criar cada botão manualmente, chamando o construtor em qualquer método que esteja usando e, em seguida, inicialize manualmente, especificando a altura, largura, posição, rótulo, manipulador de eventos e assim por diante. Isso é extremamente tedioso, porque eu tenho que adivinhar todos esses parâmetros cosméticos sem poder ver como será a forma e também gera muitas linhas de código repetitivo para cada botão.
  • Fazer os botões fazerem algo também é muito trabalhoso. Lidar com eventos em um aplicativo completo é uma grande dor. A única maneira de saber como fazer isso é selecionar um botão, ir para a guia de eventos em suas propriedades, clicar no OnClickevento para que ele gere o evento no Formcódigo do e, em seguida, preencher o corpo do evento. Como desejo separar lógica e apresentação, todos os meus manipuladores de eventos acabam sendo chamadas de linha única para a função lógica de negócios apropriada. Mas usar isso para muitos botões (por exemplo, imagine o número de botões presentes em um aplicativo como o MS Word) polui o código do meu Formcom dezenas de métodos manipuladores de eventos padrão e é difícil mantê-lo.

Por causa disso, qualquer programa de GUI mais complicado que o Hello World é muito impraticável para mim. Para ser claro, não tenho nenhum problema em lidar com a complexidade dos programas que escrevo com uma interface do usuário mínima - sinto que sou capaz de usar o OOP com um grau razoável de competência para estruturar perfeitamente meu código de lógica de negócios. Mas ao desenvolver a GUI, eu estou preso. Parece tão entediante que sinto que estou reinventando a roda, e há um livro em algum lugar por aí explicando como fazer a GUI corretamente que eu não li.

Estou esquecendo de algo? Ou todos os desenvolvedores de C # simplesmente aceitam as listas intermináveis ​​de manipuladores de eventos repetitivos e o código de criação de botões?


Como uma sugestão (espero que útil), espero que uma boa resposta fale sobre:

  • Usando técnicas de POO (como o padrão de fábrica) para simplificar a criação repetida de botões
  • Combinando muitos manipuladores de eventos em um único método que verifica Senderpara descobrir qual botão chamou e se comporta de acordo
  • XAML e usando o WPF em vez do Windows Forms

Você não precisa mencionar nada disso, é claro. É apenas o meu melhor palpite sobre o tipo de resposta que estou procurando.


Na minha opinião, a fábrica seria um bom padrão, mas vejo muito mais um padrão de Model-View-Controller com comandos conectados a controles em vez de eventos diretamente. O WPF geralmente é MVVM, mas o MVC é bem possível. Atualmente, temos um software enorme no WPF usando MVC em 90%. Todos os comandos são encaminhados para o controlador e ele lida com tudo
Franck

Você não pode criar o formulário dinâmico com todos os botões possíveis com um editor de GUI e apenas tornar as coisas desnecessárias dinamicamente invisíveis? Parece muito menos trabalho.
Hans-Peter Störr

@hstoerr Mas seria difícil fazer o layout funcionar. Muitos botões se sobrepõem.
Superbest

Respostas:


22

Existem várias metodologias que evoluíram ao longo dos anos para lidar com esses problemas mencionados, que são, eu concordo, os dois principais problemas que as estruturas de interface do usuário tiveram que enfrentar nos últimos anos. Vindo de um histórico do WPF, eles são abordados da seguinte maneira:

Design declarativo, em vez de imperativo

Ao descrever cuidadosamente o código para instanciar controles e definir suas propriedades, você descreve o modelo imperativo do design da interface do usuário. Mesmo com o designer do WinForms, você está apenas usando um invólucro sobre esse modelo - abra o arquivo Form1.Designer.cs e verá todo esse código ali.

Com o WPF e o XAML - e modelos semelhantes em outras estruturas, é claro, a partir de HTML - você deve descrever seu layout e deixar que a estrutura faça o trabalho pesado de implementá-lo. Você usa controles mais inteligentes, como Panels (Grid ou WrapPanel, etc) para descrever o relacionamento entre os elementos da interface do usuário, mas a expectativa é que você não os posicione manualmente. Conceitos flexíveis, como controles repetíveis - como o Repeater do ASP.NET ou o ItemsControl do WPF - ajudam a criar uma interface do usuário de escala dinâmica sem escrever códigos repetidos, permitindo que entidades de dados em crescimento dinâmico sejam representadas dinamicamente como controles.

Os DataTemplates do WPF permitem definir - novamente, declarativamente - pequenas pepitas de qualidade da interface do usuário e associá-las aos seus dados; por exemplo, uma lista de objetos de dados do Cliente pode ser vinculada a um ItemsControl e um modelo de dados diferente invocado, dependendo se ele é um funcionário comum (use um modelo de linha de grade padrão com nome e endereço) ou um Cliente Principal, enquanto um modelo diferente , com uma imagem e botões de fácil acesso são exibidos. Novamente, sem escrever código na janela específica , apenas controles mais inteligentes que estão cientes de seu contexto de dados e, assim, permitem que você simplesmente os vincule aos seus dados e que eles executem operações relevantes.

Ligação de dados e separação de comandos

Ao tocar na ligação de dados, a ligação de dados do WPF (e também em outras estruturas, como o AngularJS) permite que você indique sua intenção vinculando um controle (por exemplo, uma caixa de texto) a uma entidade de dados (por exemplo, o Nome do Cliente) e deixe a estrutura lida com o encanamento. A lógica é tratada da mesma forma. Em vez de conectar manualmente os manipuladores de eventos code-behind à lógica de negócios baseada no Controller, use o mecanismo Data Binding para vincular o comportamento de um controlador (por exemplo, a propriedade Command de um botão) a um objeto Command que representa a pepita de atividade.

Ele permite que esse comando seja compartilhado entre janelas sem reescrever os manipuladores de eventos todas as vezes.

Maior nível de abstração

Ambas as soluções para seus dois problemas representam uma mudança para um nível mais alto de abstração do que o paradigma orientado a eventos do Windows Forms que você acha, por direito, cansativo.

A idéia é que você não deseja definir, no código, todas as propriedades que o controle possui e todo comportamento iniciado a partir do botão. Você deseja que seus controles e estrutura subjacente trabalhem mais para você e permita que você pense em conceitos mais abstratos de Ligação de Dados (que existe no WinForms, mas não é nem de longe tão útil quanto no WPF) e no padrão Comando para definir links entre a interface do usuário e comportamento que não exige que se reduza ao metal.

O padrão MVVM é a abordagem da Microsoft para esse paradigma. Eu sugiro ler sobre isso.

Este é um exemplo aproximado de como esse modelo ficaria e como pouparia tempo e linhas de código. Isso não será compilado, mas é pseudo-WPF. :)

Em algum lugar dos recursos do aplicativo, você define Modelos de Dados:

<DataTemplate x:DataType="Customer">
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

<DataTemplate x:DataType="PrimeCustomer">
   <Image Source="GoldStar.png"/>
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

Agora, você vincula sua tela principal a um ViewModel, uma classe que expõe seus dados. Digamos, uma coleção de Clientes (simplesmente a List<Customer>) e um objeto de Comando (novamente, uma propriedade pública simples do tipo ICommand). Este link permite a ligação:

public class CustomersnViewModel
{
     public List<Customer> Customers {get;}
     public ICommand RefreshCustomerListCommand {get;}  
}

e a interface do usuário:

<ListBox ItemsSource="{Binding Customers}"/>
<Button Command="{Binding RefreshCustomerListCommand}">Refresh</Button>

E é isso. A sintaxe do ListBox pega a lista de Customers fora do ViewModel e tenta renderizá-los para a interface do usuário. Por causa dos dois DataTemplates definidos anteriormente, ele importará o modelo relevante (com base no DataType, assumindo que o PrimeCustomer herda do Customer) e o colocará como o conteúdo do ListBox. Sem loop, sem geração dinâmica de controles via código.

O Button, da mesma forma, possui sintaxe preexistente para vincular seu comportamento a uma implementação ICommand, que presumivelmente sabe atualizar a Customerspropriedade - solicitando que a estrutura de ligação de dados atualize a interface do usuário automaticamente, automaticamente.

Peguei alguns atalhos aqui, é claro, mas essa é a essência.


5

Primeiro, recomendo esta série de artigos: http://codebetter.com/jeremymiller/2007/07/26/the-build-your-own-cab-series-table-of-contents/ - não aborda os problemas detalhados com o código do botão, mas fornece uma estratégia sistemática para projetar e implementar sua própria estrutura de aplicativos, especialmente para aplicativos GUI, mostrando como aplicar MVC, MVP, MVVM ao seu aplicativo típico de formulários.

Respondendo à sua pergunta sobre "lidar com eventos": se você tiver muitos formulários com botões trabalhando de maneira semelhante, provavelmente será útil implementar a atribuição do manipulador de eventos em sua "estrutura de aplicativos" por si mesmo. Em vez de atribuir os eventos de clique usando o designer da GUI, crie uma convenção de nomenclatura como um manipulador de "clique" deve ser nomeado para um botão de um nome específico. Por exemplo - para o botão MyNiftyButton_ClickHandlerdo botão MyNiftyButton. Então, não é realmente difícil escrever uma rotina reutilizável (utilizando reflexão) que itere sobre todos os botões de um formulário, verifique se o formulário contém um manipulador de cliques relacionado e atribua o manipulador ao evento automaticamente. Veja aqui um exemplo.

E se você quiser apenas usar um manipulador de eventos para vários botões, isso também será possível. Como cada manipulador de eventos recebe o remetente e o remetente é sempre o botão pressionado, você pode despachar para o código comercial utilizando a propriedade "Nome" de cada botão.

Para a criação dinâmica de botões (ou outros controles): você pode criar botões ou outros controles com o designer em um formulário não utilizado apenas com a finalidade de ser um modelo . Em seguida, use a reflexão (veja aqui um exemplo ) para clonar o controle do modelo e alterar apenas as propriedades como local ou tamanho. Provavelmente será muito mais simples do que inicializar duas ou três dúzias de propriedades manualmente no código.

Eu escrevi isso acima, tendo em mente o Winforms (assim como você, eu acho), mas os princípios gerais devem se aplicar a outros ambientes da GUI, como o WPF, com recursos semelhantes.


Na verdade, é possível fazer no WPF e é ainda mais fácil. Você simplesmente preenche uma lista de comandos (funções específicas pré-codificadas) e define a fonte na sua janela. Você cria um modelo visual simples e agradável e tudo o que precisa para cuidar é preencher uma coleção com os comandos desejados e a excelente associação do WPF cuida do resto visualmente.
Franck
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.