Paulo Roberto's profileSomos a última geraçãoPhotosBlogListsMore Tools Help

Blog


    October 31

    Nova versao do NHibernate

    acabou de sair uma versao nova! ainda não vi as novidades. mas, vamos baixar! www.nhforge.org
    October 23

    Apostila de C#

    Aos meus alunos do SENAC, baixem aqui: Apostila

    October 22

    Auditoria de dados, usando NHibernate

    O NHibernate possui alguns eventos padrão que nos permitem fazer alguma coisa quando um objetos é incluído, excluido ou carregado do banco de dados. São os chamados EventListeners, realmente não tem muita documentação sobre o assunto, então, fazer qualquer coisa sobre isso, demanda uma boa pesquisa. Comecei aqui a implementar algo, então, vou poupar vocês de algum trabalho.

    No meu caso, eu preciso registrar as mudanças de informações que uma determinada classe sofreu, e quem realizou a mudança. Isso deve ficar armazenado no banco de dados. Logo, defini a seguinte classe:

       1:  public class Historico
       2:      {
       3:          private Int64 _id;
       4:          private string _classe;
       5:          private string _alteracoes;
       6:   
       7:          public virtual Int64 Id
       8:          {
       9:              get { return _id; }
      10:              set{ _id = value;}
      11:          }
      12:   
      13:          public virtual string Classe
      14:          {
      15:              get{ return _classe;}
      16:              set{ _classe = value;}
      17:              
      18:          }
      19:   
      20:          public virtual string Alteracoes
      21:          {
      22:              get { return _alteracoes; }
      23:              set{ _alteracoes = value;}
      24:          }
      25:          
      26:          public  Historico()
      27:          {
      28:              Id = 0;
      29:              Classe = string.Empty;
      30:              Alteracoes = string.Empty;
      31:          }
      32:   
      33:          public Int64 IdObjeto { get; set; }
      34:          public string Acao { get; set; }
      35:          public DateTime Data { get; set; }
      36:          public string Usuario { get; set; }
      37:      }

     

    Nada complicado. Definimos aí uma propriedade Classe que irá armazenar o nome da classe que sofre alteração, uma propriedade Historico, que irá conter as alterações feitas, IdObjeto que guarda o Id do objeto que sofre alteração; Acao, que guarda o tipo de acontecido, Data que representa quando foi alterado e Usuario, que guarda o nome do usuario que fez a alteração. Como essa classe deve ser persistida, é preciso também fazer seu mapeamento. Não vou entrar em detalhes como fazer isso…  Agora chegou a hora de implementarmos os eventos.

    Para registrar o que foi alterado, é preciso implementar a interface IPostUpdateEventListener. Ela disponibiliza o método OnPostUpdate. Esse evento é disparado depois que o objeto foi atualizado. A seguir temos a classe de auditoria.

       1:   public class LogAlteracao : IPostUpdateEventListener
       2:      {
       3:          private const string _noValueString = " ";
       4:   
       5:          private static string getStringValueFromStateArray(object[] stateArray, int position)
       6:          {
       7:              var value = stateArray[position];
       8:   
       9:              return value == null || value.ToString() == string.Empty
      10:                      ? _noValueString
      11:                      : value.ToString();
      12:          }
      13:   
      14:          public void OnPostUpdate(PostUpdateEvent @event)
      15:          {
      16:              if (@event.Entity is Historico)
      17:              {
      18:                  return;
      19:              }
      20:   
      21:              var entityFullName = @event.Entity.GetType().FullName;
      22:   
      23:              if (@event.OldState == null)
      24:              {
      25:                  var session = @event.Session.GetSession(EntityMode.Poco);
      26:                  var sb = new StringBuilder();
      27:   
      28:                  for (int i = 0; i <= @event.State.Count() - 1; i++)
      29:                  {
      30:                      var newValue = getStringValueFromStateArray(@event.State, i);
      31:                      sb.AppendLine("Propriedadade: " + @event.Persister.PropertyNames[i]);
      32:                      sb.AppendLine("  Novo valor..: " + newValue);
      33:                      sb.AppendLine("-------------------------------");
      34:                  }
      35:   
      36:                  var historico = new Historico();
      37:                  historico.Classe = @event.Entity.GetType().Name;
      38:                  historico.IdObjeto = (Int64)@event.Id;
      39:                  historico.Acao = "Atualização";
      40:                  historico.Alteracoes = sb.ToString();
      41:                  historico.Data = DateTime.Now;
      42:                  session.Save(historico);                     
      43:              }
      44:              else
      45:              {
      46:                  var dirtyFieldIndexes = @event.Persister.FindDirty(@event.State, @event.OldState, @event.Entity,
      47:                                                                     @event.Session);
      48:                  
      49:                  var session = @event.Session.GetSession(EntityMode.Poco);
      50:                  var sb = new StringBuilder();
      51:   
      52:                  foreach (var dirtyFieldIndex in dirtyFieldIndexes)
      53:                  {
      54:                      
      55:                      //Aqui tem que testar se for component. 
      56:                      //Se for, tem que pegar as propriedades manualmente... por reflection
      57:                    if (@event.Persister.PropertyTypes[dirtyFieldIndex] is ComponentType)
      58:                      {
      59:                          // Recupera o estado atual e o anterior da propriedade.
      60:                          var obj = @event.State[dirtyFieldIndex];
      61:                          var objAnterior = @event.OldState[dirtyFieldIndex];
      62:   
      63:                          var propertyInfos = obj.GetType().GetProperties();
      64:                          foreach (var info in propertyInfos)
      65:                          {
      66:                              // Se a propriedade não pode ser lida, não faz nada.
      67:                              if (!info.CanRead)
      68:                                  continue;
      69:   
      70:                              try
      71:                              {
      72:                                  // Recupera os valor novo e o antigo da propriedade.
      73:                                  var novoValor = info.GetValue(obj, null); 
      74:                                  var valorAntigo = info.GetValue(objAnterior, null);
      75:   
      76:                                  // Verifica se são mesmo diferentes.
      77:                                  if (valorAntigo.ToString().Equals(novoValor.ToString()))
      78:                                      continue;
      79:   
      80:                                  sb.AppendLine("Propriedadade: " + info.Name);
      81:                                  sb.AppendLine("  Valor antigo: " + valorAntigo);
      82:                                  sb.AppendLine("  Novo valor: " + novoValor);
      83:                                  sb.AppendLine("-------------------------------");
      84:                              }
      85:                              catch
      86:                              {
      87:                                  continue;
      88:                              }
      89:                          }
      90:                      }
      91:                      else
      92:                      {
      93:                          var oldValue = getStringValueFromStateArray(@event.OldState, dirtyFieldIndex);
      94:                          var newValue = getStringValueFromStateArray(@event.State, dirtyFieldIndex);
      95:   
      96:                          if (oldValue == newValue)
      97:                          {
      98:                              continue;
      99:                          }
     100:   
     101:                          sb.AppendLine("Propriedadade: " + @event.Persister.PropertyNames[dirtyFieldIndex]);
     102:                          sb.AppendLine("  Valor antigo: " + oldValue);
     103:                          sb.AppendLine("  Novo valor..: " + newValue);
     104:                          sb.AppendLine("-------------------------------");
     105:                      }
     106:                  }
     107:   
     108:                  var historico = new Historico();
     109:                  historico.Classe = @event.Entity.GetType().Name;
     110:                  historico.IdObjeto = (Int64) @event.Id;
     111:                  historico.Acao = "Atualização";
     112:                  historico.Alteracoes = sb.ToString();
     113:                  historico.Data = DateTime.Now;
     114:                  session.Save(historico);
     115:                  session.Flush();
     116:              }
     117:          }
     118:      }

     

    Vamos dar uma entendida no código…. Todas informações que vamos precisar estão acessíveis no parêmetro PostUpdateEvent event. A primeira coisa que verificamos é se, o objeto que está vindo é Historico. Se for, não fazemos nada… senão ia ficar em loop, certo? Na sequência verificamos se o objeto possui um OldState. Essa propriedade representa o estado do objeto antes da alteração e só é preenchida se o objeto tiver sido carregado pelo Session que está em uso.

    Caso o objeto não tenha OldState, percorremos a propriedade State, que contém os valores atuais, e montamos com esses valores uma string para ser salva. Então instanciamos um objeto Historico e salvamos o objeto. Agora, se o objeto tem um OldState, podemos gerar histórico só do que foi alterado. Veja a linha 46. O NHibernate disponibiliza o método FindDirty, que retorna os índices das propriedades que sofreram alterações.  Aí é só montar novamente o texto e salvar…

    Quero apontar um detalhe lá na linha 57. Estamos verificando se a propriedade que vem é do tipo ComponentType. Quando no mapeamento de uma classe, mapeamos outra nela como Component, ela aparece nesse evento PostUpdate como uma única propriedade. Assim, é necessário usarmos reflection pra extrair as propriedades….. 

    Talvez precise melhorar algo aí nessa implementação, mas por enquanto… está funcionando :). Depois posto como registrar as exclusões.

     

     

    October 05

    Configurando o NHibernate por código

    Olá pessoal. Aqui temos a necessidade de flexibilizar a string de conexão do sistema, de tal forma que o usuário possa mudá-la a partir  de uma tela de configurações. Outro detalhe que prevemos, é a mudança de banco de dados sem a necessidade de recompilar o projeto. Levando tudo isso em consideração, mais a possibilidade de adicionar algum parâmetro de execução e habilitar ou não a exibição de SQL ou geração de estatísticas, resolver configurar o NHibernate por código. Contudo o desafio em fazer isso estava no fato de que utilizamos o projeto uNHaddins.

    Até o momento estávamos utilizando a classe DefaultSessionFactoryConfigurationProvider(),  responsável por carregar e configurar o NHibernate. A solução foi criar uma classe herdada de AbstractConfigurationProvider. Essa classe abstrata expoem os métodos necessários que devem ser implementados para que possam ter uma inicialização personalizada do NHibernate. Sendo assim temos a seguinte classe:

    /// <summary>
        /// Esta classe configura manualmente, por código, as propriedades do NHibernate.
        /// Estamos fazendo por código para facilitar a troca de connection string e até mesmo
        /// a troca de banco de dados.
        /// </summary>
        public class ByCodeConfigurationProvider: AbstractConfigurationProvider
        {
            private readonly string _connectionString;
            private Configuration _configuration;
    
            /// <summary>
            /// Construtor da classe. Recebe como parâmetro <paramref name="connectionString"/>
            /// </summary>
            /// <param name="connectionString">String de conexão do banco de dados em uso</param>
            public ByCodeConfigurationProvider(string connectionString)
            {
                _connectionString = connectionString;
            }
    
            private void ConfigureByCode()
            {
                if (_configuration == null)
                {
                    _configuration =  new Configuration();
                    _configuration.SetProperty("connection.driver_class","NHibernate.Driver.SqlClientDriver");
                    _configuration.SetProperty("hbm2ddl.auto","update");
                    _configuration.SetProperty("show_sql","true");
                    _configuration.SetProperty("connection.connection_string",@"Data Source=SRV2003NET\SQLEXPRESS;Initial Catalog=NotasBD;Persist Security Info=True;User ID=NotasMaster;Password=cont10rol");
                    _configuration.SetProperty("adonet.batch_size","10");
                    _configuration.SetProperty("dialect","NHibernate.Dialect.MsSql2005Dialect");
                    _configuration.SetProperty("use_outer_join","true");
                    _configuration.SetProperty("command_timeout","10");
                    _configuration.SetProperty("query.substitutions","true 1, false 0, yes 'Y', no 'N'");
                    _configuration.SetProperty("current_session_context_class","uNhAddIns.SessionEasier.Conversations.ThreadLocalConversationalSessionContext, uNhAddIns");
                    _configuration.SetProperty("proxyfactory.factory_class","NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle");
                    _configuration.SetProperty("use_proxy_validator","false");
                    _configuration.AddAssembly(Assembly.GetAssembly(typeof (Pessoa)));
                    
                    var eventos =   _configuration.EventListeners;
                    eventos.PreUpdateEventListeners = new List<IPreUpdateEventListener>(eventos.PreUpdateEventListeners){new EventListener()}.ToArray();
                    eventos.PreInsertEventListeners = new List<IPreInsertEventListener>(eventos.PreInsertEventListeners){new EventListener()}.ToArray();
                }
    
            }
    
    
            public override IEnumerable<Configuration> Configure()
            {
                ConfigureByCode();
                return new SingletonEnumerable<NHibernate.Cfg.Configuration>(_configuration);
            }
        }

    Como pode ser visto, o método ConfigureByCode instancia um objeto Configuration do NHibernate e utilizando o método SetProperty, configura suas propriedades. O construtor da classe recebe como parâmetro uma string que contém a connection string do banco, porém não estamos utilizando nesse momento. Mas o detalhe da flexibilização é que  como estamos fazendo tudo por código, é muito fácil ler algum arquivo de configuração e mudar essas propriedades como desejarmos, sem termos que recompilar código.

    Aqui centralizamos a carga de nossos serviços em uma classe tipo ServiceLocator, e nela executamos a carga do NHibernate assim:

     

    public class ServiceLocatorProvider
       {
           public static void Initialize()
           {
               var container = new WindsorContainer();
               container.AddFacility<PersistenceConversationFacility>();
    
               var nhConfigurator = new ByCodeConfigurationProvider("");
    
               var sfp = new SessionFactoryProvider(nhConfigurator);
    
               container.Register(Component.For<ISessionFactoryProvider>().Instance(sfp));
               container.Register(Component.For<ISessionWrapper>().ImplementedBy<SessionWrapper>());
               container.Register(Component.For<IConversationFactory>().ImplementedBy<DefaultConversationFactory>());
               container.Register(Component.For<IConversationsContainerAccessor>().ImplementedBy<NhConversationsContainerAccessor>());
               container.Register(Component.For<ISessionFactory>().Instance(sfp.GetFactory(null)));
    
               container.Register(Component.For(typeof(IDao<>)).ImplementedBy(typeof(BaseDao<>)).LifeStyle.Transient);
         var sl = new WindsorServiceLocator(container);
         container.Register(Component.For<IServiceLocator>().Instance(sl));
         ServiceLocator.SetLocatorProvider(() => sl);
      }
    }

     

    Segue aí a dica!

    September 28

    Gerenciamento de janelas em WPF

    Desenvolver uma aplicação comercial utilizando WPF é realmente muito legal! A plataforma nos oferece recursos de DataBinding que facilitam nossa vida como desenvolvedores e que permitem a criação de visualizações magníficas para os usuários finais. Mesmo com tantas facilidades ainda nos esbarramos em conceitos que não existem em WPF, como herança visual de XAML e até mesmo o conceito de MDI.

    Nota: Neste post, não entrarei nos detalhes, contudo, o código estará disponível.

    Aqui em nossa aplicação um dos requisitos era montar uma interface MDI, que pemitisse que um usuário pudesse abrir várias janelas de forma simultânea e até mesmo a mesma janela mais de uma vez. A solução desevolvida foi a de abstrair o conceito de “janela gerenciada” de tal forma que é possível indicar ao sistema qual janela será gerenciada ou não. Posteriormente foi necessário se criar um mecanismo que mantivesse o estado dessas janelas gerenciadas. Essas janelas são mantidas em uma lista que pode ser exibida ao usuário e ele, ao clicar em um item dessa lista, traz a janela selecionada para o foco ativo. Bem vamos ver algum código. 

    Nota: Esta solução que apresento foi inspirada por este post, apresentando um código parecido, contudo foi ajustado para minha realidade… Logo irei postar tudo detalhado no Code Project.

    Tenho uma classe que irá gerenciar as janelas, a classe WindowViewStateManager. Ela herda de ViewModel, que é um tipo declarado pelo framework Julmar que facilita o uso padrão M-V-VM. Se você desenvolve em WPF sem utilizar esse padrão (ou algo similar), corra!!! Por que seu código deve estar virando uma bela macarronada!

    Nessa classe tenho a lista de janela que foram abertas:

    public ObservableCollection<WindowViewStateInstance> WindowViewStates
            {
                get{ return_windowViewStates ?? (_windowViewStates = newObservableCollection<WindowViewStateInstance>()); }
            }

    Agora como que uma janela pode ser adicionada à essa lista? Para isso criamos uma Attached Property que inclue esse comportamento em nossas janelas. Ela está definida na classe a seguir:

     

    public class WindowViewState
    {
        public static bool GetIsManaged(DependencyObject obj)
        {
            return (bool) obj.GetValue(IsManagedProperty);
        }
    
        public static void SetIsManaged(DependencyObject obj, bool value)
        {
            obj.SetValue(IsManagedProperty, value);
        }
    
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsManagedProperty =
            DependencyProperty.RegisterAttached("IsManaged", typeof (bool), typeof (WindowViewState),
                                                new FrameworkPropertyMetadata((bool) false,
                                                                              new PropertyChangedCallback(
                                                                                  OnIsManagedChanged)));
    
        private static void OnIsManagedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Window target = (Window) d;
            if (target != null)
            {
                target.Loaded += new RoutedEventHandler(target_Loaded);
                target.Closed += new EventHandler(target_Closed);
    
            }
        }
        
    
        private static void target_Closed(object sender, EventArgs e)
        {
            Window target = (Window) sender;
            if (target != null)
            {
                WindowViewStateManager.Instance.RemoveWindowViewState(target);
                WindowViewStateManager.Instance.ShowLastWindow();
            }
        }
    
        private static void target_Loaded(object sender, RoutedEventArgs e)
        {
            Window target = (Window) sender;
            if (target != null)
            {
                WindowViewStateInstance instance = new WindowViewStateInstance();
                instance.Title = target.Title;
                instance.Icon = new VisualBrush(target);
                instance.Window = target;
                instance.Number = (WindowViewStateManager.Instance.CountInstancesForWindowTitle(target.Title) + 1);
                instance.Window.Tag = instance.Number;
                WindowViewStateManager.Instance.HideAllVisible(instance.WindowId);
                WindowViewStateManager.Instance.WindowViewStates.Add(instance);
            }
        }
    }

    Diferentemente da solução original, o meu requisito exige que mesmo podendo abrir mais de uma janela, apenas uma será visível. Isso eu trato no método target_Loaded, que é disparado sempre que uma janela é carregada. Isso é feito pelo método HideAllVisible. Na verdade esse método não “esconde” as janelas, pois ao escondê-las, o VisualBrush da mesma também desaparece. Veja que uso o VisualBrush da janela para que posteriormente tenhamos uma lista das janelas abertas sendo exibidas de forma visual mesmo! Como pode ser visto aqui:

    Veja lá ao fundo a miniatura das janelas, é exatamente isso que estamos fazendo. Voltando ao esconder janelas, então, para que sua miniatura também não desapareça, apenas movo a posição da janela para uma área não visível e, posteriormente quando for necessário exibí-la novamente, a trago para o centro do monitor:

    public void HideAllVisible(int WindowHashCode)
    {
        foreach (var view in _windowViewStates)
        {
            if (view.WindowId != WindowHashCode)
            {
                view.Window.Left = -3000;
            }
        }
    }
    private void CenterWindow(Window w)
     {
         var width = (SystemParameters.PrimaryScreenWidth / 2);
         var tamanhojanela = w.Width / 2;
    
         var height = SystemParameters.PrimaryScreenHeight / 2;
         var alturajanela = w.Height / 2;
         w.Left = width - tamanhojanela;
         w.Top = height - alturajanela;
     }

    Ao fechar uma janela, a removemos da lista e pedimos para que a última que foi aberta, fique ativa novamente:

    private static void target_Closed(object sender, EventArgs e)
    {
        Window target = (Window) sender;
        if (target != null)
        {
            WindowViewStateManager.Instance.RemoveWindowViewState(target);
            WindowViewStateManager.Instance.ShowLastWindow();
        }
    }
    public bool RemoveWindowViewState(Window window)
    {
        var view = (from v in _windowViewStates where (v.Title == window.Title) && (v.Number.ToString() == window.Tag.ToString()) select v).First();
        if (view != null)
        {
            if (_windowViewStates.Count == 1)
            {
                view.Window.IsEnabled = true;
                view.Window.Owner.Activate();
                view.Window.Owner.Focus();
            }
            _windowViewStates.Remove(view);
            view = null;
            GC.Collect();
            return true;
        }
        return false;
    }
    public void ShowLastWindow()
    {
        if (_windowViewStates.Count > 0)
        {
            var w = _windowViewStates[_windowViewStates.Count - 1];
            CenterWindow(w.Window);
        }
    
    }

    Para testar tudo isso foi criado um estilo para um ListBox, onde ele fique na horizontal. Ele irá exibir o conteúdo da lista de janelas gerenciadas. Antes, para definir que uma janela é gerenciada, fazemos uso da AttachedProperty:

    <Window x:Class="Test.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WindowViewState;assembly=WindowViewState"
        Title="Window2" Height="300" Width="300" Background="Aqua" local:WindowViewState.IsManaged="true">
        <Grid>
            <Button Margin="90,109,114,130" Name="button1">Button</Button>
        </Grid>
    </Window>

    Depois, no construtor da janela principal do projeto de teste, ligamos o datacontext do ListBox à lista de janelas abertas:

    public Window1()
           {
               InitializeComponent();
               WindowsList.DataContext = WindowViewStateManager.Instance;
           }

    E lá no ListBox utilizo um recurso do Framework Julmar para ligar o evento de SelectionChanged ao comando definido em WindowViewStateManager.

    <ListBox x:Name="WindowsList"
                    DockPanel.Dock="Top" 
                    Style="{DynamicResource WindowsListTemplate}"
                    ItemsSource="{Binding Path=WindowViewStates}" 
                    ItemContainerStyle="{DynamicResource WindowsListItem}"  
                    IsSynchronizedWithCurrentItem="True" 
                    SelectedItem="{Binding Path=SelectedWindow}"
                    ItemTemplate="{StaticResource WindowsListDataTemplate}" 
                    Height="140">
                    <julmar:EventCommander.Mappings>
                        <julmar:CommandEvent Command="{Binding CmdSetFocusWindow}" Event="SelectionChanged" />
                    </julmar:EventCommander.Mappings>
                </ListBox>
     
    public ICommand CmdSetFocusWindow { get; private set; }
    public ICommand CmdCloseWindow { get; private set; }
    private WindowViewStateManager()
    {
        CmdCloseWindow = new DelegatingCommand(CloseWindow, CanCloseWindow);
        CmdSetFocusWindow = new DelegatingCommand(FocusWindow, CanFocusWindow);
    }
    private void FocusWindow()
    {
        if (SelectedWindow !=null)
        SetFocusWindow(SelectedWindow.WindowId);
    }
    public bool SetFocusWindow(int WindowHashCode)
    {
        var view = (from v in _windowViewStates where v.WindowId == WindowHashCode select v).First();
        HideAllVisible(view.WindowId);
        CenterWindow(view.Window);
        return view.Window.Activate();
    }

    Com isso temos um gerenciador de janelas funcional :)  Baixem o código, estudem e comentem !! Sei que aqui não entrei em detalhes profundos, mas qualquer dúvida, é só entrar em contato. Abraço!!

    September 16

    Eu sumi não é???

    Pessoal, sumi, eu sei...
    Mas estou preparando material sobre WPF, legal!  Por exemplo, como simular herança de janelas em WPF...




    June 08

    WPF e Linq, melhoranda a interface com o usuário

    Acabou de ser publicado meu segubndo artigo no www.codeproject.com, onde mostro como criar um controle que realiza uma pesquisa nos itens de um menu.

    Veja o post: Improving user experience using WPF and LINQ

    June 04

    WPF: Efeito de fade nos controles de uma janela

    No WPF é possivel criar estilos para todo controle visual. Hoje quero mostrar como fazer um estilo que fará com que o controle onde ele está aplicado faça um fade ao ser carregado. Ou seja, qdo a Window abrir, seus controles irão surgir aos poucos. O style pode ser definido como a seguir:

    <Page.Resources>
           <Style x:Key="FadeIn" TargetType="FrameworkElement">
               <Style.Triggers>
                   <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                       <BeginStoryboard>
                           <Storyboard>
                               <DoubleAnimation
                   Storyboard.TargetProperty="(FrameworkElement.Opacity)"
                   From="0.0" To="1.0" Duration="0:0:3"/>
                           </Storyboard>
                       </BeginStoryboard>
                   </EventTrigger>
               </Style.Triggers>
           </Style> 
    </Page.Resources>

    Nota: aqui coloquei o estilo em um resource da página que estou usando, mas é totalmente indicado que se crie arquivos de resource para essas situações.

    Para utilizar esse estilo:

    <TextBlock Name="Message" Text="This is a test." Margin="5,5,5,5"  Style="{StaticResource FadeIn}"/>

    <TextBlock Name="Message2" Text="This is a test2." Margin="5,5,5,5"  Style="{StaticResource FadeIn}"/>
    <TextBox Width="100" Height="30" Margin="5,5,5,5"  Style="{StaticResource FadeIn}"/>
    <Button Content="Fade" Margin="5,5,5,5"  Style="{StaticResource FadeIn}"/>

    Como nosso style tem como target o tipo FrameworkElement, podemos aplicá-lo até mesmo em um stackpanel e automaticamente todo seu conteúdo sofrerá o fade:

    <StackPanel Orientation="Horizontal" Grid.Row="0" VerticalAlignment="Center" Style="{StaticResource FadeIn}">
                <TextBlock Name="Message" Text="This is a test." Margin="5,5,5,5"  />
                <TextBlock Name="Message2" Text="This is a test2." Margin="5,5,5,5"  />
                <TextBox Width="100" Height="30" Margin="5,5,5,5"  />
                <Button Content="Fade" Margin="5,5,5,5" />
    </StackPanel>

    Fica aí a dica!

    May 21

    Suas classes são amigas do DataBinding?

     

    Atualmente estou trabalhando com WPF… e uma das coisas que mais gosto é o mecanismo de DataBinding… simplesmente fantástico!

    Mas

    para que seus objetos de negócio possam ser utilizados adequadamente pelo databinding, é necessário que sejam implementadas algumas interfaces…

    São elas:

    • INotifyPropertyChanged – Notifica as mudanças de valores em propriedades
    • IEditableObject – Permite que um objeto se torne “editável”, podendo desfazer alterações
    • IDataErrorInfo – Adiciona recursos de validação aos objetos, permitindo que a UI seja notificada disso e possa apresentar adequadamente essa situação

     

    Deixo aqui pra vcs a minha implementação disso tudo. Essas classes são utilizadas por mim como base de meus objetos… espero que ajudem a todos que estejam começando com esse incrível framework que é o WPF.

    Infelizmente não consigo entrar no SkyDrive… acho que é Telefônica… mas fiz o upload para o Rapidshare:

    Baixem aqui!!!

    Abraço!

    April 23

    Imperdível!!! Ayende palestrando sobre ActiveRecord

     

    Vale, e muito, assistir!

    March 26

    Relacionamentos MxN com NHibernate

    Todos sabemos que manter um relacionamento MxN é chato. E claro que ao utilizar uma ferramenta ORM podemos minimizar essa dor de cabeça.  Imaginem que em seu banco de dados exista a seguinte situação:

     

    digrama-sql

    Nosso modelo de objetos está da seguinte forma:

    ClassDiagram1

    Observe que em nenhum momento temos alguma classe que representa a tabela associativa TURMA_X_ALUNO. É no mapeamento das classes que vamos indicar a existência dessa tabela e informar ao NHibernate que a coleção Turmas e Alunos devem utilizá-la. Vamos ver primeiramento o mapeamento da classe Aluno.

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Dominio"  namespace="Dominio" >
      <class name="Aluno" table="ALUNO" >
        <id name="Id" column="ID_ALUNO" type="Int32" unsaved-value="0">
            <generator class="hilo"/>
        </id>
        <property name="Nome" column="NOME" type="string" length="100" not-null="true" />
        <idbag name="Turmas" table="TURMA_X_ALUNO" generic="true" lazy="true" cascade="save-update" inverse="true">
          <collection-id type="Int32" column="ID_TURMA_X_ALUNO">
            <generator class ="hilo"/>
          </collection-id>
          <key column="ID_ALUNO" />
          <many-to-many class="Turma" column="ID_TURMA"/>
        </idbag>

      </class>
    </hibernate-mapping>

    Destaquei em azul a parte que nos interessa. Veja que utilizamos uma idbag para representar a coleção de  Turmas. Ela foi utilizada porque nossa tabela associativa possui uma chave primária própria, que está mapeada na tag colection-id. Em vermelho estão destacadas coisas importantes também. Definimos que a tabela que mantém a coleção de Turmas é nossa tabela associativa e informamos ao NHibernate que não é por aqui que vamos realizar as associações. Isso quer dizer que não vamos adicionar turmas aos alunos mas sim, alunos às turmas existentes. Então se eu fizer algo assim:

    Aluno.Turmas.Add(novaTurma);

    Essa nova associação não será salva, porque a marcamos com inverse=true. Depois dizemos que essa mesma associação é many-to-many, informando também de qual classe. Dessa forma, automaticamente o NHibernate já sabe como preencher a lista Turmas.   Quanto ao mapeamento de Turma, é semelhante, veja.

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Dominio"  namespace="Dominio" >
      <class name="Turma" table="TURMA" >
        <id name="Id" column="ID_TURMA" type="Int32" unsaved-value="0">
          <generator class="hilo"/>
        </id>
        <property name="Descricao" column="DESCRICAO" type="string" length="100" not-null="true" />

        <idbag name="Alunos" table="TURMA_X_ALUNO" generic="true" lazy="true" cascade="save-update">
          <collection-id type="Int32" column="ID_TURMA_X_ALUNO">
            <generator class ="hilo"/>
          </collection-id>

          <key column="ID_TURMA" />
          <many-to-many class="Aluno" column="ID_ALUNO"/>
        </idbag>
      </class>
    </hibernate-mapping>

    A diferença é que não especificamos aqui a tag inverse=true  é porque é por aqui que vamos realizar as associações. Veja um teste feito:

                Configuration cfg = new Configuration();
                cfg.Configure();
                cfg.AddAssembly(typeof(Aluno).Assembly);
                ISessionFactory factory = cfg.BuildSessionFactory();
                ISession session = factory.OpenSession();
                ITransaction transaction = session.BeginTransaction();

                Aluno objAluno1 = new Aluno();
                objAluno1.Nome = "Paulo Quicoli";

                Aluno objAluno2 = new Aluno();
                objAluno2.Nome = "Sem Nome";

                session.Save(objAluno1);
                session.Save(objAluno2);

                Turma objTurma = new Turma();
                objTurma.Descricao = "Teste de MxN";

                objTurma.Alunos.Add(objAluno1);
                objTurma.Alunos.Add(objAluno2);

                session.Save(objTurma);
                try
                {
                    transaction.Commit();
                    session.Flush();
                }
                catch
                {
                    transaction.Rollback();
                }
                finally
                {
                    session.Close();
                    Console.ReadLine();
                }

    É muito simples manter esse tipo de relacionamento com o NHibernate, o detalhe como sempre, é acertar no mapeamento.   Observe também que não estou usando mais ids do tipo identity, após recomendação do meu último post…

    Abraço a todos…

    March 20

    Evite o uso do recurso Identity do SQLServer quando utilizar NHibernate

    Hoje Tuna Toksoz fez um post interessante no site do NHibernate. Ele explica porque evitar o uso do Identity. Vou resumir o post aqui.

    Basicamente quando se usa o Identiy, o recurso de batching do NHibernate fica impossibilitado de ser aplicado. Ele mostra isso utilizando os seguintes códigos:

       1:  [Test]
       2:  public void Should_not_insert_entity_in_a_transaction_HiLo()
       3:  {
       4:      var post = new PostWithHiLo {Title = "Identity Generators Revealed"};
       5:      var postComment = new PostCommentWithHiLo { Post = post, Comment = "Comment" };
       6:      using (ISession session = factory.OpenSession())
       7:      using (var tran = session.BeginTransaction())
       8:      {
       9:          session.Save(post); //No commit here
      10:          session.Save(postComment);
      11:          long insertCount = factory.Statistics.EntityInsertCount;
      12:          Assert.That(insertCount, Is.EqualTo(0), "Shouldn't insert entity in a transaction before commit.");
      13:      }
      14:  }
      15:   
      16:  [Test]
      17:  public void Should_not_insert_entity_in_a_transaction_Identity()
      18:  {
      19:      var post = new PostWithIdentity {Title = "Identity Generators Revealed"};
      20:      var postComment = new PostCommentWithIdentity {Post = post, Comment = "Comment"};
      21:      using (ISession session = factory.OpenSession())
      22:      using (var tran = session.BeginTransaction())
      23:      {
      24:          session.Save(post);
      25:          session.Save(postComment);
      26:          long insertCount = factory.Statistics.EntityInsertCount;
      27:          Assert.That(insertCount, Is.EqualTo(0), "Shouldn't insert entity in a transaction before commit.");
      28:      }
      29:  }

    No primeiro teste, que não utiliza Identity, nada é enviado para o banco. Já no segundo teremos 2 comandos INSERT enviados ao banco.  Isso significa que podemos enfrentar problemas de performance, já que o banco será acessado várias vezes.

    Pra finalizar ele mostra um último exemplo:

       1:  using (ISession session = factory.OpenSession())
       2:  using (var tran = session.BeginTransaction())
       3:  {
       4:      for (int i = 0; i < 3; i++)
       5:      {
       6:          var post = new PostWithHiLo {Title = string.Format("Identity Generators Revealed {0}", i)};
       7:          session.Save(post);
       8:      }
       9:      tran.Commit();
      10:  }

    Neste caso os três comandos INSERT que são gerados, só são enviados após o commit e de uma vez só ao banco. Se estivéssemos utilizando Identity, a cada Save teríamos um INSERT sendo enviado.

    Para ver o post completo, acesse o link: http://nhforge.org/blogs/nhibernate/archive/2009/03/20/nhibernate-poid-generators-revealed.aspx

    Até mais!

    February 12

    O WPF está pronto para aplicações LOB?

    Antes que eu comece, talvez você esteja se perguntando o que é a sigla LOB. LOB são as iniciais de Line of Business. Quando é dito que uma aplicação é LOB, dizemos que ela segue a linha de uma aplicação comercial. Refazendo então a pergunta, o wpf está pronto para aplicações comerciais?.

    Vamos rever um pouco a história. Tínhamos cartões perfurados, depois teclados, monitores de fósforo branco, depois verdes e até laranjados. O mouse então ganhou seu espaço…. lembram de um programa chamado DOSSHELL? Era uma espécie de windows explorer do DOS, muito legal para a época.

    Hoje ainda temos o mouse, mas a interação do usuário com um sistema informatizado está mudando. Temos webcams, videos, sons, telas sensíveis ao toque, até ao multi-toque. Os monitores estão cada vez maiores, em alta definição, internet, GPS, wireless. A imersão do usuário em relação ao computador evoluiu em muito nos últimos 15 ou 20 anos.

    Acredito que a Microsoft percebeu isso e viu algo no futuro, alguns anos a frente e decidiu apostar em algo novo, substituindo sua plataforma gráfica. Trocando o GDI pelo WPF. O GDI possui suas limitações, a principal que vejo é estar preso a pixels. O WPF por sua vez é independente de resolução e ainda pode utilizar o poder de processamento da placa gráfica instalada no micro para renderizar a interface de um aplicativo.

    O WPF também oferece recursos necessários para se criar aplicações que satisfaçam as necessidades visuais dos usuários. Ainda temos aí o windowsforms e vamos continuar a tê-lo por um bom tempo. Mas até quando? Creio que aos poucos ele vai deixar de ser utilizado, mas não por determinação da Microsoft e sim pelas escolhas dos usuários finais.

    Imagine que você vai comprar um carro. Você vê um Toyota e um Civic. Ambos são similares em qualidade, recursos e segurança e digamos, tem o mesmo preço. Qual você compraria? Com certeza o que você acha mais bonito. Com as aplicações vai acontecer a mesma coisa. Se você possui uma máquina razoável, e lhe é apresentado o mesmo sistema teoricamente só que um utilizando formulários comuns, e outro usando WPF, permitindo requintes de visualização que melhoram a interatividade com o usuário, com qual você ficaria?

    Nos EUA já existe uma grande adoção pela tecnologia. Até mesmo a própria Microsoft a está adontando, como prova de sua estabilidade e segurança. Sabe a nova versão do Windows Live Messenger? É feita com WPF. Até mesmo o Dynamics, um CRM/ERP da Microsoft, já possui módulos em WPF. Essa adesão está tomando lugar.

    Outro ponto que me chama a atenção está por parte da comunidade de usuários. Em suas listas de discussão muito se fala em como fazer  a coisa certa. Separar as camada de interface da camada de lógica. Um padrão de projeto, utilizado no desenvolvimento do Blend, é divulgado e espalhado pela comunidade, o MVVM (Model-View-ViewModel). Quando você viu algo assim no início do windowsforms?

    Por isso digo, sim. O WPF está pronto para o desenvolvimento de aplicações comerciais. Temos o suporte de uma comunidade muito ativa, componentes, mesmo que de terceiros. Contudo isso não significa que vamos sair migrando tudo, mas, se você têm um projeto para os próximos 3 ou 5 anos, comece já em WPF. Isso porque a oferta desenvolvedores nessa área estará maior, creio eu.

    Agora existe um ponto importante. Você desenvolvedor, que não é designer… não fique preso apenas aos recursos visuais que o WPF oferece. Ou, contrate algum designer para fazer a interface pra você. Com WPF isso é facilmente possível. Acredito que cada um deve estar no seu lugar. Um exemplo de uma aplicação WPF é o próprio Windows Live Messenger. Une uma boa interface com uma usabilidade legal. Agora, um exempo de um não tão bom programa WPF para o usuário final é o *Chirp, ou agora chamado Blu. Ele é um cliente twitter, segue o link pra vocês baixarem: http://www.thirteen23.com/experiences/desktop/blu/

    Mas porque ele não é bom? Veja a Figura abaixo. O primeiro programa é o Blu. O segundo é o TwitterFox.

    Bem, não dá nem pra comparar o visual não é? Mas e a experiência do usuário, a usabilidade da coisa? Em seu blog, Tamir Khason apresenta uma lista dos problemas do Blu:

    • Utiliza 140MB  de memória;
    • Não é possivel ocultá-lo 
    • Não notifica novo twitters sem ter que exibir a janela principal do programa
    • É necessário presscionar um botão update para ser notificado 
    • If you not finished typing, you can either dismiss all text of post it.
    • Não é possível digitar mais de 140 caracteres. Se você cola um texto maior, o texto é truncado.
    • Para operar o programa, é exigido o mouse
    • Não existe uma scrollbar para a lista de twitters exibidos, ou seja, você não pode correr para o último. É preciso passsar um a um.

    Vamos ao TwitterFox:

    • 10MB de RAM apenas
    • Ao clicar no “X” ou pressionar ESC, ele é ocultado 
    • Oferece recurso que notifica de novos twitters sem ter que abrir a tela principal
    • Once focused text are become active, expanded automatically and ready to write
    • Você pode digitar mais de 140 caracteres, contudo ele avisa qdo o limite é excedido permitindo que você ajuste o texto.
    • Pode ser operado via teclado.

    Para mais detalhes dos problemas funcionais do blu acesse este post: http://khason.net/blog/line-of-business-vs-beautifulness-or-two-dogmas-comparison-as-exemplified-by-two-twitter-applications/ .

    Portanto, uma coisa é fato. A beleza de uma aplicação não é tudo… o WPF veio literalmente unir o útil ao agradável. Uma boa aparência com uma grande usabilidade.

    Pensem nisso, e comentem, sintam-se à vontade.

    February 10

    WPF em aplicações comerciais

    Se você está desevolvendo em WPF, segue aqui um ótimo exemplo:

    Essa aplição é fruto de um treinamento acontecido lá fora (USA) explicando o padrão M-V-VM e como utilizá-lo de forma eficiente. Mais detalhes a respeito deste demo, acesse o site do Jaime Rodriguez.

    Os links pra download:

    Apresentação

    Código Fonte

    Em breve darei minhas considerações sobre o uso do WPF em aplicações comerciais….

    January 21

    Festival de artigos na DevMedia !!!

    O grupo DevMedia, responsável por várias revistas técnicas de informática, incluindo a .NET Magazine, está promovendo um concurso muito legal.

    Você pode escrever um artigo técnico, seja C#, Java, Delphi, SQL… o que você souber de melhor, e envia para a DevMedia.  Os melhores autores ganharão vários prêmios… (leiam aí um PS3 !!!)

    Não percam!!!!!

    Mais detalhes aqui

    November 20

    Telerik OPENACCESS ORM

    Pessoal, descobri hoje que a Telerik possui uma ferramenta ORM. O OPENACCESS, e o mais legal, possui uma versão free. Essa versão free possui todos os recursos da versão paga, a única “limitação” é trabalhar apenas com os seguintes bancos free: MS SQL Server Express, Oracle Express, MySQL e Firebird.

    Alguns recursos:

    • Assistentes integrados ao VS2008;
    • Lazy Loading;
    • Estratégias de cache;
    • Estratégias de Fetching;
    • Suporte ao Linq;
    • Mapeamento a partir do seu modelo de classes ou a partir de um banco de dados.

    Sou fã de carteirinha do NHibernate mas vou dar uma olhadinha nisso :)

    Mais detalhes em: http://www.telerik.com/products/orm/features.aspx

    November 11

    Palestra sobre NHibernate

    Nesta semana está acontecendo na UNAERP, em Ribeirão Preto/SP, a 12ª semana da tecnologia. Ontem pude realizar uma palestra sobre NHibernate. Quero agradecer a todos que compareceram e à comissão organizadora.

    Infelizmente o tempo não foi suficiente pra mostrar tudo o que gostaria, de qualquer forma, estou aqui para retirar qualquer dúvida.

    obrigado!

    October 31

    LINQ to SQL..... acabou


    É pessoal... a Microsoft vai se dedicar mais ao Entity Framework e por isso "não tem espaço pro Linq To SQL". Estão até sugerindo disponibilizá-lo no Codeplex.
    Imagino como não deve estar o pessoal que abraçou a idéia e desenvolveu utilizando ele....Eu continuo com meu NHibernate , sem ter que me preocupar  se ele "vai sair do ar"...

    Mais detalhes aqui (inglês):

    Blog do time do ADO.Net
    Blog do David Hayden
    Idéias do que pode acontecer com o Linq to SQL
    October 29

    WPF + (M-V-VM) + NHibernate + NHValidator = Quarteto Fantástico!

    Acabei de postar um artigo no  www.codeproject.com, mostrando como colocar tudo isso funcionando.

    Vejam o artigo aqui: Artigo do codeproject
    October 21

    WPF + Model-View-ViewModel + NHibernate

    Estou liberando aqui um pequeno exemplo que mostra como utilizar o NHibernate em um aplicação WPF, empregando o padrão M-V-VM. Além disso utilizo também uma implementação do padrão Repository e Unit of Work, ambos descritos pelo NHibernate FAQ. Este demo utiliza a versão Embedded do Firebird 2.1 e para rodar é necessário apenas um pequeno ajuste no arquivo aliases.conf, localizado na pasta debug da aplicação. O ajuste a ser feito é apenas informar o path correto da localização do banco de dados. Qualquer dúvida é só deixar um recado aqui.

    PS.: Estou com o tempo curto, por isso não fiz um post explicando como tudo foi feito….