WPF: persistindo configurações de posicionamento, tamanho e zoom de janelas e colunas de grades por usuário

Você gostaria de dar aos usuários dos seus programas a flexibilidade de poder ajustar cada janela da aplicação ao posicionamento, tamanho e zoom que melhor atendesse às necessidades de cada um e também fazer o mesmo com as colunas de cada grade podendo ajustar suas larguras, ordens e visibilidades?

Pois bem, com WPF é possível fazer tudo isto aplicando um padrão que iremos mostrar aqui neste projeto de exemplo feito no Visual Studio 2015.

No projeto de exemplo intitulado PosPersist, temos uma janela de login para a escolha do usuário para entrar no sistema. Feita a escolha do usuário e clicando-se no botão "Login", o programa abre mostrando uma lista simples de objetos da classe Pessoa num DataGrid. A janela da lista pode ser redimensionada e reposicionada. Uma combo no topo da janela mostra várias opções de zoom que podem ser aplicadas ao conteúdo da janela. As colunas da grade podem ter a largura, ordem e visibilidade alteradas. Para ocultar uma coluna, deve-se clicar com o botão direito sobre o cabeçalho da coluna e será mostrado o menu de contexto com a opção "Ocultar coluna". Querendo restaurar os padrões para toda a janela, basta clicar no botão "Restaurar posições" para que tudo volte ao padrão inicial.

Ao se fechar a janela, todas estas configurações são salvas e associadas à identificação do usuário logado. Na próxima vez que o mesmo usuário abrir a janela, ela será mostrada com as últimas configurações salvas. Vejamos a seguir como isto é feito.

Primeiramente, devemos dizer que o padrão que iremos apresentar aqui visa ser aplicado ao modelo de programação Model-View-ViewModel, onde as janelas e suas grades são controladas por classes do tipo viewmodel. As viewmodels compõem uma hierarquia em que os dados principais da janela podem ser controlados pela viewmodel principal e os dados listados em grades nesta janela por viewmodels secundárias que são propriedades da viewmodel principal. Cada classe do tipo viewmodel será derivada de uma classe base que conterá a propriedade ViewConfigs do tipo da classe ViewConfigData usada para armazenar todos os dados das configurações seja de uma janela ou de uma grade controlada pela viewmodel.

A classe ViewConfigData possui propriedades para controlar altura, largura, posicionamento e fator de zoom de uma janela, mas ela possui também a propriedade Columns, usada para controlar as configurações das colunas de uma grade. Haverá casos em que uma viewmodel usará a propriedade ViewConfigs da classe base, que no nosso exemplo é ViewModelBase, para controlar uma janela; mas haverá casos em que a viewmodel não visará controlar uma janela, mas apenas uma das grades exibidas nela. Neste último caso, apenas a propriedade Colums de ViewConfigData será usada para salvar os dados daquela grade em particular.

Dependendo da viewmodel ser a principal que controla a janela ou ser uma viewmodel filha que controla uma das grades, o salvamento se dará ou na execução do comando de fechamento da janela na viewmodel principal ou no tratamento do evento Unload das grades no módulo de classe da janela. No nosso exemplo, criamos um classe DAL (de data access layer) para persistir em um arquivo binário as configurações, mas numa aplicação real de banco de dados, os métodos da classe DAL usarão uma tabela para armazenar as configurações de cada par viewmodel-conta de usuário.

No exemplo, também foi adicionado um dicionário de recursos para definir o menu de contexto usado para ocultar as colunas ao clicar-se sobre os cabeçalhos delas.

A aplicação das configurações de posicionamento, largura, altura e zoom são feitas usando databinding como pode ser visto no XAML das janelas, mas a aplicação das configurações das colunas é feita no tratamento do evento Load das grades como pode ser visto no módulo de código da janela ListPessoasVw. Por outro lado, quando a janela é fechada e ocorre o evento Unload de cada grade, é no tratamento deste evento que as configurações da grade são salvas. Os mesmos tratamentos de eventos Load e Unload podem ser usadas para todas as grades da janela, pois são genéricos.

Como o salvamento é feito associando as configurações a uma viewmodel e a uma conta de usuário, caso seja preciso criar mais de uma classe derivada de ViewModelBase para o mesmo tipo de model, as diferentes viewmodels compartilharão as mesmas configurações, coisa indesejável. Neste caso, apenas uma classe se serviria da propriedade ViewConfigs da classe base, as demais deveriam implementar suas propriedades ViewConfigs fazendo o override desta propriedade copiando o mesmo formato da classe base.

Por último, chamamos a atenção para uma sutileza que foi necessário usar. Reparem que a variável que armazena o valor da propriedade ViewConfigs em ViewModelBase é estática, mas o get e set da propriedade são de escopo de objeto e não estáticos. Isto foi necessário para possibilitar definir a interface IViewConfig que contém esta propriedade. Como propriedades estáticas não podem fazer parte de interfaces, foi necessário usar este truque.

Caso queira ver esta técnica funcionando numa aplicação real, visite esta página e veja o primeiro vídeo "CashPreview 4.0: janelas mais funcionais".