terça-feira, 8 de fevereiro de 2011

Criando um Quadro de Horários

Cá estou eu novamente para o primeiro post do ano. Demorei um pouco, mas agora estou de volta :).

Uma das idéias que eu tinha vontade de escrever é como montar um quadro de horários em C#. Os requisitos seriam que ele deveria ser de segunda a domingo e customizável, isto é, poderiam ser adicionados quantos horários por dia fossem necessários. Pois bem, segue agora a minha solução.

Crie uma Web Application no Visual Studio. Para esse exemplo, a implementação foi toda feita utilizando o arquivo Default.aspx (gerado automaticamente pelo VS).

No aspx, adicionei um botão salvar para a persistência dos dados (não é parte desse post explicar como armazenar os dados, isso daria outro post hehehe). Já que esse botão não precisa armazenar status, seu View State foi desabilitado (Figura 1).

<asp:Button ID="btnSalvar" runat="server" Text="Salvar" OnClick="btnSalvar_Click"
            EnableViewState="false" Style="padding-left: 5px;" />
Figura 1 - Botão de Salvar

Para o corpo da tela, criei uma tabela e, dentro de cada coluna, um botão para adicionar mais horários e um DataList. Neste serão exibidos quantos forem os horários, além de um botão para remover o referido horário inválido (Figura 2). Pelo mesmo motivo do botão salvar, o View State foi desabilitado.

<table style="border: 1px; border-style: inset; border-color: Black;">
            <tr>
                <td style="text-align: left; margin: 5px; vertical-align: text-top;">
                    Segunda-Feira:
                    <asp:Button ID="btnAdicionarHorarioSegunda" runat="server"
                        Text="Adicionar" OnClick="btnAdicionarHorarioSegunda_Click"
                        EnableViewState="false" Width="70px" />
                    <asp:DataList ID="dlstSegunda" runat="server" RepeatColumns="1" RepeatDirection="Vertical"
                        OnItemDataBound="dlstHorarios_ItemDataBound">
                        <ItemTemplate>
                            <table cellpadding="3" style="text-align: left; vertical-align: text-top;">
                                <tr>
                                    <td>
                                        <asp:TextBox ID="txtHorario" runat="server" Width="60px"></asp:TextBox>
                                    </td>
                                    <td>
                                        <asp:Button ID="btnRemover" runat="server" Text="Remover"
                                        OnClick="btnRemoverHorarioSegunda_Click" Width="70px" />
                                    </td>
                                </tr>
                            </table>
                        </ItemTemplate>
                    </asp:DataList>
                </td>
                <td style="text-align: left; margin: 5px; vertical-align: text-top;">
                    Terça-Feira:
                                    ...

Figura 2 – Corpo do quadro de horários

Continuando, para os outros dias a implementação é a mesma. Basta apenas alterar o nome dos botões de adição de horários e seus respectivos eventos, além do evento de remoção de horário. Feito isso para todos os dias, está pronta parte visual.

Vamos agora ao code-behind.

No evento Page_Load adicionei um método para obter os horários desejados (Figura 3).

protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                this.ObterHorarios();
        }
Figura 3 - Page Load

Segue a implementação do método ObterHorarios (Figura 4):

private void ObterHorarios()
        {
            IList<TimeSpan> horarios = new List<TimeSpan>(4);
            horarios.Add(new TimeSpan(10, 0, 0));
            horarios.Add(new TimeSpan(11, 0, 0));
            horarios.Add(new TimeSpan(12, 0, 0));
            horarios.Add(new TimeSpan(15, 0, 0));

            this.ListarHorarios(dlstSegunda, horarios);
            this.ListarHorarios(dlstTerca, null);
            this.ListarHorarios(dlstQuarta, horarios);
            this.ListarHorarios(dlstQuinta, horarios);
            this.ListarHorarios(dlstSexta, horarios);
            this.ListarHorarios(dlstSabado, null);
            this.ListarHorarios(dlstDomingo, null);
        }
Figura 4 – Obter Horários

Adicionei alguns horários defaults, o que numa aplicação real podem ser obtidos de um arquivo XML, do banco de dados ou de qualquer outra forma de persistência dependendo do requisito. Posteriormente, o DataList  de cada dia é preenchido. Escolhi a terça, sábado e domingo para não serem preenchidos. Daí o porquê do parâmetro null.

Agora vamos ao método que lista os horários na tela (Figura 5). Até agora nada demais.

private void ListarHorarios(DataList dataList, IList<TimeSpan> horarios)
 {
            dataList.DataSource = horarios;
            dataList.DataBind(); 
}
Figura 5 – Lista de Horários

Para formatar os horários e não exibir os segundos, foi preciso criar o método ItemDataBound (Figura 6). Segue a implementação:


protected void dlstHorarios_ItemDataBound(object sender, DataListItemEventArgs e)
{
            if (e.Item.ItemType.Equals(ListItemType.AlternatingItem) || e.Item.ItemType.Equals(ListItemType.Item))
            {
                TimeSpan horario = (TimeSpan)e.Item.DataItem;

                if (this.IsHorarioValido(horario))
               {
                    TextBox txtHorario = (TextBox)e.Item.FindControl("txtHorario");
                    txtHorario.Text = this.FormatarHorario(horario);

                    Button btnRemover = (Button)e.Item.FindControl("btnRemover");
                    btnRemover.CommandArgument = txtHorario.Text;
             }
       }
}
Figura 6 – Método ItemDataBound do DataList de Horários

Neste momento, o horário passado é formatado e atribuído ao TextBox de horário. Depois, é utilizado como CommandArgumment no botão de remover. Será utilizado mais adiante.

Abaixo a implementação dos métodos complementares ao ItemDataBound (Figura 7).

private string FormatarHorario(TimeSpan horario)
{
      return string.Format("{0:D2}:{1:D2}", horario.Hours, horario.Minutes);
}                                                                                                                       

private bool IsHorarioValido(TimeSpan horario)    
{ 
     return horario.TotalDays > 0;
}
Figura 7 – Validação do Horário

O objetivo do método IsHorarioValido é analisar se o horário não é 00:00:00. Já o FormatarHorario apenas retira os segundos.

Até este momento, os horários persistidos já estão apresentados na tela. Mas e se quisermos adicionar mais um horário à segunda-feira? Vamos a ele!

Para cada dia foi adicionado um botão para adicionar um horário extra. Essa implementação é a mesma para cada dia. Portanto, é prudente criar um método que, de acordo com o dia, adiciona um horário (Figura 8).

protected void btnAdicionarHorarioSegunda_Click(object sender, EventArgs e)
{
                  this.AdicionarHorario(dlstSegunda);
}

private void AdicionarHorario(DataList dataList)
{
        IList<TimeSpan> horarios = this.ObterHorariosPorControle(dataList);
        horarios.Add(TimeSpan.Zero);

        this.ListarHorarios(dataList, horarios);
}
Figura 8 – Adição de Horários

Como cada dia tem o seu próprio botão de adição de horários, cada um vai chamar o método AdicionarHorario informando o DataList que deve receber o horário extra.

No método AdicionarHorario, são obtidos os horários do DataList informado e adicionado o novo horário. Perceba que foi utilizado o TimeSpan.Zero. Dessa forma, o valor é tratado no ItemDataBound (já visto acima) e o usuário pode digitar seu horário novo normalmente.  Por fim, os horários obtidos são listados (método já conhecido).

Dando sequência, temos o método ObterHorariosPorControle. Você deve estar se perguntando o porquê desse nome. Se não está, pelo menos finja um minuto :). Poderia ter colocado ObterHorariosPorDataList já que a aplicação utiliza DataLists. Mas e se amanhã não usar mais? Os nomes dos métodos também devem ser alterados? Por isso que o nome do método fala de controle. Se amanhã o ideal for utilizar um Repeater, o nome do método não precisa mudar.

private IList<TimeSpan> ObterHorariosPorControle(Control controle)
{
            IList<TimeSpan> horarios = new List<TimeSpan>();
            string valorHora;
            foreach (Control controlePai in controle.Controls)
            {
                foreach (var controleFilho in controlePai.Controls)
                {
                    if (controleFilho is TextBox)
                    {
                        valorHora = ((TextBox)controleFilho).Text;
                       horarios.Add(new TimeSpan(this.ObterHoras(valorHora), this.ObterMinutos(valorHora), 0));
                    }
                }
            }

            return horarios;
}
Figura 9 – Obtendo Todos os Horários do Controle

No método acima, são percorridos todos os controles filhos do controle parâmetro. Para cada um, são percorridos seus filhos. Assim que encontrar um TextBox, obtém o seu valor  e adiciona na lista de horários.

Complementando o código acima, seguem os métodos ObterHoras e ObterMinuto (Figura 10).

private int ObterHoras(string valorHora)      
{                
            if (!string.IsNullOrEmpty(valorHora))   
            { 
               string[] horas = valorHora.Split(':');  
                return Convert.ToInt32(horas[0]);          
            }
            return 0;                                                                                       
}            
                    
private int ObterMinutos(string valorHora)       
{       
            if (!string.IsNullOrEmpty(valorHora))       
            {  
                string[] horas = valorHora.Split(':');  
                return Convert.ToInt32(horas[1]);  
            } 
             return 0;
}
Figura 10 – Obtendo Horas e Minutos

Os métodos acima recebem a hora completa e extraem as horas e os minutos, de acordo com os dois pontos.

Prosseguindo com as funcionalidades, um usuário pode perfeitamente querer remover um horário. Para fazer a remoção, precisamos saber qual horário será removido. Para tal, através do botão clicado, eu obtenho o horário que será removido. A partir daí, basta remover da lista de horários aquele selecionado. Por fim, preencher o DataList de horários novamente.

protected void btnRemoverHorarioSegunda_Click(object sender, EventArgs e)
{
    Button botao = (Button)sender;
    this.RemoverHorario(dlstSegunda, botao.CommandArgument);
}

private void RemoverHorario(DataList dataList, string horario)
{
      IList<TimeSpan> horarios = this.ObterHorariosPorControle(dataList);
     horarios.Remove(new TimeSpan(this.ObterHoras(horario), this.ObterMinutos(horario), 0));

    this.ListarHorarios(dataList, horarios);
}
 Figura 11 – Removendo Horários

Para finalizar, falta apenas salvar os dados. Já que existe o evento ObterHorariosPorControle, basta chamá-lo passando os DataLists desejados (Figura 12). Depois, a forma de persistência é a gosto do freguês.

protected void btnSalvar_Click(object sender, EventArgs e)                         {
   IList<TimeSpan> horariosSegunda = this.ObterHorariosPorControle(dlstSegunda);
   IList<TimeSpan> horariosTerca = this.ObterHorariosPorControle(dlstTerca);
   IList<TimeSpan> horariosQuarta = this.ObterHorariosPorControle(dlstQuarta);
   IList<TimeSpan> horariosQuinta = this.ObterHorariosPorControle(dlstQuinta);
   IList<TimeSpan> horariosSexta = this.ObterHorariosPorControle(dlstSexta);
   IList<TimeSpan> horariosSabado = this.ObterHorariosPorControle(dlstSabado);
   IList<TimeSpan> horariosDomingo = this.ObterHorariosPorControle(dlstDomingo);
//Código de salvar...
 
}
Figura 12 – Salvando os Horários

Chegamos ao final deste post. Foi apresentado a parte visual e o code-behind do quadro de horários. Foram apresentados os métodos de adição e remoção de horários. Essa é apenas uma parte da solução. Para torná-la mais elegante, faltam as formas de persistência e algumas alterações no layout. Para quem se interessou, segue o código abaixo para download e uma imagem do resultado.

http://www.megaupload.com/?d=HBYK3616

terça-feira, 9 de novembro de 2010

Tempo de Instanciação de Variáveis em C#

Sempre me preocupei com as boas práticas de programação e questões de desempenho de software. Mas o mais legal não é ler sobre isso (não que não seja importante, muito pelo contrário), é abrir o Visual Studio e ver na prática as coisas funcionando. Bom, vamos a elas.

O propósito de hoje é descobrir o quanto a instanciação das variáveis interfere no desempenho do software. Para isso, abra o Visual Studio e crie um projeto do tipo "Console Application" para C#. Ele gera a solution, uma Class Library e uma classe Program.cs. Utilize a classe Program.cs para os testes.

A primeira coisa que a fazer é criar uma constante para guardar a quantidade de iterações (Figura 1). À medida que formos executando o teste, basta alterá-la.

      Figura 1 - Total de Iterações

Agora crie o método ObterTempoVariasInstancias() para calcular o tempo de instanciação de várias instâncias (Figura 2).

Figura 2  - Método ObterTempoVariasInstancias


Neste método não há nada demais. Nas linhas 3-4 eu instancio um objeto Stopwatch para calcular o tempo de instanciação das variáveis e o tempo começa a ser contado. Perceba que nas linhas 8-9, toda vez que uma iteração é executada, uma variável nova é instanciada. Na linha 12 o tempo é finalizado e na 13 é retornado o tempo total em milissegundos.

Para medir o tempo, utilizei um notebook Core 2 Duo 2.1 Ghz de 32 bits. Fui alterando a constante TOTAL_ITERACOES e anotando os tempos. Rodei três vezes para cada quantidade de iterações e calculei a média dos tempos de 10 até 1.000.000. Seguem os tempos (Figura 3):

Figura 3 - Tempos para instanciação de várias variáveis


Fiz a mesma coisa para a instanciação de uma variável (Figura 4).

Figura 4 - Método ObterTempoUmaInstancia


As diferenças para o método anterior estão nas linhas 6-10 em que a variável j é instanciada fora do bloco do for e reaproveitada internamente. Desta forma, existe somente uma variável sendo utilizada durante a iteração. E abaixo os resultados (Figura 5):

Figura 5 - Tempos para instanciação de uma variável



Por fim, comparei as duas situações diminuindo o tempo médio para instanciar uma variável e utilizá-la durante todas as iterações do tempo médio para instanciar quantas variáveis for o total de iterações (Figura 6).

Figura 6 - Comparação entre os tempos



Pela tabela acima, percebe-se que utilizar somente uma variável na iteração é mais rápido.

Para esse teste simples, a diferença não foi considerável (apenas para 1.000.000 iterações), porém em um software com várias buscas no banco de dados, muitos usuários acessando e funcionalidades complexas, esse tempo tende a aumentar bastante.
Portanto, uma boa prática de programação é não encher a aplicação de variáveis que podem ser reaproveitadas, reduzindo assim o tempo de execução das funções.

sábado, 9 de outubro de 2010

Design para Quem não é Designer

Faz tempo que não lia nada sobre TI, desenvolvimento, banco de dados ou designer. Foi aí que lembrei de um livro que eu já tinha começado a ler, mas não terminei. Voltei a ler o Designer para Quem não é Designer.

É um bom livro, ensina a ver as páginas de uma forma diferente: alinhamento e contraste são fundamentais! Ele ainda ilustra o antes e o depois. E o melhor disso tudo, é que depois de ler, passei a ser mais criterioso com o designer. Já começo a olhar a página com outros olhos.

Bom, para quem se interessou pelo livro, segue o link para download:
http://www.megaupload.com/?d=UOJ73U91

E boa leitura :)

quinta-feira, 16 de setembro de 2010

Uma maneira fácil de limpar todos os campos de um formulário em C#

Olá pessoal,

Há tempos que pretendo criar um blog para postar sobre as coisas legais que eu li, discutir alguns temas interessantes (pelo menos pra mim hehehe), algumas soluções que me ajudaram no dia-a-dia de desenvolvimento e nunca tinha tempo. Agora esse tempo surgiu e criei o "Na Minha Máquina Funciona". Escolhi este nome porque é a frase mais dita no ambiente de programação e achei uma forma bem-humorada de escrever sobre o ambiente de TI.

Vou começar com uma solução simples que me sempre me ajuda . Nos nossos softwares, temos formulários com vários campos (TextBox, DropDownList, CheckBoxList, etc.) e sempre aparece aquele botão "Limpar" ou "Cancelar", em que cancela tudo que o usuário digitou.

Quando temos 2, 3 ou 4 campos na tela, é simples: basta olhar no aspx o nome do campo e retirar o que o usuário escreveu. E quando temos 20 campos na tela? E quando surgir um campo novo? O programador tem que lembrar de testar se esse último campo criado foi limpo.

Para evitar isso, tem um método simples que soluciona esse problema. Basta envolver os controles num Panel e chamar o método recursivo LimparControles(Control controlePai). Segue o código:
























Percebam que é um método muito simples.

Nas linhas 5-8, é verificado se o controle possui filhos - útil no caso de Panel, UpdatePanel, Table, etc. Se possuir, chama novamente o método.

Nas linhas 9-28, são testados os tipos dos controles. Caso seja TextBox, ele recebe string.Empty. Se for DropDownList, utiliza-se o método ClearSelection() e assim por diante. Se precisar de mais controles, basta incrementar o método.

Espero ter ajudado nesse primeiro artigo e pretendo escrever com freqüência agora.