• R/O
  • SSH
  • HTTPS

zostportal: 提交


Commit MetaInfo

修訂115 (tree)
時間2021-08-10 04:40:44
作者derekwildstar

Log Message

Backup pós atualização

Change Summary

差異

--- trunk/Backup/zost.sql (revision 114)
+++ trunk/Backup/zost.sql (revision 115)
@@ -384,7 +384,7 @@
384384
385385 LOCK TABLES `joomla_banners` WRITE;
386386 /*!40000 ALTER TABLE `joomla_banners` DISABLE KEYS */;
387-INSERT INTO `joomla_banners` (`id`, `cid`, `type`, `name`, `alias`, `imptotal`, `impmade`, `clicks`, `clickurl`, `state`, `catid`, `description`, `custombannercode`, `sticky`, `ordering`, `metakey`, `params`, `own_prefix`, `metakey_prefix`, `purchase_type`, `track_clicks`, `track_impressions`, `checked_out`, `checked_out_time`, `publish_up`, `publish_down`, `reset`, `created`, `language`, `created_by`, `created_by_alias`, `modified`, `modified_by`, `version`) VALUES (28,9,1,'Google AdSense - ZOST: (468x60)','google-adsense-zost-468x60',0,890276,0,'',1,111,'','<script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\r\n<!-- ZOST: Banner (234x60) (L) --><!-- ZOST: Banner (234x60) (R) -->\r\n<div style=\"padding-left: 301px; padding-right: 301px;\">\r\n<div style=\"background-color: rgba(255, 255, 255, 0.9);\">\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:234px;height:60px; float: left\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"8451446418\"></ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:234px;height:60px; float: right\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"6048219328\"></ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>\r\n</div>\r\n</div>\r\n<!-- ZOST: Banner (468x60) -->\r\n<div style=\"padding-left: 301px; padding-right: 301px;\">\r\n<div style=\"background-color: rgba(255, 255, 255, 0.9);\">\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:468px;height:60px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"1700791547\"></ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>\r\n</div>\r\n</div>\r\n\r\n\r\n\r\n',0,1,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','2016-10-22 00:00:00','2016-09-22 23:43:59','*',24,'','2018-12-03 17:20:04',24,46),(32,9,1,'Google AdSense - ZOST: (200x200) #1','google-adsense-zost-200x200',0,890319,0,'',1,112,'','<div style=\"margin-left: 9px; margin-right: 9px; background-color: rgba(255,255,255,0.90)\">\r\n<script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\r\n<!-- ZOST: 200x200 -->\r\n<ins class=\"adsbygoogle\"\r\n style=\"display: contents;width:200px;height:200px\"\r\n data-ad-client=\"ca-pub-3110076814260873\"\r\n data-ad-slot=\"8448342346\"></ins>\r\n<script>\r\n(adsbygoogle = window.adsbygoogle || []).push({});\r\n</script>\r\n</div>',0,1,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','2017-08-10 00:00:00','2017-07-10 19:05:13','*',24,'','2020-05-29 00:21:56',24,8),(33,9,1,'Google AdSense - ZOST: (200x200) #2','google-adsense-zost-200x200-2',0,890566,0,'',1,112,'','<div style=\"margin-left: 9px; margin-right: 9px; background-color: rgba(255,255,255,0.90)\">\r\n<script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\r\n<!-- ZOST: Banner (200x200) #2 -->\r\n<ins class=\"adsbygoogle\"\r\n style=\"display: contents;width:200px;height:200px\"\r\n data-ad-client=\"ca-pub-3110076814260873\"\r\n data-ad-slot=\"2605418240\"></ins>\r\n<script>\r\n(adsbygoogle = window.adsbygoogle || []).push({});\r\n</script>\r\n</div>',0,2,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','2018-12-03 17:21:19','0000-00-00 00:00:00','2019-01-03 00:00:00','2018-12-03 17:20:27','*',24,'','2020-05-29 00:23:54',24,4),(34,9,1,'Banner 200x200 (Basic Black) #1','banner-200-200-basicblack-1',0,1175999,0,'',1,115,'','<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"> </script>\r\n<!-- ZOST: Banner (200x200) #1 -->\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:200px;height:200px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"8448342346\"> </ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>',0,1,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','2020-06-26 00:00:00','2020-05-26 23:32:16','*',24,'','2020-06-29 21:13:20',24,20),(36,9,1,'Banner 200x200 (Basic Black) #2','banner-200-200-basicblack-2',0,1175977,0,'',1,116,'','<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"> </script>\r\n<!-- ZOST: Banner (200x200) #2 -->\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:200px;height:200px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"2605418240\"> </ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>',0,15,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','2020-06-29 00:00:00','2020-05-29 01:38:00','*',24,'','2020-06-29 21:13:10',24,6),(37,9,1,'Banner 728x90 (Basic Black) #1','banner-728x90-basicblack-1',0,337327,0,'',1,117,'','<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"> </script>\r\n<!-- ZOST: Banner (728x90) -->\r\n<ins class=\"adsbygoogle\" style=\"display:contents;width:728px;height:90px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"6443009564\"> </ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>',0,1,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','2020-06-03 23:26:11','0000-00-00 00:00:00','2020-07-03 00:00:00','2020-06-03 23:24:36','*',24,'','2020-06-28 21:29:47',24,13),(38,9,1,'Banner 728x90 (Basic Black) #2','banner-728x90-basicblack-2',0,337340,0,'',1,118,'','<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"> </script>\r\n<!-- ZOST: Banner (728x90) #2 -->\r\n<ins class=\"adsbygoogle\" style=\"display:contents;width:728px;height:90px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"7210184376\"> </ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>',0,17,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','2020-06-03 23:26:11','0000-00-00 00:00:00','2020-07-03 00:00:00','2020-06-03 23:47:06','*',24,'','2020-06-28 21:30:12',24,5);
387+INSERT INTO `joomla_banners` (`id`, `cid`, `type`, `name`, `alias`, `imptotal`, `impmade`, `clicks`, `clickurl`, `state`, `catid`, `description`, `custombannercode`, `sticky`, `ordering`, `metakey`, `params`, `own_prefix`, `metakey_prefix`, `purchase_type`, `track_clicks`, `track_impressions`, `checked_out`, `checked_out_time`, `publish_up`, `publish_down`, `reset`, `created`, `language`, `created_by`, `created_by_alias`, `modified`, `modified_by`, `version`) VALUES (28,9,1,'Google AdSense - ZOST: (468x60)','google-adsense-zost-468x60',0,890276,0,'',1,111,'','<script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\r\n<!-- ZOST: Banner (234x60) (L) --><!-- ZOST: Banner (234x60) (R) -->\r\n<div style=\"padding-left: 301px; padding-right: 301px;\">\r\n<div style=\"background-color: rgba(255, 255, 255, 0.9);\">\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:234px;height:60px; float: left\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"8451446418\"></ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:234px;height:60px; float: right\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"6048219328\"></ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>\r\n</div>\r\n</div>\r\n<!-- ZOST: Banner (468x60) -->\r\n<div style=\"padding-left: 301px; padding-right: 301px;\">\r\n<div style=\"background-color: rgba(255, 255, 255, 0.9);\">\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:468px;height:60px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"1700791547\"></ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>\r\n</div>\r\n</div>\r\n\r\n\r\n\r\n',0,1,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','2016-10-22 00:00:00','2016-09-22 23:43:59','*',24,'','2018-12-03 17:20:04',24,46),(32,9,1,'Google AdSense - ZOST: (200x200) #1','google-adsense-zost-200x200',0,890319,0,'',1,112,'','<div style=\"margin-left: 9px; margin-right: 9px; background-color: rgba(255,255,255,0.90)\">\r\n<script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\r\n<!-- ZOST: 200x200 -->\r\n<ins class=\"adsbygoogle\"\r\n style=\"display: contents;width:200px;height:200px\"\r\n data-ad-client=\"ca-pub-3110076814260873\"\r\n data-ad-slot=\"8448342346\"></ins>\r\n<script>\r\n(adsbygoogle = window.adsbygoogle || []).push({});\r\n</script>\r\n</div>',0,1,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','2017-08-10 00:00:00','2017-07-10 19:05:13','*',24,'','2020-05-29 00:21:56',24,8),(33,9,1,'Google AdSense - ZOST: (200x200) #2','google-adsense-zost-200x200-2',0,890566,0,'',1,112,'','<div style=\"margin-left: 9px; margin-right: 9px; background-color: rgba(255,255,255,0.90)\">\r\n<script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\r\n<!-- ZOST: Banner (200x200) #2 -->\r\n<ins class=\"adsbygoogle\"\r\n style=\"display: contents;width:200px;height:200px\"\r\n data-ad-client=\"ca-pub-3110076814260873\"\r\n data-ad-slot=\"2605418240\"></ins>\r\n<script>\r\n(adsbygoogle = window.adsbygoogle || []).push({});\r\n</script>\r\n</div>',0,2,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','2018-12-03 17:21:19','0000-00-00 00:00:00','2019-01-03 00:00:00','2018-12-03 17:20:27','*',24,'','2020-05-29 00:23:54',24,4),(34,9,1,'Banner 200x200 (Basic Black) #1','banner-200-200-basicblack-1',0,1176001,0,'',1,115,'','<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"> </script>\r\n<!-- ZOST: Banner (200x200) #1 -->\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:200px;height:200px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"8448342346\"> </ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>',0,1,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','2020-06-26 00:00:00','2020-05-26 23:32:16','*',24,'','2020-06-29 21:13:20',24,20),(36,9,1,'Banner 200x200 (Basic Black) #2','banner-200-200-basicblack-2',0,1175979,0,'',1,116,'','<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"> </script>\r\n<!-- ZOST: Banner (200x200) #2 -->\r\n<ins class=\"adsbygoogle\" style=\"display:inline-block;width:200px;height:200px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"2605418240\"> </ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>',0,15,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','2020-06-29 00:00:00','2020-05-29 01:38:00','*',24,'','2020-06-29 21:13:10',24,6),(37,9,1,'Banner 728x90 (Basic Black) #1','banner-728x90-basicblack-1',0,337329,0,'',1,117,'','<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"> </script>\r\n<!-- ZOST: Banner (728x90) -->\r\n<ins class=\"adsbygoogle\" style=\"display:contents;width:728px;height:90px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"6443009564\"> </ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>',0,1,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','2020-06-03 23:26:11','0000-00-00 00:00:00','2020-07-03 00:00:00','2020-06-03 23:24:36','*',24,'','2020-06-28 21:29:47',24,13),(38,9,1,'Banner 728x90 (Basic Black) #2','banner-728x90-basicblack-2',0,337342,0,'',1,118,'','<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"> </script>\r\n<!-- ZOST: Banner (728x90) #2 -->\r\n<ins class=\"adsbygoogle\" style=\"display:contents;width:728px;height:90px\" data-ad-client=\"ca-pub-3110076814260873\" data-ad-slot=\"7210184376\"> </ins>\r\n<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>',0,17,'','{\"imageurl\":\"\",\"width\":\"\",\"height\":\"\",\"alt\":\"\"}',0,'',-1,0,0,0,'0000-00-00 00:00:00','2020-06-03 23:26:11','0000-00-00 00:00:00','2020-07-03 00:00:00','2020-06-03 23:47:06','*',24,'','2020-06-28 21:30:12',24,5);
388388 /*!40000 ALTER TABLE `joomla_banners` ENABLE KEYS */;
389389 UNLOCK TABLES;
390390
@@ -575,7 +575,7 @@
575575
576576 LOCK TABLES `joomla_content` WRITE;
577577 /*!40000 ALTER TABLE `joomla_content` DISABLE KEYS */;
578-INSERT INTO `joomla_content` (`id`, `asset_id`, `title`, `alias`, `introtext`, `fulltext`, `state`, `catid`, `created`, `created_by`, `created_by_alias`, `modified`, `modified_by`, `checked_out`, `checked_out_time`, `publish_up`, `publish_down`, `images`, `urls`, `attribs`, `version`, `ordering`, `metakey`, `metadesc`, `access`, `hits`, `metadata`, `featured`, `language`, `xreference`, `note`) VALUES (71,174,'Comprimindo os Data Packets no DataSnap','compressaodedatapacketxml-art','<p style=\"text-align: justify;\">Procure um meio de validar se sempre se usa base64 e se não tiver certeza, altere o texto para não falar a respeito disso</p>\r\n<p style=\"text-align: justify;\">A comunicação de dados no DataSnap é feita no modelo requisição/resposta, isto é, o cliente, conhecido formalmente como \"thin client\" (cliente magro) envia uma requisição ao middleware (servidor de aplicação) e o mesmo responde de acordo. Quanto mais rápido esse processo se dá, mais felizes seus clientes ficarão, mas existem duas coisas que podem desacelerar o ciclo <strong>requisição / processamento / resposta</strong>: o tempo de processamento e a quantidade de dados que trafegam durante a requisição ou resposta. Se você trabalha com DataSnap e geralmente faz consultas que retornam grande quantidade de dados, situação comum em relatórios, este artigo é para você! Continue lendo...</p>\r\n','\r\n<p style=\"text-align: justify;\">No modelo de requisição/resposta do DataSnap, bastante eficiente aliás, uma das maiores vantagens é justamente a redução drástica do tráfego de dados, pois todas as requisições são stateless e não ficam ativas mais tempo que o necessário. Grosso modo, o tempo em que uma conexão fica estabelecida entre um cliente e o middleware começa com a requisição, passando pelo processamento (por parte do servidor de aplicação) e finaliza quando este último envia a resposta de volta ao cliente. O tempo de processamento depende de como o middleware foi programado e não será abordado aqui. Neste artigo vou ensinar um meio através do qual os dados enviados na requisição e os recebidos nas respostas podem ser comprimidos drasticamente.</p>\r\n<p style=\"text-align: justify;\">Hoje em dia o Delphi já possui meios para utilizar JSON como formato para intercâmbio de informações no DataSnap, no entanto quero salientar que a compressão de <strong>Data Packets</strong> (<strong>DP</strong>) independe da forma como os dados são trafegados na rede. Usar XML, JSON ou outra forma mais antiga de transporte como sockets por exemplo não interfere no tamanho do DP, o qual é sempre enviado de forma codificada em Base64.</p>\r\n<p style=\"text-align: justify;\">De forma bem geral, no DataSnap (<strong>DS</strong>) as requisições e respostas são construídas de acordo com a tecnologia utilizada para transporte, por exemplo, ao se usar SOAP, será usado XML e o DP faz parte das mensagens que são enviadas entre o middleware e o cliente, ou seja, não importa se está sendo usado JSON ou XML, o DP está lá, codificado em Base64.</p>\r\n<p style=\"text-align: justify;\">Mas afinal, o que seria esse DP (DataPacket)? Novamente, não quero me estender neste assunto, mas basicamente o DP pode assumir dois significados. Ele pode ser a representação de um TClientDataSet contendo seus dados e opcionalmente algumas de suas características ou pode ser a representação de um Delta. E o que seria um Delta? Um Delta é o conjunto de todas as alterações sofridas por um TClientDataSet a partir de um estado inicial não alterado, por exemplo, quando inserimos 2 registros, excluímos 3 registros e atualizamos 4 registros em um TClientDataSet, ao confirmarmos todas estas operações, será enviado ao middleware um Delta contendo exatamente estas operações (duas inserções, três exclusões e quatro atualizações) e ao chegar lá estas operações são replicadas como se estivéssemos fazendo-as diretamente no servidor de aplicação. Ao terminar este processamento, o middleware envia de volta ao cliente um outro Delta, com o resultado das operações realizadas (sucessos e erros).</p>\r\n<p style=\"text-align: justify;\">Resumidamente, basta entender que cada operação confirmada no cliente gera um DP que é enviado ao middleware e que, após o conteúdo desse DP for processado, um outro DP será enviado de volta ao cliente com o resultado de todas as operações realizadas. Esses DPs trocados são codificados em Base64 e por este motivo, dependendo de quantas operações foram realizadas ou de quantos dados serão retornados do middleware, o tamanho desse código aumenta substancialmente.</p>\r\n<p style=\"text-align: justify;\">Antes de continuar quero informar que não sou especialista na forma tradicional de DataSnap, e nem nas formas mais modernas. De fato, eu resolvi aprender apenas uma forma (DataSnap sobre SOAP), a que eu achei mais interessante, e me aprofundei na mesma. Os procedimentos explicados aqui funcionaram pra minha realidade, mas eu imagino que com algumas modificações ele pode servir a qualquer tipo de forma de uso do DataSnap. Então finalmente vamos ao que interessa.</p>\r\n<p>Primeiramente, no servidor, abra o Remote Datamodule e em sua classe, sobrescreva os seguintes métodos na seção public, da forma como está abaixo:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function SAS_ApplyUpdates(const ProviderName: WideString; \r\n Delta: OleVariant; \r\n MaxErrors: Integer; \r\n out ErrorCount: Integer; \r\n var OwnerData: OleVariant): OleVariant; override; safecall;\r\n\r\nfunction SAS_GetRecords(const ProviderName: WideString; \r\n Count: Integer; \r\n out RecsOut: Integer; \r\n Options: Integer; \r\n const CommandText: WideString; \r\n var Params: OleVariant; \r\n var OwnerData: OleVariant): OleVariant; override; safecall;</code></pre>\r\n<p style=\"text-align: justify;\">Estes dois métodos são responsáveis por enviar as alterações feitas no cliente (Delta) e obter os registros de uma determinada tabela ou consulta (dependendo da sua implementação) respectivamente. É nestes dois métodos onde concentram-se a maior parte do tráfego de dados e por isso deve ser neles onde a compressão/descompressão deve ser realizada.</p>\r\n<p style=\"text-align: justify;\">A implementação destes dois métodos vem a seguir. Assumo que o nome da classe do Remote DataModule seja simplesmente <strong>TRemoteDataModule</strong>:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function TRemoteDataModule.SAS_ApplyUpdates(const ProviderName: WideString; \r\n Delta: OleVariant; \r\n MaxErrors: Integer; \r\n out ErrorCount: Integer; \r\n var OwnerData: OleVariant): OleVariant;\r\nbegin\r\n { Descomprime o Delta o qual foi comprimido na aplicação cliente }\r\n OleVariantByteArrayUCLDecompress(Delta);\r\n { Executa o método AS_ApplyUpdates original }\r\n Result := inherited;\r\n { Comprime o resultado, o qual é enviado de volta ao cliente no final do processamento }\r\n OleVariantByteArrayUCLCompress(Result);\r\nend;\r\n\r\nfunction TRemoteDataModule.SAS_GetRecords(const ProviderName: WideString; \r\n Count: Integer; \r\n out RecsOut: Integer;\r\n Options: Integer; \r\n const CommandText: WideString; \r\n var Params, \r\n OwnerData: OleVariant): OleVariant;\r\nbegin\r\n { Executa o método SAS_GetRecords com os parâmetros que foram fornecidos }\r\n Result := inherited;\r\n\r\n { Comprime o resultado, o qual é enviado de volta ao cliente no final do processamento }\r\n OleVariantByteArrayUCLCompress(Result);\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Note a presença dos dois procedures OleVariantByteArrayUCLDecompress e OleVariantByteArrayUCLCompress. Estes procedures são os responsáveis pela descompressão e compressão de um \"OleVariantByteArray\", respectivamente. Um OleVariantByteArray é como eu chamo os dados que são manipulados pelos métodos \"SAS_\". Após alguma depuração profunda descobri que o conteúdo dos retornos das funções e de alguns parâmetros que contém Data Packets, são salvos como Array de Bytes. Por isso eu chamo de OleVariantByteArray.</p>\r\n<p style=\"text-align: justify;\">UCL é o nome da biblioteca de compressão. A sigla significa \"Ultimate Compression Library\". Para saber mais sobre o UCL, siga este <a href=\"http://www.oberhumer.com/opensource/ucl/\">link</a>. Estes dois procedures foram elaborados por mim. Abaixo está a implementação de ambos:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>procedure OleVariantByteArrayUCLDecompress(var aOleVariant: OleVariant);\r\nvar\r\n MemoryStream: TMemoryStream;\r\nbegin\r\n if VarIsNull(aOleVariant) then\r\n Exit;\r\n\r\n MemoryStream := TMemoryStream.Create;\r\n try\r\n UclDeCompressStream(OleVariantByteArrayToMemoryStream(aOleVariant),MemoryStream);\r\n aOleVariant := OleVariantByteArrayFromMemoryStream(MemoryStream);\r\n finally\r\n MemoryStream.Free;\r\n end;\r\nend;\r\n\r\nprocedure OleVariantByteArrayUCLCompress(var aOleVariant: OleVariant);\r\nvar\r\n MemoryStream: TMemoryStream;\r\nbegin\r\n if VarIsNull(aOleVariant) then\r\n Exit;\r\n\r\n MemoryStream := TMemoryStream.Create;\r\n try\r\n UclCompressStream(OleVariantByteArrayToMemoryStream(aOleVariant),MemoryStream);\r\n aOleVariant := OleVariantByteArrayFromMemoryStream(MemoryStream);\r\n finally\r\n MemoryStream.Free;\r\n end;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Perceba que ainda existem algumas funções desconhecidas. São elas <strong>UclDeCompressStream</strong>, <strong>OleVariantByteArrayToMemoryStream</strong>, <strong>OleVariantByteArrayFromMemoryStream</strong>, e <strong>UclCompressStream</strong>. As funções <strong>UclCompressStream</strong> e <strong>UclDeCompressStream</strong> são da biblioteca DiUCL a qual pode ser encontrada <a href=\"http://www.yunqa.de/delphi/products/ucl/index\">aqui</a>. Baixe esta biblioteca e use estas funções, as quais estão declaradas na unit DIUclStreams. Abaixo estão as implementações das outras funções:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function OleVariantByteArrayToMemoryStream(const aOleVariant: OleVariant): TMemoryStream;\r\nvar\r\n Data: Pointer;\r\n Size: integer;\r\nbegin\r\n Result := TMemoryStream.Create;\r\n try\r\n Size := OleVariantByteArraySize(aOleVariant);\r\n\r\n Data := VarArrayLock(aOleVariant);\r\n try\r\n Result.WriteBuffer(Data^, Size);\r\n finally\r\n Result.Position := 0;\r\n VarArrayUnlock(aOleVariant);\r\n end;\r\n except\r\n FreeAndNil(Result);\r\n end;\r\nend;\r\n\r\nfunction OleVariantByteArrayFromMemoryStream(const aMemoryStream: TMemoryStream): OleVariant;\r\nvar\r\n Data: Pointer;\r\n OldPosition: Int64;\r\nbegin\r\n Result := Unassigned;\r\n\r\n Result := VarArrayCreate([0, Pred(aMemoryStream.Size)], varByte);\r\n Data := VarArrayLock(Result);\r\n\r\n OldPosition := aMemoryStream.Position;\r\n try\r\n aMemoryStream.Position := 0;\r\n aMemoryStream.ReadBuffer(Data^,aMemoryStream.Size);\r\n finally\r\n aMemoryStream.Position := OldPosition;\r\n VarArrayUnlock(Result);\r\n end;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Estas duas funções de conversão convertem um OleVariantByteArray de, e para, um TMemoryStream. Todos os bons algoritmos de compressão usam ou fornecem meios de comprimir/descomprimir dados em um TMemoryStream, por isso é necessário converter os dados em um TMemoryStream antes de poder manipulá-los. Ainda falta a implementação de uma função, a qual é usada numa das funções acima. A implementação segue:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function OleVariantByteArraySize(const aOleVariant: OleVariant): Cardinal;\r\nbegin\r\n Result := Succ(VarArrayHighBound(aOleVariant, 1) - VarArrayLowBound(aOleVariant, 1));\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Esta função, retorna o tamanho do Array de Bytes contido no OleVariant. Não é possível usar Length diretamente em um OleVariant e por isso tive de subtrair os índices do primeiro e do último elementos do array interno a fim de obter seu tamanho.</p>\r\n<p style=\"text-align: justify;\">Recomendo que todas as funções criadas acima sejam postas em uma unit comum, a qual será utilizada tanto na compilação do Servidor, como na compilação do Cliente. Agora vem a parte do cliente que usa um artifício pouco conhecido mas bastante poderoso e às vezes potencialmente perigoso: Classe Interposer. Uma Classe Interposer, ou \"mediadora\" é como uma Classe Helper, mas ao contrário desta última, a Classe Interposer se localiza hierarquicamente e obrigatoriamente abaixo da classe pai e por este motivo, sua implementação funciona exatamente como se estivéssemos estendendo as funcionalidades de uma classe e não simplesmente incluindo novas propriedades e métodos, como as Classes Helper fazem.</p>\r\n<p style=\"text-align: justify;\">Aqui faremos uma classe Interposer para o TClientDataSet, simplesmente porque o TClientDataSet é o \"outro lado da moeda\" do DataSnap; no Cliente o TClientDataSet e no Servidor o TDataSetProvider dois componentes complementares que juntos fazem tudo acontecer. A implementação da Classe Interposer segue:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>interface\r\n\r\ntype\r\n TClientDataSet = class (DBClient.TClientDataSet)\r\n private\r\n protected\r\n function DoApplyUpdates(Delta: OleVariant; \r\n MaxErrors: Integer; \r\n out ErrorCount: Integer): OleVariant; override;\r\n function DoGetRecords(Count: Integer; \r\n out RecsOut: Integer; \r\n Options: Integer; \r\n const CommandText: WideString; \r\n Params: OleVariant): OleVariant; override;\r\n public\r\n end;\r\n\r\nimplementation\r\n\r\nfunction TClientDataSet.DoApplyUpdates(Delta: OleVariant; \r\n MaxErrors: Integer; \r\n out ErrorCount: Integer): OleVariant;\r\nbegin\r\n OleVariantByteArrayUCLCompress(Delta);\r\n\r\n Result := inherited;\r\n\r\n OleVariantByteArrayUCLDecompress(Result);\r\nend;\r\n\r\nfunction TClientDataSet.DoGetRecords(Count: Integer; \r\n out RecsOut: Integer; \r\n Options: Integer; \r\n const CommandText: WideString; \r\n Params: OleVariant): OleVariant;\r\nbegin\r\n Result := inherited;\r\n\r\n OleVariantByteArrayUCLDecompress(Result);\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Não! O código acima não vai funcionar se você simplesmente copiar e colar. Ainda faltam os uses, os quais você terá de descobrir. Fica como lição de casa. Note que os métodos sobrescritos comprimem ou descomprimem Delta e Result de forma invertida daquilo que foi feito no servidor. Isso era esperado, já que, ao enviar dados do cliente ao servidor usando o método DoApplyUpdates, primeiramente eu devo comprimir o Delta, executar a função no servidor (Result := inherited) e descomprimir o resultado (Result) que vem do servidor. No caso do método DoGetRecords apenas executamos a função no servidor (Result := inherited) e imediatamente após isso, descomprimimos o resultado.</p>\r\n<p style=\"text-align: justify;\">Como se pode ver, a classe interposer tem o mesmo nome da classe original, por isso, se você declarar a classe interposer acima da classe de seu TForm ou TDataModule, TODOS os TClientDataSet que você incluir no seu TForm ou TDataModule, serão do tipo TClientDataSet, mas não daquele contido na unit DBClient e sim daquele que você declarou dentro da sua unit. Esta parte é meio confusa de primeira mas vou tentar explicar.</p>\r\n<p style=\"text-align: justify;\">Suponha que você tem a \"unitA\", que declara a classe \"TA\" então em uma \"unitB\" você precisa criar uma instância da classe \"TA\". Você então inclui a \"unitA\" na cláusula uses e assim, em um local qualquer você pode criar sua classe \"TA\". Isso todo mundo sabe. Agora suponha que dentro do código, em qualquer ponto APÓS a cláusula uses que contém a \"unitA\" e antes da declaração da variável do tipo \"TA\" você declare novamente a classe \"TA\" da seguinte maneira:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>type\r\n TA = class(unitA.TA)\r\n protected\r\n { este método é original em TA e está sendo sobrescrito em sua Interposer! }\r\n procedure Metodo1; override;\r\n end;</code></pre>\r\n<p style=\"text-align: justify;\">Você cria uma classe que herda de TA, mas que tem o mesmo nome da classe pai, o que \"engana\" todas as declarações subsequentes de forma que tudo que for declarado após a declaração da Interposer será do tipo da Interposer, que é uma classe filha da classe original! No exemplo, Metodo1 está sendo sobrescrito e qualquer implementação no mesmo será executada quando Metodo1 for executado. Criar uma classe interposer é como criar um componente baseado em outro, mas sem as complicações de instalação. O inconveniente é que você tem de se certificar de que a declaração da Interposer encontra-se após a declaração da classe original, do contrário, nada funcionará.</p>\r\n<p style=\"text-align: justify;\">Bom, com isso eu concluo este (breve?) artigo sobre como comprimir o tráfego de dados entre cliente e servidor DataSnap usando UCL como compressor. Qualquer outra biblioteca poderia ter sido usada, incluindo ZLib. Optei pelo uso do UCL por ser menos conhecido (segurança/criptografia) e possuir taxas de compressão e velocidade comparáveis às do ZLib/Zip.</p>',0,99,'2012-10-03 13:23:14',24,'','2018-10-05 20:53:30',24,0,'0000-00-00 00:00:00','2012-10-03 13:23:14','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/datasnap\\/ID000071\\/FullArticle.png\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"article_layout\":\"\",\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_associations\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_page_title\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',63,9,'','',1,980,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(72,187,'Quem somos','quemsomos-art','<p style=\"text-align: justify;\">A <strong>Zetta-&#216;mnis Solu&#231;&#245;es Tecnol&#243;gicas</strong>&#160;&#233; uma empresa formada por profissionais especialistas em Delphi e PHP, todos com experi&#234;ncia m&#233;dia de pelo menos 5 anos no mercado, desenvolvendo as mais diversas solu&#231;&#245;es nestas linguagens. Nossa base de funcionamento &#233; a coopera&#231;&#227;o entre nossos profissionais, cada um deles sendo, portanto, dono de um peda&#231;o da empresa</p>\r\n<p style=\"text-align: justify;\">&#160;</p>\r\n<p style=\"text-align: justify;\">&#160;</p>','',1,86,'2012-10-07 00:49:30',24,'','2020-07-16 17:31:54',24,0,'0000-00-00 00:00:00','2012-10-07 00:49:30','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',35,1,'','',1,3370,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',0,'*','',''),(73,188,'O que fazemos','oquefazemos','<p style=\"text-align: justify;\">A <strong>Zetta-&#216;mnis Solu&#231;&#245;es Tecnol&#243;gicas</strong> &#233; uma empresa de Tecnologia da Informa&#231;&#227;o que desenvolve sistemas customizados e&#160;padronizadas para os mais diversos segmentos de mercado</p>','',1,86,'2012-10-07 00:49:47',24,'','2020-07-16 17:33:16',24,0,'0000-00-00 00:00:00','2012-10-07 00:49:47','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_intro\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',9,2,'','',1,3226,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',0,'*','',''),(74,189,'Nossa missão','nossamissao','<p style=\"text-align: justify;\">Nossa missão é desenvolver sistemas de informação e prestar serviços na área de T.I. para a comunidade, com excelência e perfeição, tornando-se assim, referência nacional e internacional no desenvolvimento de soluções tecnológicas.</p>','',1,86,'2012-10-07 00:50:36',24,'','2016-07-27 19:54:38',24,0,'0000-00-00 00:00:00','2012-10-07 00:50:36','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":\"\",\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":\"\",\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":\"\",\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_intro\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',4,3,'','',1,3113,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',0,'*','',''),(78,201,'Como funciona o breadcrumb','entendendoobreadcrumb-art','<p style=\"text-align: justify;\">Há um bom tempo eu tentei fazer com que o breadcrumb funcionasse da forma como eu queria, mostrando o caminho correto (hierarquia) do recurso sendo exibido no momento de acordo com a categoria do mesmo, no entanto a informação exibida sempre era diferente da esperada e eu sempre me frustrei. Busquei em fóruns a solução, procurei até mesmo um módulo que apresentasse o que eu queria, mas não o achei. O que achei foram mais e mais pessoas com o mesmo problema que o meu, até que um dia finalmente eu descobri como resolver o problema do breadcrumb, na verdade não havia problema algum, apenas era um entendimento inadequado deste módulo, o qual vou explicar a seguir.</p>\r\n','\r\n<p style=\"text-align: justify;\">O grande segredo oculto no funcionamento dos breadcrumbs do Joomla é que eles, na verdade, seguem a hierarquia dos menus e não a hierarquia das categorias e isso confunde bastante porque normalmente fazemos os menus com seus itens associados a categorias de alguma forma.</p>\r\n<p style=\"text-align: justify;\">Para facilitar o entendimento, primeiramente esqueça as categorias. Entenda que categorias apenas categorizam o conteúdo do site e não hierarquizam as coisas. Categorias podem ser hierarquizadas apenas para organizá-las e facilitar sua localização ao categorizar um artigo, mas não servem para os breadcrumbs.</p>\r\n<p style=\"text-align: justify;\">Por exemplo, neste site, existem 4 menus, 1 no topo e mais 3 laterais (que nunca são vistos ao mesmo tempo), então é como se existissem 4 breadcrumbs, pois cada menu \"configura\" um breadcrumb. Eu notei isso melhor quando desativem a exibição do item \"home\" no breadcrumb, pois a exibição desse item confundia as coisas e dava a impressão que todos os 4 menus eram um só, por conta da correlação com as categorias, as quais normalmente estão TODAS em uma única hierarquia que contempla o site como um todo.</p>\r\n<p style=\"text-align: justify;\">Por falar em item home home, ele está associado SEMPRE ao item de menu definido como home no backend, INDEPENDENTEMENTE do menu onde ele se encontra, por isso, com o item home habilitado, acontecia de que ele apontava para um item de uma hierarquia, mas o restante do breadcrumb estava apontando para outra hierarquia. Havia uma falta de sincronia entre o que deveria ser exibido e o que estava de fato sendo exibido.</p>\r\n<p style=\"text-align: justify;\">Ainda usando esse site como exemplo, a solução foi criar um item raiz para cada um dos menus laterais e configurar a exibição para ocultar este item. Se ele vai ser ocultado, porque então criá-lo? Simples! Porque o breadcrumb exibe este item sempre, e isso é um efeito desejado. Para exemplificar, clique no item <a href=\"index.php/a2p-mei\">Addicted 2 PHP!</a> Esse item acessa o menu raiz criado. Siga então a hierarquia e note que, no breadcrumb, ele sempre aparece, dando uma indicação visual de onde estamos. Este artigo, por exemplo, está em \"<a href=\"index.php/a2p-mei\">Addicted 2 PHP!</a> \\ <a href=\"index.php/a2p-mei/articles-a2p-mei\">Artigos</a> \\ <a href=\"index.php/a2p-mei/articles-a2p-mei/joomla-articles-a2p-mei\" rel=\"alternate\">Joomla!</a>\", ou seja, ele é um artigo sobre o Joomla! de Addicted 2 PHP! e isso fica bem claro.</p>\r\n<p style=\"text-align: justify;\">O menu do topo, contém apenas aliases para os itens raiz de cada um dos menus da esquerda, portanto, quando eles são clicados, o breadcrumb é exibido de forma adequada. Toda a lógica de apresentação do breadcrumb, ou melhor, dos breadcrumbs, já que são 3, está concentrada nos 3 menus da esquerda</p>',1,110,'2012-10-16 01:56:57',24,'','2016-08-31 14:38:34',24,0,'0000-00-00 00:00:00','2016-07-21 01:56:00','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_intro\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',30,1,'','',1,3216,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(80,203,'Ordem de criação e execução de ApplyUpdates','ordemdeexecucaodeapplyupdates','<pre xml:lang=\"ASM\">; #line 2128\r\npush offset aError_message ; \"ERROR_MESSAGE\"\r\npush ebx ; lpString1\r\ncall lstrcpyA\r\n; #line 2129\r\nmov dword ptr [ebx+20h], 1\r\n; #line 2130\r\nmov dword ptr [ebx+28h], 0FFh\r\n; #line 2131\r\nadd ebx, 4Ch ; \'L\'</pre>\r\n<p> </p>\r\n<pre xml:lang=\"CPP\">// 2128\r\nLdStrCpy((pCHAR)pFldDes-&gt;szName, szdsERRMESSAGE);\r\n// 2129\r\npFldDes-&gt;iFldType = fldZSTRING;\r\n// 2130\r\npFldDes-&gt;iUnits1 = 255;\r\n// 2131\r\npFldDes++;</pre>\r\n<p>Boa tarde,</p>\r\n<div> </div>\r\n<div>Tenho uma estrutura com 4 dataset relacionadas da seguinte maneira:</div>\r\n<div> </div>\r\n<div>Mestre</div>\r\n<div>    Detalhe 1</div>\r\n<div>    Detalhe 2</div>\r\n<div>        SubDetalhe 2.1 - aqui tenho uma FK apontando pra um registro do Detalhe 1</div>\r\n<div> </div>\r\n<div>A ordem de inclusão dos registros nos datasets é Mestre, Detalhe 1, Detalhe 2, SubDetalhe 2.1.</div>\r\n<div> </div>\r\n<div>Entretanto, a ordem de gravação, quando executado o método ApplyUpdates, parece ser Mestre, Detalhe 2, SubDetalhe 2.1, Detalhe 1.</div>\r\n<div> </div>\r\n<div>Isso gera o erro \"Foreign key reference target does not exist\", pois existe uma FK em SubDetalhe 2.1 que aponta para um registro incluído no Detalhe 1.</div>\r\n<div> </div>\r\n<div>Tenho como interferir na ordem de gravação dos registros? Ou existe outra maneira de contornar essa problema?</div>\r\n<div> </div>\r\n<div>----------------------------</div>\r\n<div> </div>\r\n<div>\r\n<div>Clique com o direito no DTM... e escolha “Create Order”...</div>\r\n<div> </div>\r\ncheque se a ordem de criação dos DataSet estão corretas.</div>\r\n<div> </div>\r\n<div>----------------------------</div>\r\n<div> </div>\r\n<div>\r\n<p>Obrigado pela resposta Caique.</p>\r\n<div> </div>\r\n<div>Conferi a ordem de criação e a mesma está correta, seguindo a hierarquia que apresentei.</div>\r\n<div> </div>\r\n<div>Antes de dar o ApplyUpdates, verifiquei a ordem dos campos no dataset e está assim:</div>\r\n<div> </div>\r\n<div>Fields = Campo1, Campo2, Detalhe1, Detalhe2.</div>\r\n<div> </div>\r\n<div>FieldDefs = Campo1, Campo2, Detalhe2, Detalhe1.</div>\r\n<div> </div>\r\n<div>Ou seja, a propriedade FieldDefs possui a lista de campos na ordem incorreta que estão sendo gravados os dados. Alterei a ordem dos FieldDefs pelo Index, mas não mudou a ordem de gravação.</div>\r\n<div> </div>\r\n<div>------------------------</div>\r\n<div> </div>\r\n<div>\r\n<p>Boas novas!!!</p>\r\n<div> </div>\r\n<div>Após muitos testes e experimentações, usando vários DataSources (um para cada detalhe), descobri que a sugestão do Caique fazia sentido, só que em ordem inversa!</div>\r\n<div> </div>\r\n<div>Então defini a ordem de criação dos datasets no \"Create Order\" do DTM da seguinte maneira: Mestre, Detalhe 2, SubDetalhe 2.1, Detalhe 1.</div>\r\n<div> </div>\r\n<div>E a ordem de gravação seguiu a ordem inversa da criação, portanto a correta!</div>\r\n<div> </div>\r\n<div>Obrigado pela dica Caique.</div>\r\n</div>\r\n</div>','',0,80,'2012-11-14 13:50:23',24,'','2016-07-26 19:17:06',24,0,'0000-00-00 00:00:00','2012-11-14 13:50:23','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":\"\",\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":\"\",\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":\"\",\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_intro\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',6,83,'','',1,586,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',0,'*','',''),(81,204,'Alteração de conexão em servidor DataSnap','alteracao-de-conexao-em-servidor-datasnap','<div class=\"gmail_extra\">Oi Fellipe<br /><br /></div>\r\n<div class=\"gmail_extra\">(desculpe, não li as outras respostas por preguiça mesmo. Se eu estiver perguntando besteira, por favor, dá um desconto. Quero ajudar, mas estava muito ocupado ultimamente...)</div>\r\n<div class=\"gmail_extra\">Você tem apenas um middleware (MW) e quer que vários clientes de empresas distintas acessem este middleware único, mas ao fazer isso estejam manipulando dados em bancos de dados específicos de acordo com cada um deles?</div>\r\n<div class=\"gmail_extra\">Qual a tecnologia de transporte do teu Server App, isto é, trocando em miúdos, qual o componente de conexão DataSnap (DS) você está usando nas aplicações cliente?<br /><br /></div>\r\n<div class=\"gmail_extra\">Se você estivesse usando SOAP (TSOAPConnection) e teu middleware fosse um Webservice (WS) eu poderia dizer com certeza, que cada conexão de cliente cria uma thread separada (modelo apartment) e sendo assim a solução que você mesmo apresentou, via login, seria perfeita, contudo, há um ponto que precisa ser definido: o teu login deveria ser feito em uma base única, igual para todos os clientes, e cada cliente deve ter o código da empresa associado.<br /> <br />O problema com isso é que ao menos o WS é stateless, tudo acontece por requisição/resposta, ou seja, ao fazer o login você requisita uma autenticação e a resposta é OK ou NÃO OK. E depois disso, conexões subsequentes não vão mais saber que houve outras conexões anteriormente. A solução pra isso vem naturalmente quando você compara uma aplicação DS a um site dinâmico da web, feito, digamos em PHP. Ao fazer login, todas as próximas requisições e respostas precisam saber que você \"está logado\", e isso é feito com o uso de sessões.</div>\r\n<div class=\"gmail_extra\">Minha solução para uso de sessões no DS foi implementada por meio de um arquivo que é atualizado no servidor toda vez que um usuário faz login ou logout. Se existe uma solução para uso de sessões já pronta eu não usei, por simples falta de conhecimento profundo do DS. Estou usando Delphi XE. Não sei se já existe uma solução via componente pra isso. No lugar do arquivo poderia ser um banco de dados embarcado, por exemplo, como o MySQL Embeded (que dispensa um servidor de banco e roda localmente), que é muito bom e poderoso. Só não usei um banco de dados justamente porque seria desperdício de poder.<br /> <br />Criei uma função específica para login e logout no WS. Ao fazer login bem sucedido, um arquivo, cujo formato eu mesmo defino é atualizado. Este arquivo contém uma lista de usuários autenticados. No teu caso seriam todos os usuários, de todas as empresas. Cada registro neste arquivo é composto por 3 campos básicos:<br /><ol>\r\n<li>SessionID: String - um GUID gerado por uma função específica</li>\r\n<li>SessionData: String - Informações do teu usuário conectado. Pode ser uma string JSON. Eu uso um DFM Object</li>\r\n<li>SessonLastModified: TDateTime - Data e hora da última alteração em SessionData. A data é alterada quando o usuário troca alguns de seus dados, como a senha ou e-mail. A data também serve como um marcador para invalidar a sessão após um determinado período de tempo, caso você queira implementar isso.</li>\r\n</ol>\r\n<p>O campo SessionData é onde você guarda as informações do teu cliente, da forma que você quiser, eu guardo as seguintes informações:</p>\r\n<ol>\r\n<li>IDUsuario: SmallInt - ID na tabela de usuários</li>\r\n<li>IDEmpresa: SmallInt - ID da empresa na qual o usuário trabalha</li>\r\n<li>Nome: String - óbvio</li>\r\n<li>Login: String -  óbvio</li>\r\n<li>Senha: String - Hash da senha do usuário. Eu uso SHA-1</li>\r\n<li>Email: String - óbvio</li>\r\n<li>Superusuario: Boolean - No meu caso indica que o usuário é um administrador para algumas funções DS, de forma que validações de permissão não sejam executadas.</li>\r\n</ol></div>\r\n<div class=\"gmail_extra\">Após o login, eu tenho então, dentro do arquivo de sessões no servidor um registro que identifica cada usuário logado e suas empresas, ao mesmo tempo que no cliente eu salvo em memória algumas informações sobre o login, a saber:</div>\r\n<ol>\r\n<li>SessionID: String - GUID retornado pela função de login</li>\r\n<li>SessionData: TSessionData - Objeto ou Record com as mesmas informações do campo SessionData citado anteriormente. No meu caso eu uso um DFM Object aqui, para facilitar minha vida, mas como eu disse antes, pode ser um JSON.</li>\r\n</ol>\r\n<div class=\"gmail_extra\">O que fazer com isso? No meu caso, como eu estou usando o arquivo de sessões para identificar o usuário e suas permissões dentro do sitema eu tive de redefinir (override) os seguintes métodos DS tanto no servidor como no cliente:<br /><ol>\r\n<li>function SAS_ApplyUpdates(const ProviderName: WideString; Delta: OleVariant; MaxErrors: Integer; out ErrorCount: Integer; var <strong>OwnerData</strong>: OleVariant): OleVariant; override; stdcall;</li>\r\n<li>procedure SAS_Execute(const ProviderName: WideString; const CommandText: WideString; var Params: OleVariant; var <strong>OwnerData</strong>: OleVariant); override; stdcall;</li>\r\n<li>function SAS_GetParams(const ProviderName: WideString; var <strong>OwnerData</strong>: OleVariant): OleVariant; override; stdcall;</li>\r\n<li>function SAS_GetRecords(const ProviderName: WideString; Count: Integer; out RecsOut: Integer; Options: Integer; const CommandText: WideString; var Params: OleVariant; var <strong>OwnerData</strong>: OleVariant): OleVariant; override; stdcall;</li>\r\n<li>function SAS_RowRequest(const ProviderName: WideString; Row: OleVariant; RequestType: Integer; var <strong>OwnerData</strong>: OleVariant): OleVariant; override; stdcall;</li>\r\n</ol>\r\n<p>Estes métodos (SAS_) são métodos em WebServices. Se você estiver usando outra tecnologia, você vai ter exatamente os mesmos métodos, mas com o prefixo \"AS_\" apenas, porém com a mesma assinatura. Note que todos eles tem um parâmetro chamado OwnerData.</p>\r\n<p>(Apartir deste ponto a técnica descrita não foi testada, cabe a você tentar e ver se dá certo, ok?)</p>\r\n<p>O problema de conectar a um banco diferente daquele definido em designtime ou no momento da criação do TRemoteDataModule, reside no fato de que no DS a conexão com o banco é iniciada sempre a partir de uma ação de Inserção, Atualização, Exclusão ou Seleção de dados, isto é:</p>\r\n<ol>\r\n<li>Você solicita uma consulta na tabela X no clinete</li>\r\n<li>A requisição é enviada ao MW</li>\r\n<li>O MW cria automaticamente a conexão com o banco de dados com as informações de conexão definidas em designtime ou arquivo de configuração externo</li>\r\n<li>O select que você solicitou no cliente é finalmente executado e a resposta é enviada de volta</li>\r\n<li>O cliente exibe o resultado da consulta</li>\r\n<li>A conexão com o MW é finalizada e neste momento o MW fecha a conexão com o BD</li>\r\n</ol></div>\r\n<div class=\"gmail_extra\">Como se pode ver não há um comando explicito para conectar ao banco de dados. O MW realiza a conexão porque houve uma requisição de consulta, de acordo com o exemplo, por isso o que tem de ser feito é forçar uma desconexão/reconfiguração/reconexão do banco de dados a partir da solicitação realizada. Usando o mesmo exemplo de consulta, é sabido que uma consulta no cliente executa o método SAS_GetRecords (AS_GetRecords) no servidor e sendo assim precisamos sobrescrever estes métodos tanto no cliente como no servidor (MW).<br /> <br />Note que os métodos \"AS\" tem um parâmetro comum OwnerData do tipo OleVariant. Este parâmetro coringa serve para várias coisas e eu estou usando ele para passagem de dados de sessão. No cliente, nós devemos sobrescrever os métodos AS e codificar em OwnerData apenas o SessionID.</div>\r\n<div class=\"gmail_extra\">No cliente os métodos \"AS\" tem ligação direta com eventos de TClientDataSet. No nosso exemplo, o método AS_GetRecords, gera eventos OnBeforeGetRecords, OnGetRecords e OnAfterGetRecords. Precisamos então criar um manipulador de evento apenas para o OnBeforeGetRecords, pois precisamos enviar dados ao MW antes de qualquer coisa. No meu caso eu criei uma classe interposer para o TClientDataSet e por isso eu sobrescrevi o método DoBeforeGetRecords, o qual é executado sempre imediatamente antes de uma requisição de consulta (AS_GetRecords). Ficaria mais ou menos assim:<br /> <br />procedure TClientDataSet.DoBeforeGetRecords(var OwnerData: OleVariant);<br />begin<br />  OwnerData := SessionID;<br />  inherited;<br />end;<br /><br /></div>\r\n<div class=\"gmail_extra\">Isso é suficiente para enviar ao servidor a identificação de nossa sessão. No servidor, devemos então sobrescrever o método AS_GetRecords assim:<br /> <br />function AS_GetRecords(const ProviderName: WideString; Count: Integer; out RecsOut: Integer; Options: Integer; const CommandText: WideString; var Params, OwnerData: OleVariant): OleVariant;<br />begin<br />  if SessionExists(OwnerData) then<br />   begin<br />    DBConnectBySession(OwnerData);<br /><br />    Result := inherited;<br />  end<br />  else<br />    raise Exception.Create(\'Para usar este método é necessário que você seja um usuário autenticado no sistema\');<br /> end;<br /><br /></div>\r\n<div class=\"gmail_extra\">Explicando...<br /><br /></div>\r\n<div class=\"gmail_extra\">Caso uma sessão identificada pelo SessionID codificado em OwnerData existir então eu devo executar um métodos chamado DBConnectBySession e somente em seguida realizar o restante da ação que é simplesmente executar novamente AS_GetRecords. Caso uma sessão identificada pelo SessionID codificado em OwnerData NÃO existir. Eu levanto uma exceção pois a ação não é autorizada para usuários não autenticados.</div>\r\n<div class=\"gmail_extra\">O método DBConnectBySession não existe, você preicisa criá-lo, mas acho que isso é bem simples. Tudo que você precisa fazer é buscar o SessionID no arquivo local de sessões e obter no final o ID da empresa a partir dele. De posse do ID da empresa você realiza a desconexão do banco de dados, depois reconfigura ele, e conecta novamente. O importante é que ao chegar na linha que diz \"Result := inherited;\" você tenha efetivamente uma conexão com um banco de dados.</div>\r\n<div class=\"gmail_extra\">Esta mesma alteração deve ser realizada para os outros quatro métodos AS da mesma forma!<br /><br /></div>\r\n<p><em><strong>É isso. Espero que tenha ajudado em algo, apesar de que a tentativa de solucionar teu problema não foi testada por mim, mas se funcionar vai ser realmente uma mão na roda. Se não funcionar, ao menos vou saber que algumas pessoas puderam aprender algo, e isso é o que importa. Funcionando ou não, deixa a gente saber. Se tiver alguma dúvida, pergunta.</strong></em></p>','',0,99,'2013-02-27 18:01:05',24,'','2016-07-27 12:53:10',24,0,'0000-00-00 00:00:00','2013-02-27 18:01:05','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":\"\",\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":\"\",\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":\"\",\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_intro\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',3,8,'','',1,180,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',0,'*','',''),(89,220,'10 razões para ser um desenvolvedor Delphi','10-razoes-para-ser-um-desenvolvedor-delphi','<p style=\"text-align: justify;\">Hoje li um artigo muito interessante que enumerava 10 raz&#245;es para ser um desenvolvedor Delphi. Resolvi ent&#227;o tomar a liberdade de traduzi-lo. O texto &#233; relativamente simpl&#243;rio, mas ele &#233; destinado aos iniciantes da programa&#231;&#227;o. Tomei a liberdade de incluir algumas coisas que faltavam e comentar algo que achei curioso, ou seja, esta &#233; uma tradu&#231;&#227;o livre e despretensiosa. Considere-a como uma \"vers&#227;o brasileira, eu mesmo!\" :)</p>\r\n','\r\n<p style=\"text-align: justify;\">Segue abaixo minha tradu&#231;&#227;o/vers&#227;o do texto original dispon&#237;vel em <a href=\"http://delphi.org/2016/07/top-10-reasons-to-be-a-delphi-developer/\">http://delphi.org/2016/07/top-10-reasons-to-be-a-delphi-developer/</a>. Como se trata de uma vers&#227;o, n&#227;o existe uma tradu&#231;&#227;o ao p&#233; da letra, mas sim uma interpreta&#231;&#227;o daquilo que o autor original escreveu. Esta interpreta&#231;&#227;o, contudo, n&#227;o alterou em nada o sentido do texto, mas facilitar&#225; bastante sua leitura.</p>\r\n<hr style=\"width: 100%;\" width=\"100%\" />\r\n<p style=\"text-align: justify;\">O trabalho de desenvolvedor de softwares &#233; muito interessante. Voc&#234; aprende uma linguagem de programa&#231;&#227;o para desenvolver um grande variedade de softwares e assim \"digitalizar o mundo\", mas enquanto faz isso, voc&#234; &#224;s vezes enfrenta situa&#231;&#245;es onde pequenos erros tiram seu sono por muitos dias e noites. A melhor parte dessa experi&#234;ncia &#233; quando voc&#234; finalmente encontra a solu&#231;&#227;o e d&#225; um tapa em si mesmo, com um sorriso no rosto, dizendo \"Meu Deus! Foi este probleminha que me tirou o sono?\". Isso pode acontecer com qualquer desenvolvedor de softwares, mas o resultado final &#233; sempre gratificante, e &#233; por isso que o desenvolvimento de softwares &#233; um trabalho que est&#225; agradando mais e mais jovens ao redor do mundo.</p>\r\n<p style=\"text-align: justify;\">Dependendo das linguagens de programa&#231;&#227;o que voc&#234; estudou e implementou, as chances de ficar travado em um pequeno problema pode variar. A utiliza&#231;&#227;o de uma linguagem complexa pode tirar seu sono muitas vezes durante o desenvolvimento de um software, por outro lado, a utiliza&#231;&#227;o de uma linguagem de programa&#231;&#227;o com uma sintaxe simples de escrever e recursos realmente &#250;teis pode transformar sua vida de desenvolvedor em uma vida que todos adorariam ter. A Linguagem Delphi (Object Pascal) &#233; este tipo de linguagem de programa&#231;&#227;o, largamente conhecida por ser uma das linguagens de sintaxe mais simples de escrever.</p>\r\n<p style=\"text-align: justify;\">Se voc&#234; pretende ser um desenvolvedor de softwares voc&#234; pode n&#227;o querer come&#231;ar com uma linguagem dif&#237;cil, que ponha seu moral pra baixo. Ent&#227;o, a fim de manter a sua paix&#227;o em ser um desenvolvedor no mais alto grau de entusiasmo, come&#231;ar com a Linguagem Delphi ser&#225; uma op&#231;&#227;o sem igual. Come&#231;ando sua carreira como um Desenvolvedor Delphi traz para voc&#234; muitos benef&#237;cios. Vejamos agora algumas das principais raz&#245;es que far&#227;o voc&#234; entender porque voc&#234; deve ser um Desenvolvedor Delphi:</p>\r\n<ol type=\"I\">\r\n<li>A Linguagem Delphi &#233; uma combina&#231;&#227;o de linguagem de programa&#231;&#227;o e SDK, o que permite o desenvolvimento de de aplica&#231;&#245;es para desktop, dispositivos m&#243;veis, <a title=\"Eu n&#227;o tenho conhecimento sobre a possibilidade de desenvolvimento para consoles usando o Delphi. Eu j&#225; vi, sim, ports de Doom e Quake 100% desenvolvidos em Delphi Language e que ficaram simplesmente perfeitos. Em conversa com outro especialista o mesmo me disse que isso pode ser poss&#237;vel, considerando que o XBOX One usa o Windows 10 como OS, e sendo assim, o Delphi de fato conseguiria desenvolver para consoles Microsoft\" href=\"#\" rel=\"bookmark\">consoles</a>&#160;e web;</li>\r\n<li>A Linguagem Delphi &#233; uma linguagem de programa&#231;&#227;o simples e com sintaxe limpa;</li>\r\n<li>C&#243;digo escrito em Delphi &#233; facilmente leg&#237;vel, por exemplo, voc&#234; pode concatenar strings usando o caractere \"+\" ao inv&#233;s de usar uma fun&#231;&#227;o para isso;</li>\r\n<li>A documenta&#231;&#227;o do Delphi &#233; bem organizada para ajud&#225;-lo a iniciar rapidamente;</li>\r\n<li>Vem com uma IDE que lhe permite desenvolver facilmente uma GUI usando arrastar &amp; soltar, adicionando manipuladores de eventos e muitas outras caracter&#237;sticas realmente &#250;teis;</li>\r\n<li>Suporta teste em tempo real (debug), tornando muito mais simples e r&#225;pido encontrar e corrigir problemas;</li>\r\n<li>Suporta o Desenvolvimento R&#225;pido de Aplica&#231;&#245;es --&#160;Rapid Application Development (RAD) -- com caracter&#237;sticas tais como, o framework de aplica&#231;&#227;o e o designer de layout de&#160;janelas totalmente visual (WYSIWYG);</li>\r\n<li>Suporta arquitetura cliente-servidor, arquitetura n-tier (DataSnap) e bancos de dados SQL;</li>\r\n<li>Suporta a API do Windows 100%;</li>\r\n<li>Permite a cria&#231;&#227;o de componentes que s&#227;o facilmente integr&#225;veis na IDE.</li>\r\n</ol>',1,80,'2016-07-22 14:15:16',24,'','2020-12-12 19:49:09',24,0,'0000-00-00 00:00:00','2016-07-22 14:15:16','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/zost\\/ourproducts\\/delphi\\/Delphi.png\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','',29,82,'Delphi, Addicted 2 Delphi, Desenvolver, Razões','Hoje li um artigo muito interessante que enumerava 10 razões para ser um desenvolvedor Delphi. Resolvi então tomar a liberdade de traduzi-lo. O texto é relativamente simplório, mas ele é destinado aos iniciantes da programação. Tomei a liberdade de incluir algumas coisas que faltavam e comentar algo que achei curioso, ou seja, esta é uma tradução livre e despretensiosa. Considere-a como uma \"versão brasileira, eu mesmo!\" :)',1,5418,'',1,'*','',''),(90,222,'A verdade sobre o TDataModule e o consumo de memória','a-verdade-sobre-o-tdatamodule-e-o-consumo-de-memoria','<p style=\"text-align: justify;\">Há algumas semanas tomei conhecimento de pelo menos duas publicações que estavam falando a respeito do TDataModule, acusando-o de ser um voraz consumidor de memória dentro das aplicações. Como tenho costume de usá-lo de todas as formas possíveis em meus projetos, fiquei curioso para saber como este, que é simplesmente um contêiner não visual, poderia consumir tanta memória como era dito. Depois de ler argumentações rasas e pseudo verdades eu resolvi pôr a mão na massa para tentar descobrir se o TDataModule realmente era tão mau assim.</p>\r\n','\r\n<p style=\"text-align: justify;\">Muito se fala e pouco se aprofunda, por isso tomei as dores do pobre TDataModule e, a fim de provar sua inocência, comecei verificando sua hierarquia, e notei que o TDataModule tem menos \"pais\" do que o TForm. Estou usando o TForm como exemplo porque, primariamente e da forma mais básica possível, um TDataModule é um contêiner de componentes não visuais e um TForm também aceita componentes não visuais e por isso um programa pode muito bem ser criado sem que haja sequer um TDataModule, contudo, TDataModules ajudam a modularizar um programa e, usando-o da forma mais básica, é possível manter as coisas organizadas, concentrando seus componentes não visuais em um TDataModule e evitando que um tela complexa (e com muitos componentes não visuais) fique poluída. A quantidade de hierarquias é sim um fator crucial para a contabilização do consumo de memória, e se um TDataModule tem menos hierarquias do que um TForm, as chances dele consumir menos memória do que este último é bem elevada. Achei isso muito curioso e fui investigar mais a fundo</p>\r\n<p style=\"text-align: justify;\">Algumas pessoas vão dizer que tudo pode ser criado dinamicamente e não é preciso ter quaisquer componentes não visuais em designtime, seja em forms, seja em data modules, mas entenda que o intuito desse artigo é mostrar a verdade sobre o TDataModule ser ou não um consumidor de memória. Além disso, iniciantes não vão jamais criar componentes em tempo de execução. É muito mais fácil soltar um TQuery num TForm (ou TDataModule) do que lidar com construtores, destrutores e atribuição programática de propriedades. <strong>Este texto é destinado aos iniciantes</strong>, para que eles não acreditem em tudo que lêem e usem os componentes da melhor forma possível, até que um dia resolvam \"complicar\", criando tudo em runtime, que trás poucas vantagens, diga-se. Minha opinião é de que se eu posso fazer as coisas de forma visual, eu vou fazer de forma visual porque isso facilita minha vida. Não existe um concurso secreto que dá prêmios em dinheiro pra programadores que fazem tudo de forma linda, OO ou sem gambiarras! Aliás, aquele que nunca fez uma gambiarra que atire o primeiro teclado :)</p>\r\n<h2>Sim, mas e o tamanho do TDataModule?</h2>\r\n<p style=\"text-align: justify;\">Colocar um TDataModule numa aplicação aumenta seu consumo de memória? Claro que aumenta! Tudo que é incluído numa aplicação, seja em designtime, seja em runtime, impacta no consumo final de memória. O que quero comprovar aqui é que, colocar um TDataModule em uma aplicação não é diferente de incluir um TForm, por exemplo. O consumo de memória de um TForm e de um TDataModule é similiar, o que significa que uma aplicação com um TForm e um TDataModule é maior do que uma aplicação com apenas um TForm, mas usar um TDataModule para organizar o código e concentrar componentes não visuais COMPENSA este leve aumento.</p>\r\n<p style=\"text-align: justify;\">Como foi que cheguei a esta conclusão a respeito do tamanho do TDataModule? Simples! Criei um projeto (que está anexado a esta publicação) que faz medições de consumo de memória antes e após a criação/destruição de diversos objetos. Baixe o projeto e execute-o. Ele é guiado passo-a-passo. Tire suas próprias conclusões</p>\r\n<h2>E daí?</h2>\r\n<p style=\"text-align: justify;\">E daí que, da próxima vez que alguém disser que a culpa é do TDataModule, você poderá dar o link deste artigo e esperar que a pessoa não seja muito cabeça dura pra entender de uma vez por todas que, normalmente, a culpa do consumo de memória desenfreado é de componentes de terceiros ou programação inadequada. Obrigado por ler :)</p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-07-25 20:11:09',24,'','2020-06-26 17:22:15',24,0,'0000-00-00 00:00:00','2016-07-25 20:11:09','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000090\\/FullArticle.png\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','',15,81,'Delphi, Addicted 2 Delphi, Performance, TDataModule, DataModule, Memória, Consumo','Há algumas semanas tomei conhecimento de pelo menos duas publicações que estavam falando a respeito do TDataModule, acusando-o de ser um voraz consumidor de memória dentro das aplicações. Como tenho costume de usá-lo de todas as formas possíveis em meus projetos, fiquei curioso para saber como este, que é simplesmente um contêiner não visual, poderia consumir tanta memória como era dito. Depois de ler argumentações rasas e pseudo verdades eu resolvi pôr a mão na massa para tentar descobrir se o TDataModule realmente era tão mau assim.',1,6439,'',1,'*','',''),(91,224,'Avaliação booleana em modo \"curto-circuito\" e operandos do tipo Variant','avaliacao-booleana-em-modo-curto-circuito-nao-funciona-com-operandos-do-tipo-variant','<p style=\"text-align: justify;\">Você sabia que o modo curto-circuito de avaliação boolena não funciona bem com operandos do tipo Variant? Pois é, eu também não sabia. E isso estava acabando com minha lógica. Neste rápido artigo vou mostrar um exemplo de como isso pode atrapalhar toda a sua lógica, caso você não esteja ciente deste \"pequeno\" detalhe. </p>\r\n','\r\n<p style=\"text-align: justify;\">O modo curto-circuito de avaliação boolena é a forma padrão de avaliação lógica de todas as linguagens de programação. Basicamente o curto-circuito é um conceito que prega que uma expressão booleana complexa seja avaliada o mais rapidamente possível pelo compilador mediante o descarte de avaliações adicionais que são óbvias ou nulas dentro da referida expressão.</p>\r\n<p style=\"text-align: justify;\">Se ainda assim não consegue entender o que é uma avaliação boolean em curto-circuito, dê uma olhada em <a href=\"http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/compdirsbooleanshortcircuitevaluation_xml.html\">Boolean short-circuit evaluation (Delphi compiler directive)</a> e saiba que mesmo sem você saber da existência disso, provavelmente você usa, pois o curto-circuito é padrão do Delphi.</p>\r\n<p style=\"text-align: justify;\">Chega de bla bla bla. Tenho a seguinte função:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function CanSelect(const aTableName: String): Boolean;\r\nbegin\r\n Result := CurrentSession.Data.bo_superusuario \r\n or CLDSPermissoes.Lookup(\'ENTIDADE\',aTableName,\'LER\');\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">A lógica é simples: A função vai retornar TRUE se eu for um superusuário ou se eu tiver permissão de leitura para a tabela passada no parâmetro aTableName.</p>\r\n<p style=\"text-align: justify;\">Como a diretiva de compilação <strong>{$B-}</strong> está ativada como padrão, a lógica diz que se CurrentSession.Data.bo_superusuario for TRUE, o segundo operando nem mesmo vai ser avaliado pois apenas a condição do primeiro operando já é suficiente para saber o resultado da expressão booleana inteira. O que estava acontecendo é que o segundo operando, neste caso, estava sendo avaliado, mesmo quando o primeiro era TRUE, que, no meu caso gerava um exceção por conta de CLDSPermissoes estar inativo.</p>\r\n<p style=\"text-align: justify;\">Após algumas pesquisas atentei para o fato do segundo operando (Lookup) retornar um dado Variant e por este motivo o compilador não tem condições de saber se ele é realmente um booleano ou não, causando a avaliação completa da expressão booleana mesmo no modo <strong>{$B-}</strong>. Ao meu ver isso é algum tipo de bug relacionado ao modo <strong>{$B-}</strong> o qual deveria \"curtocircuitar\" a expressão APENAS após a conversão do dado Variant, mas isso é outro assunto.</p>\r\n<p style=\"text-align: justify;\">Após descobrir a causa do problema que impactava na minha lógica, foi simples resolver o problema. Simplesmente dei um cast no variant para Boolean, explicitando para o compilador que se trata de um operando BOOLEANO, assim:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function CanSelect(const aTableName: String): Boolean;\r\nbegin\r\n Result := CurrentSession.Data.bo_superusuario \r\n or Boolean(CLDSPermissoes.Lookup(\'ENTIDADE\',aTableName,\'LER\'));\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Desta forma a lógica funciona como deve. Fica a dica!</p>',1,80,'2016-07-25 20:52:54',24,'','2016-11-18 14:12:18',24,0,'0000-00-00 00:00:00','2016-07-25 20:52:54','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"right\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000091\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','',26,80,'Delphi, Addicted 2 Delphi!, Variant, Short Circuit, Boolean, Lógica Booleana, Avaliação Booleana','Você sabia que o modo curto-circuito de avaliação boolena não funciona bem com operandos do tipo Variant? Pois é, eu também não sabia. E isso estava acabando com minha lógica. Neste rápido artigo vou mostrar um exemplo de como isso pode atrapalhar toda a sua lógica, caso você não esteja ciente deste \"pequeno\" detalhe. ',1,4659,'',1,'*','',''),(92,225,'Como obter a real diferença entre dois valores TDateTime','como-obter-a-real-diferenca-entre-dois-valores-tdatetime','<p style=\"text-align: justify;\">Nas minhas andanças pela web, descobri que a unit DateUtils, continha vários problemas de arredondamento e que sua precisão era muito pobre. Descobri também que existia uma proposta no site da Embarcadero (Quality Central) para mudanças nesta unit de forma que ela seja tão precisa quanto um milissegundo. Hoje este problema de precisão não existe mais, no entanto ainda é válida a técnica usada para criação de uma função que obtém precisamente (1 milissegundo) a diferença entre duas datas passadas por parâmetro. O resultado é um record com anos, semanas, dias, horas, minutos, segundos e milissegundos que corresponde à diferença de datas completa! Existem por aí inúmeros algoritmos que realizam tal tarefa, mas não com esta precisão e não com esta velocidade ;)</p>\r\n','\r\n<p style=\"text-align: justify;\">Quem estiver interessado na proposta sobre mudança da VCL, é só dar uma olhada em <a href=\"http://qc.embarcadero.com/wc/qcmain.aspx?d=56957\">Report #56957 - A Fix for DateUtils Date-Time Compare Functions</a>. Eu não sei se esta página ainda está ativa, mas se não estiver, faça uma busca pelo seu título. Se você tiver sorte, achará o texto em algum outro local.</p>\r\n<h2 style=\"text-align: center;\">Datas e Pontos Flutuantes </h2>\r\n<p style=\"text-align: justify;\">Como se sabe, as datas no Delphi (TDate, TDateTime e TTime) são armazenadas como números do tipo extended, de ponto flutuante. Sabe-se também que números de ponto flutuante não tem uma precisão muito boa. Comparações e operações matemáticas com tais números causam erros de arredondamento que não são percebidos até que se precise manipular casas decimais com mais precisão, fato que acontece quando tentamos obter valores muito pequenos a partir de datas, como milissegundos. É neste ponto onde a leitura deste artigo pode ajudar você.</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Primeiramente quero salientar <span style=\"font-size: 12pt;\">que</span> existem inúmeros outros algoritmos que fazem o mesmo e que até retornam a quantidade de meses, mas estejam avisados; a quantidade de meses exatos entre duas datas não pode ser conseguida de forma direta, apenas usando mais um loop, o que deixaria a função mais lenta. Deixei propositalmente a quantidade de meses de fora para que o leitor tente implementar.</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Antes de começar preciso dizer que não vou simplesmente colar aqui a solução, pretendo explicar de forma didática, e estou subentendendo que o leitor já possui conhecimento básico da linguagem Delphi, bem como termos desta linguagem. Estejam avisados. O que vem a seguir é um caminho com lacunas faltando. Não espere copiar, colar e compilar sem erros. Sem mais delongas, lá vamos nós....</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: center;\">As mudanças propostas para a VCL</h2>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Infelizmente não lembro em qual Delphi a VCL foi corrigida, por isso não tenho como dizer com certeza se você vai ou não precisar utilizar as funções apresentadas aqui ou aquelas que VCL já dispõe. O exemplo de uso no final deste artigo demostra esta função para que ela retorne exatamente 1 milissegundo. Caso ao executar a função o valor retornado não seja 1 milissegundo, provavelmente seu Delphi não possui a VCL corrigida e assim será necessário usar as funções que estão aqui. Esta seção só será útil, portanto, caso seu Delphi ainda não tenha sido corrigido.</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Precisamos criar duas funções para o cálculo preciso de milissegundos entre duas datas. Estas funções foram extraídas e modificadas a partir da proposta de alteração da unit DateUtils explicada anteriormente. O crédito por estas funções deve ser dado a John Herbster , que propôs as mudanças na unit DateUtils (<a href=\"http://qc.embarcadero.com/wc/qcmain.aspx?d=56957\">Report #56957 - A Fix for DateUtils Date-Time Compare Functions</a>). Os comentários originais, em inglês, foram preservados.</p>\r\n<pre class=\"line-numbers language-pascal\"><code>{ Converts a TDateTime variable to Int64 milliseconds from 0001-01-01.}\r\nfunction DateTimeToMilliseconds(aDateTime: TDateTime): Int64;\r\nvar\r\n TimeStamp: TTimeStamp;\r\nbegin\r\n { Call DateTimeToTimeStamp to convert DateTime to TimeStamp: }\r\n TimeStamp := DateTimeToTimeStamp(aDateTime);\r\n { Multiply and add to complete the conversion: }\r\n Result := Int64(TimeStamp.Date) * MSecsPerDay + TimeStamp.Time;\r\nend;\r\n\r\n{ Uses DateTimeToTimeStamp, TimeStampToMilliseconds, and DateTimeToMilliseconds. }\r\nfunction MillisecondsBetween(const aNow, aThen: TDateTime): Int64;\r\nbegin\r\n if aNow &gt; aThen then\r\n Result := DateTimeToMilliseconds(aNow) - DateTimeToMilliseconds(aThen)\r\n else\r\n Result := DateTimeToMilliseconds(aThen) - DateTimeToMilliseconds(aNow);\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">A unit DateUtils também possui uma função de nome MillisecondsBetween no entanto esta função não é tão precisa como parece. A experiência mostra que ao criar dois valores TDateTime usando a função EncodeDateTime e que distam entre si apenas 1 milissegundo, o retorno da função MillisecondsBetween não retorna 1, como era de se esperar, provando que ela não é precisa.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: center;\">Apresentando a função DecodeDateDiff</h2>\r\n<p style=\"text-align: justify;\">Primeiramente devemos declarar o tipo do resultado. Um record, com todas as variáveis retornáveis.</p>\r\n<pre class=\"line-numbers language-pascal\"><code>type\r\n TDecodedDateDiff = record\r\n Years: Word;\r\n Weeks: Byte;\r\n Days: Word;\r\n Hours: Byte;\r\n Minutes: Byte;\r\n Seconds: Byte;\r\n Milliseconds: Word;\r\n end;</code></pre>\r\n<p style=\"text-align: justify;\">Agora vamos à função que faz todo o trabalho. Comentários internos dão as dicas do que está sendo feito. Quero salientar que esta função foi criada há cerca de 10 anos e que provavelmente hoje em dia devem haver formas mais eficientes de se obter o mesmo resultado.</p>\r\n<pre class=\"language-pascal\"><code>function DecodeDateDiff(aStartDateTime, aFinishDateTime: TDateTime): TDecodedDateDiff;\r\nvar\r\n MilliSeconds: Int64;\r\n WholeStartDate, WholeEndDate: TDateTime;\r\n Days: Cardinal;\r\n Years: Word;\r\nbegin\r\n { Validando as datas, que devem ser passadas corretamente para função, isto é,\r\n a data final tem de ser maior ou igual à data inicial. Qualquer outro caso é\r\n inválido e lança a exceção }\r\n if aStartDateTime &gt;= aFinishDateTime then\r\n raise Exception.Create(\'A data final é menor que a data inicial\');\r\n\r\n { Zerando as variáveis que serão usadas no decorrer da função }\r\n ZeroMemory(@Result,SizeOf(TDecodedDateDiff));\r\n Years := 0;\r\n\r\n { Obtendo a quantidade exata de millissegundos entre as datas }\r\n MilliSeconds := MilliSecondsBetween(aStartDateTime,aFinishDateTime);\r\n\r\n { A partir da quantidade exata de millissegundos, podemos obter a quantidade\r\n exata de dias, já que sabemos quantos millissegundos hão exatamente em um dia }\r\n Days := MilliSeconds div MSecsPerDay;\r\n\r\n { Abaixo estamos normalizando as datas, de forma que a data inicial começe\r\n exatamente no início do ano subsequente a ela, e a data final termine\r\n exatamente no final do ano anterior a ela }\r\n WholeStartDate := IncMilliSecond(EndOfTheYear(aStartDateTime));\r\n WholeEndDate := IncMilliSecond(StartOfTheYear(aFinishDateTime),-1);\r\n\r\n { O loop abaixo vai realizar duas ações: Decrementar a quantidade de dias\r\n obtida anteriormente da quantidade de dias no ano sendo verificado no momento\r\n e incrementar a variável Years, de forma a obter a quantidade de anos. }\r\n while WholeStartDate &lt; WholeEndDate do\r\n begin\r\n Dec(Days,DaysInYear(WholeStartDate));\r\n\r\n Inc(Years);\r\n\r\n WholeStartDate := IncDay(WholeStartDate,DaysInYear(WholeStartDate));\r\n end;\r\n\r\n { Caso a quantidade de dias seja maior ou igual a quantidade de dias no ano\r\n final, precisamos realizar um último ajuste para incrementar anos e\r\n decrementar dias de acordo com a quantidade de dias no ano da data final }\r\n if Days &gt;= DaysInYear(aFinishDateTime) then\r\n begin\r\n Inc(Years);\r\n Dec(Days,DaysInYear(aFinishDateTime));\r\n end;\r\n\r\n { Neste ponto, a variável Years contém a quantidade de anos inteiros entre as\r\n datas inicial final ... }\r\n\r\n Result.Years := Years;\r\n\r\n { ... E a variável Days contém a quantidade de dias que sobraram. Obtemos\r\n portanto a quantidade de semanas, que é um valor exato }\r\n\r\n Result.Weeks := Days div 7;\r\n Result.Days := Days mod 7;\r\n\r\n { A partir daqui a verificação é simples matemática, extraindo horas, minutos\r\n e segundos dos millisegundos }\r\n MilliSeconds := MilliSeconds mod MSecsPerDay;\r\n\r\n Result.Hours := MilliSeconds div (SecsPerHour * MSecsPerSec);\r\n MilliSeconds := MilliSeconds mod (SecsPerHour * MSecsPerSec);\r\n\r\n Result.Minutes := MilliSeconds div (SecsPerMin * MSecsPerSec);\r\n MilliSeconds := MilliSeconds mod (SecsPerMin * MSecsPerSec);\r\n\r\n Result.Seconds := MilliSeconds div MSecsPerSec;\r\n MilliSeconds := MilliSeconds mod MSecsPerSec;\r\n\r\n { O que sobrar no final, será apenas milissegundos! }\r\n Result.Milliseconds := MilliSeconds;\r\nend;</code></pre>\r\n<h2 style=\"text-align: justify;\">Um exemplo de uso</h2>\r\n<pre class=\"language-apacheconf\"><code>var\r\n Data1, Data2: TDateTime;\r\n Diferenca: TDecodedDateDiff;\r\nbegin\r\n data1 := EncodeDateTime(2017,12,13,0,0,0,0);\r\n data2 := EncodeDateTime(2017,12,13,0,0,0,1);\r\n Diferenca := DecodeDateDiff(data1,data2);\r\n // Todos os membros de \"Diferenca\" devem ser zero, exceto o membro \"Milliseconds\"\r\n // que deve possuir o valor 1\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">É isso! Agora você pode obter um intervalo entre duas datas com precisão de milissegundos! Se você quiser modificar o algoritmo, sinta-se a vontade, mas compartilhe com todos (como eu fiz).</p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-07-26 02:01:09',24,'','2020-07-10 20:41:49',24,0,'0000-00-00 00:00:00','2017-08-24 03:00:00','0000-00-00 00:00:00','{\"image_intro\":\"\",\"float_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000092\\/FullArticle.png\",\"float_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"article_layout\":\"\",\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_associations\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_page_title\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',20,79,'Delphi, Addicted 2 Delphi, Milissegundos, Precisão, Diferença, Comparação, Datas, TDateTime, Milliseconds, Precision, Diff, Comparing, Dates','Nas minhas andanças pela web, descobri que a unit DateUtils, continha vários problemas de arredondamento e que sua precisão era muito pobre. Descobri também que existia uma proposta no site da Embarcadero (Quality Central) para mudanças nesta unit de forma que ela seja tão precisa quanto um milissegundo. Hoje este problema de precisão não existe mais, no entanto ainda é válida a técnica usada para criação de uma função que obtém precisamente (1 milissegundo) a diferença entre duas datas passadas por parâmetro. O resultado é um record com anos, semanas, dias, horas, minutos, segundos e milissegundos que corresponde à diferença de datas completa! Existem por aí inúmeros algoritmos que realizam tal tarefa, mas não com esta precisão e não com esta velocidade ;)',1,6403,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(93,226,'Corrigindo mensagens de erro EReconcileError truncadas','corrigindo-mensagens-de-erro-ereconcileerror-truncadas','<p style=\"text-align: justify;\">Uma das primeiras coisas que se aprende ao começar a desenvolver em 3 camadas é como manipular mensagens de erro que são geradas pelo servidor de aplicações (middleware). O tipo destas mensagens de erro é <strong>EReconcileError</strong> e toda vez que há algum problema no middleware, o mesmo envia este erro de volta ao cliente que o gerou. Tudo funciona perfeitamente a não ser por um detalhe que só foi percebido por mim quando as mensagens de erro começaram a vir mais detalhadas do servidor. Mais texto era retornado e eu notei que esse texto vinha truncado em exatos 255 caracteres. Por que isso ocorre e como corrigir, é o que pretendo explicar neste artigo.</p>\r\n','\r\n<p style=\"text-align: justify;\">As mensagens de erro de conciliação (Reconcile Error) são enviadas ao cliente sempre que há algum problema na persistência dos dados no middleware. O cliente, por sua vez, captura e é capaz de manipular estes erros no evento <strong>OnReconcileError</strong> do componente <strong>TClientDataset</strong>. Através de um form padronizado, disponibilizado pelo próprio Delphi (Reconcile Error Dialog) podemos, dentro deste evento, manipular a mensagem de erro e exibir ao usuário uma tela com opções, permitindo que ele mesmo possa corrigir, ignorar ou cancelar a atualização dos dados (<strong>ApplyUpdates</strong>). </p>\r\n<p style=\"text-align: justify;\">Para começar, e para quem nunca viu, abaixo está um modelo da tela Reconcile Error Dialog com algumas alterações realizadas por mim. O modelo original desta tela pode ser obtido a partir do próprio Delphi, no caminho <em><strong>File &gt; New &gt; Other...</strong></em> e selecionando o item <em><strong>VCL Reconcile Error Dialog</strong></em>.</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/datasnap/ID000093/ReconcileErrorDialogModificado.jpg\" width=\"641\" height=\"401\" border=\"0\" /></p>\r\n<p style=\"text-align: justify;\">Como uso PostgreSQL em meus projetos eu tenho acesso a 3 tipos de informação na mensagem de erro gerada pelo banco de dados. O erro propriamente dito, um detalhe, que seria exatamente o porquê do erro ter ocorrido e um contexto, que seria o comando SQL que gerou a exceção. Fiz as alterações na caixa de diálogo e criei uma rotina de parse para obter as 3 partes da mensagem de erro separadamente. E ao testar tive uma infeliz descoberta. Nas duas primeiras abas as informações apareceram de forma completa, já na terceira (contexto) a mensagem foi cortada, veja:</p>\r\n<pre style=\"border: 2px solid white; font-family: monospace; background-color: black; color: white; padding: 3px;\">ERRO: duplicar valor da chave viola a restrição de unicidade \"uc_usu_va_login\"\r\nDETAIL: Chave (va_login)=(admin) já existe.\r\nCONTEXT: comando SQL \"INSERT INTO USUARIOS (VA_NOME\r\n ,VA_LOGIN\r\n ,CH</pre>\r\n<p style=\"text-align: justify;\">Mas na verdade, a mensagem que o banco gerou foi a seguinte:</p>\r\n<pre style=\"border: 2px solid white; font-family: monospace; background-color: black; color: white; padding: 3px;\">ERRO: duplicar valor da chave viola a restrição de unicidade \"uc_usu_va_login\"\r\nDETAIL: Chave (va_login)=(admin) já existe.\r\nCONTEXT: comando SQL \"INSERT INTO USUARIOS (VA_NOME\r\n ,VA_LOGIN\r\n ,CH_SENHA\r\n ,VA_EMAIL)\r\n VALUES (pVA_NOME\r\n ,pVA_LOGIN\r\n ,pCH_SENHA\r\n ,pVA_EMAIL)\"\r\n PL/pgSQL function \"idu_usuarios\" line 7 at comando SQL</pre>\r\n<p style=\"text-align: justify;\">Após muitas depurações (às vezes complexas) cheguei a conclusão de que quem estava cortando as mensagens era o DataSnap, mais especificamente a biblioteca Midas.dll ou, no meu caso, o midas.obj, já que eu não uso a biblioteca Midas.dll. Durante a depuração obtive mais uma curiosidade sobre o fato: a mensagem era realmente truncada exatamente em 255 caracteres.</p>\r\n<p style=\"text-align: justify;\">Por sorte as versões do Delphi a partir de 2010 trazem consigo o código-fonte do midas.dll (em C++), por isso eu pude buscar neste código-fonte o local exato onde as mensagens de erro eram montadas e finalmente descobri como aumentar a quantidade de caracteres na mensagem de erro \"Reconcile\".</p>\r\n<p style=\"text-align: justify;\">Após instalar a personalidade C++ do Delphi, de forma poder compilar o midas, encontrei a linha do erro e descobri inclusive que existe uma solicitação de conserto no QC (<a href=\"http://qc.embarcadero.com/wc/qcmain.aspx?d=84960\">http://qc.embarcadero.com/wc/qcmain.aspx?d=84960</a>) o qual parece ter sido ignorado pelo pessoal da Embarcadero, já que o campo \"Resolution\" está como \"Deferred to Next Rel\", algo como \"Adiado para o próximo release\". Só que a solicitação é de 2010 e eu estou usando o Delphi XE<a href=\"#obs1\"><sup>1</sup></a> que ao meu ver já deveria ter a solução mas cá estou eu corrigindo eu mesmo.</p>\r\n<p>O problema está dentro do método \"Clone\", da classe \"DSBASE\" dentro do fonte \"ds.cpp\" na linha 2133 (Delphi XE, Update1). Abaixo está o bloco de código. A linha 4 é a linha problemática:</p>\r\n<pre class=\"line-numbers language-cpp\"><code>// Set the third field for the error string.\r\nLdStrCpy((pCHAR)pFldDes-&gt;szName, szdsERRMESSAGE);\r\npFldDes-&gt;iFldType = fldZSTRING;\r\npFldDes-&gt;iUnits1 = 255; // Increased on request.. DBIMAXMSGLEN;\r\npFldDes++;</code></pre>\r\n<p style=\"text-align: justify;\">Note que é bem interessante a linha do problema. Ela possui um valor constante de 255, o qual limita o tamanho das mensagens de erro e um comentário \"Increased on request\" (Aumente quando pedirem). Note também que ao lado do comentário, existe a constante <strong>DBIMAXMSGLEN</strong>, a qual tem um mnemônico bem sugestivo (Tamanho Máximo de Mensagem). Eu já havia encontrado a declaração desta constante em outro fonte e já desconfiava dela como sendo a responsável pelo problema. Alterei o seu valor, mas como ela não estava sendo usada, a mensagem de erro sempre vinha truncada. Vale ressaltar ainda que após <strong>DBIMAXMSGLEN</strong> há um ponto-e-vírgula (;) o que me leva a pensar que anteriormente (não sei quando) esta linha era exatamente aquela que ficou após a minha correção, isto é:</p>\r\n<pre class=\"line-numbers language-cpp\"><code>pFldDes-&gt;iUnits1 = DBIMAXMSGLEN;</code></pre>\r\n<p style=\"text-align: justify;\">É como se alguém, deliberadamente tivesse fixado o valor do campo em 255, removendo a implementação anterior que era parametrizada e por isso mais correta. Após realizar a substituição da linha eu aumentei o valor de <strong>DBIMAXMSGLEN</strong> para <strong>1024</strong>. <strong>DBIMAXMSGLEN</strong> está declarada em \"bdetypes.h\" como um define. Após a correção a linha ficou assim:</p>\r\n<pre class=\"line-numbers language-cpp\"><code>#define DBIMAXMSGLEN 1024 // Max message len</code></pre>\r\n<p style=\"text-align: justify;\">Depois destas duas alterações em \"ds.cpp\" e \"bdetypes.h\" compilei, teste e o resultado foi o esperado: a mensagem de erro foi apresentada na íntegra na caixa de diálogo Reconcile, entretanto, havia um pequeno inconveniente. Após compilar o projeto do midas foi gerada a dll midas (midas.dll) só que eu não uso a biblioteca midas nos meus projetos. Ao invés disso, como muitos de nós programadores Delphi, eu uso o <strong>MidasLib</strong>.</p>\r\n<p style=\"text-align: justify;\">O <strong>MidasLib</strong> é um arquivo .pas (unit) que liga estaticamente o código da biblioteca midas na aplicação que estiver sendo compilada. O que muita gente talvez não saiba é que a ligação estática do midas.dll não usa efetivamente esta biblioteca, mas sim, o arquivo <strong>midas.obj</strong> o qual existe dentro da pasta LIB do Delphi. Se você der uma olhada no arquivo MidasLib.pas você vai entender exatamente o que eu estou dizendo.</p>\r\n<p style=\"text-align: justify;\">Não tenho muita experiência com C++. Tentei de várias maneiras compilar este arquivo (<strong>midas.obj</strong>) com o intuito de usá-lo ligado estaticamente ao meu projeto, mas não obtive sucesso. Procurei em vários locais na Internet como criar um arquivo .obj e finalmente cheguei a conclusão que no caso do midas isso não seria possível porque dentre os fontes do midas eu não tenho um fonte de nome <strong>midas.cpp</strong>.</p>\r\n<p style=\"text-align: justify;\">Arquivos .obj não são o arquivo final que um projeto cria, mas sim, arquivos intermediários. Numa comparação com o Delphi, os arquivos .obj, tem uma função semelhante a dos arquivos .dcu. Com esta comparação, fica claro que eu jamais geraria um arquivo <strong>midas.obj</strong> sem que houvesse um arquivo <strong>midas.cpp</strong>. A Embarcadero deixou de fora este fonte. Os motivos disso eu não sei, mas tenho certeza de que se eu entendesse mais de C++ eu poderia criar um arquivo midas.cpp e gerar meu próprio <strong>midas.obj</strong>.</p>\r\n<p style=\"text-align: justify;\">Como não tenho toda esta capacidade, usei um pouco de força-bruta para resolver meu problema. Lembrei que poderia fazer algo bem mais radical do que alterar o código-fonte. Eu posso alterar alguns bytes diretamente no arquivo obj e foi exatamente o que eu fiz. Basicamente eu, usando um editor hexadecimal, procurei pela seguinte sequencia de bytes:</p>\r\n<pre style=\"border: 2px solid white; font-family: monospace; background-color: black; color: white; padding: 3px; text-align: center; font-size: 12pt;\">C7 43 28 <span style=\"color: #ff0000;\">FF</span> 00 00 00</pre>\r\n<p>E a substituí exatamente por esta sequencia:</p>\r\n<pre style=\"border: 2px solid white; font-family: monospace; background-color: black; color: white; padding: 3px; text-align: center; font-size: 12pt;\">C7 43 28 <span style=\"color: #ff0000;\">00 04</span> 00 00</pre>\r\n<p style=\"text-align: justify;\">Note que após o byte de valor 28 havia FF que é o tamanho da mensagem de erro 255. O que eu fiz foi aumentar para 1024 (<strong>400h</strong>) caracteres, por isso usei 00 no lugar de FF e completei o byte seguinte com 04. Realmente é esquisito o fato de que esta alteração gere o hexa <strong>400h</strong>. Acredito que estes bytes devem estar em outro tipo de <a href=\"http://en.wikipedia.org/wiki/Endianness\">endianess</a>, assunto que ainda é um tanto confuso pra mim, mas que imagino mais ou menos como deve ser feito, veja:</p>\r\n<pre style=\"border: 2px solid white; font-family: monospace; background-color: black; color: white; padding: 3px; text-align: center; font-size: 12pt;\"><span style=\"color: #ff0000;\">00</span> 0<span style=\"color: #ff0000;\">4</span> 00 00 = 00 00 0<span style=\"color: #ff0000;\">4 00</span>h = <span style=\"color: #ff0000;\">400h</span> (zeros à esquerda não valem nada aqui)</pre>\r\n<p style=\"text-align: justify;\">A sequencia acima tem 4 bytes. Lendo da direita para esquerda, <span style=\"background-color: #ffff00;\">[o primeiro byte é zero zero]</span>, <span style=\"background-color: #ffff00;\">[o segundo é zero zero]</span>, <span style=\"background-color: #ffff00;\">[o terceiro é zero 4]</span> e o <span style=\"background-color: #ffff00;\">[quarto é zero zero]</span>. Escrevendo diretamente cada byte (entre colchetes) temos exatamente <strong>00 00 04 00</strong>. Desprezando os zeros à esquerda temos <strong>400</strong>, logo, achamos o porquê de a linha modificada ser deste jeito ;)</p>\r\n<p style=\"text-align: justify;\">A parte interessante começa aqui. Você me pergunta como eu achei esta sequencia e como eu sei que isso funciona. Aí é onde entra a grande maravilha chamada ASSEMBLY e o <a href=\"http://www.hex-rays.com/products/ida/support/download_freeware.shtml\">IDA - Interactive Disassembler</a> que é fantasticamente gratuito e não menos poderoso para quem entende um pouco de ASSEMBLY.</p>\r\n<p style=\"text-align: justify;\">Ao abrir o midas.obj no IDA e deixar que ele seja processado, eu vou na lista de funções exportadas \"Exports\" e busco pela função chamada \"Clone\" da classe \"DSBASE\". Na lista, esta função aparecerá como \"DSBASE::Clone(ulong,int,int,TDSBASE **)\". Após dar dois cliques eu sou enviado a aba \"IDA View-A\" que vai listar o código assembly da função. O cursor estará parado exatamente no início da implementação desta função, isto é, se fosse uma função Delphi, estaria posicionado exatemente em cima da primeira instrução após o \"begin\" de um procedure ou função. Descendo um pouco no código eu acho o seguinte trecho:</p>\r\n<pre class=\"line-numbers language-nasm\"><code>; #line 2128\r\npush offset aError_message ; \"ERROR_MESSAGE\"\r\npush ebx ; lpString1\r\ncall lstrcpyA\r\n; #line 2129\r\nmov dword ptr [ebx+20h], 1\r\n; #line 2130\r\nmov dword ptr [ebx+28h], 0FFh\r\n; #line 2131\r\nadd ebx, 4Ch ; \'L\'</code></pre>\r\n<p style=\"text-align: justify;\">Note que existem comentários que o próprio IDA inclui, sobre as linhas de onde cada instrução subsequente se aplica e sobre o valor de algumas constantes que foram utilizadas no fonte original. O código-fonte original é o ds.cpp que eu descobri quando consegui achar a linha de código com o problema. Abaixo, está o mesmo trecho de código acima, só que no bom e velho C++:</p>\r\n<pre class=\"line-numbers language-cpp\" style=\"counter-reset: linenumber 2127;\" data-start=\"2128\"><code>LdStrCpy((pCHAR)pFldDes-&gt;szName, szdsERRMESSAGE);\r\npFldDes-&gt;iFldType = fldZSTRING;\r\npFldDes-&gt;iUnits1 = 255;\r\npFldDes++;</code></pre>\r\n<p style=\"text-align: justify;\">Acima eu coloquei as linhas do arquivo onde cada linha de código aparece, para que olhando atentamente os dois blocos de código perceba-se inclusive como o compilador converte as linhas de código em instruções ASSEMBLY. É bem interessante. Como se pode ver, curiosamente, a função LdStrCpy se transforma em 3 linhas no ASSEMBLY. Note que os dois parâmetros desta função são colocados na pilha (push) antes da função ser chamada (call). Note que a colocação dos parâmetros na pilha é feita de trás pra frente, isso por conta da convenção de chamada usada no C++ (stdcall). Outra coisa interessante é que o nome da função mudou de LdStrCpy no C++ para lstrcpyA no ASSEMBLY. Isso porque LdStrCpy parece ser um tipo de alias (wrapper) para a função de api real (lstrcpyA).</p>\r\n<p style=\"text-align: justify;\">Voltando a explicação inicial, precisamos mudar no assembly a linha 2130 de forma que ela corresponda a</p>\r\n<pre class=\"line-numbers language-cpp\"><code>pFldDes-&gt;iUnits1 = 1024;</code></pre>\r\n<p style=\"text-align: justify;\">no ASSEMBLY, esta linha (2130) se torna</p>\r\n<pre class=\"line-numbers language-nasm\"><code>mov dword ptr [ebx+28h], 400h</code></pre>\r\n<p style=\"text-align: justify;\"><strong>400h</strong> é o valor <strong>1024</strong> em hexadecimal. No IDA ao clicar em cima da palavra \"mov\" e ir até a aba \"Hex View-A\", a sequencia <strong>C7 43 28 FF 00 00 00</strong> fica destacada. Esta sequencia de bytes aparentemente sem sentido é a que corresponde ao comando <strong>mov dword ptr [ebx+28h], 0FFh</strong> inteiro! Nesta sequencia o <strong>FF</strong> corresponde ao ao valor <strong>255</strong> o qual tem de ser substituido por <strong>1024</strong> que é <strong>400</strong> em hexadecimal, assim, fazendo a substituição da sequencia <strong>C7 43 28 FF 00 00 00</strong> pela sequencia <strong>C7 43 28 00 04 00 00</strong> conseguimos finalmente o resultado esperado e tudo estaria pronto para compilar, no entanto não é bem assim.</p>\r\n<p style=\"text-align: justify;\">Não sei porque, mesmo substituindo o arquivo <strong>midas.obj</strong> na pasta lib do Delphi, não consegui obter o comportamento esperado. Na verdade busquei todo o sistema para saber se não tinha algum arquivo <strong>midas.obj</strong> no path. Todos os que haviam eu apaguei, mantendo apenas aquele existente na pasta lib. Sem efeito. Foi aí que tive uma sacada de Dr. House. Apaguei o ultimo <strong>midas.obj</strong> existente (o da pasta LIB) e mesmo assim minha aplicação compilou! Isso prova que o Delphi esta usando um <strong>midas.obj</strong> contido em algum BPL ou outro arquivo similar e que aquele arquivo ali não serve pra nada. Para resolver o problema eu movi para os fontes da minha aplicação o arquivo <strong>MidasLib.pas</strong> e o arquivo <strong>midas.obj</strong>, assim, este fonte é compilado juntamente com a minha aplicação e usa efetivamente o meu <strong>midas.obj</strong> modificado.</p>\r\n<p style=\"text-align: justify;\">Agora sim! Tudo funciona como deve: minha aplicação retorna as mensagens de erro Reconcile completas (ou mais especificamente com até 1024 caracteres) e eu estou vinculando estaticamente o código do <strong>midas.obj</strong> no meu projeto tornando desnecessário o uso do <strong>midas.dll</strong>. Aliás, como uma observação final, é importante incluir o nosso <strong>MidasLib</strong> tanto no cliente quanto no Servidor.</p>\r\n<p style=\"text-align: justify;\">Para entender um pouco mais sobre algumas coisas que foram faladas aqui, seguem alguns links muito úteis</p>\r\n<ul>\r\n<li><a href=\"http://ref.x86asm.net/index.html\">X86 Opcode and Instruction Reference Home - Referência de instruções Assembly e seus opcodes correspondentes</a></li>\r\n<li><a href=\"http://pt.wikipedia.org/wiki/Opcode\">Código de operação - Definição geral de Opcode em português</a></li>\r\n<li><a href=\"http://www.hex-rays.com/index.shtml\">Hex Rays - Site oficial do IDA - Interactive Disassembler</a></li>\r\n</ul>\r\n<hr />\r\n<p style=\"text-align: justify;\"><sup><a name=\"obs1\"></a>1</sup> Quando este artigo foi publicado originalmente eu estava usando o Delphi XE. Não sei se versões mais recentes já corrigiram isso. O intuito de publicar este artigo sem verificar se a correção já foi realizada é que muitas pessoas ainda podem ter o problema por usarem Delphis mais antigos e também porque aqui eu mostro algumas técnicas interessantes para realizar patches diretamente em arquivos binários.</p>',0,99,'2016-07-26 19:52:24',24,'','2016-11-18 12:47:29',24,0,'0000-00-00 00:00:00','2016-07-26 19:52:24','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',42,7,'','',1,265,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(94,228,'User Control DXC Edition','usercontrol-dx-edition','<p style=\"text-align: justify;\">Esta é a <strong>página oficial</strong> da evolução do aclamado controle de usuários: <strong><span style=\"color: #ff0000;\">U</span>ser<span style=\"color: #ff0000;\">C</span>ontrol Suite <span style=\"color: #ff0000;\">D</span>elphi E<span style=\"color: #ff0000;\">x</span>perts <span style=\"color: #ff0000;\">C</span>onsortium <span style=\"color: #ff0000;\">E</span>dition</strong>, também conhecido como <strong>UCDXCE</strong>. Esta edição é focada na simplicidade de uso, retrocompatibilidade básica com a versão oficial final (2.31 RC4), organização de código-fonte, correção de bugs e adição de funcionalidades exclusivas.</p>\r\n','\r\n<h2>História</h2>\r\n<p style=\"text-align: justify;\">A finalidade desta distribuição do User Control (UCDXCE) é unificar todas as versões disponíveis a fim de tornar-se \"A\" distribuição definitiva deste que é um excelente componente para gerenciamento de usuários e permissões.</p>\r\n<p style=\"text-align: justify;\">Minha intenção não é usurpar o projeto dos outros, por isso, quero deixar bem claro aqui que <span style=\"text-decoration: underline;\">este componente originalmente não foi desenvolvido por mim, mas sim por um desenvolvedor principal e várias outras pessoas (colaboradores)</span> há alguns anos e o crédito por todo e qualquer desenvolvimento até a revisão 1 (inicial) dessa nova distribuição deve ser dado aos pioneiros. Todo o desenvolvimento a partir da revisão 2 será composto por customizações e <em>bugfixes</em> de autorias diversas, iniciadas por mim mesmo.</p>\r\n<p style=\"text-align: justify;\">A motivação para realizar este trabalho reside no fato de que, atualmente, a fonte original do UserControl (<a href=\"https://sourceforge.net/projects/usercontrol\">https://sourceforge.net/projects/usercontrol</a>) parece não conter quaisquer atualizações significativas há algum tempo e os arquivos existentes não parecem seguir qualquer ordem lógica, o que torna o uso do componente em projetos grandes um risco para a maioria dos desenvolvedores sérios, preocupados com a segurança em seus sistemas. A última versão disponível no SourceForge é a 2.31 RC4 e foi movimentada pela última vez em 2013. Ao que tudo indica, não houve muitas alterações no código, a não ser a inclusão de um conector para o FireDAC.</p>\r\n<p style=\"text-align: justify;\">No SF existe também uma versão 2.7, mas ela é de 2004 e é para Delphi 6! Outro fato curioso é que a última versão diz ser para Delphi XE4, mas a pasta contida dentro do arquivo compactado continha \"XE3\" no nome. Todas Estas incoerências, mais o fato de não haver mais atualizações, juntamente com o fato de que o código-fonte sempre foi muito desorganizado (não sou somente eu quem acha isso...) deixa qualquer desenvolvedor preocupado. Muitos de nós não podemos arriscar utilizar um componente crítico como esse sem que haja um mínimo de segurança em seu uso.</p>\r\n<p style=\"text-align: justify;\">Tudo isso me motivou a começar essa jornada de padronização/correção e melhorias, mas não tenho intenção de gerenciar milhares de solicitações de melhorias ou correções de bugs. Eu meramente me considero a pessoa que deu o pontapé inicial para elevar o User Control a um outro nível! Eventualmente uma ou outra correção poderá ser feita, mediante solicitação, contudo imagino que não sejam tantas assim, já que os desenvolvedores fizeram um bom trabalho, apesar do código despadronizado :)</p>\r\n<h2>Onde baixar? Como Instalar?</h2>\r\n<p style=\"text-align: justify;\">Os fontes do UCDXCE estão disponíveis gratuitamente no endereço abaixo:</p>\r\n<p style=\"text-align: center;\"><a href=\"https://osdn.net/projects/ucdxce\">https://osdn.net/projects/ucdxce</a></p>\r\n<p style=\"text-align: justify;\">Recomendo que se use o SVN para baixar os fontes, isto é, não baixe os fontes gerando um zip pelo site. Usando o SVN você poderá a qualquer momento baixar a última versão dos fontes de forma prática (SVN Update).</p>\r\n<p style=\"text-align: justify;\">Antes de instalar o UCDXCE instale o seu pré-requisito:</p>\r\n<p style=\"text-align: center;\"><a href=\"https://osdn.net/projects/pngcdxme\">https://osdn.net/projects/pngcdxme</a></p>\r\n<p style=\"text-align: justify;\">A instalação tanto do pré-requisito como do UCDXCE e de seus conectores segue o padrão de qualquer componente Delphi. Primeiro procure a pasta de projeto correspondente ao seu Delphi e instale os arquivos DPK. Arquivos DPK que que terminam com R, compilam BPLs de runtime e arquivos DPK que terminam com D, compilam BPLs de designtime. Ambos os pacotes precisam ser compilados, mas apenas o pacote D precisa ser instalado.</p>\r\n<p style=\"text-align: justify;\">Após a instalação bem sucedida, inclua no library path do Delphi as pastas dcu, res.</p>\r\n<h2>Considerações finais</h2>\r\n<p style=\"text-align: justify;\">Este componente ainda está sendo desenvolvido. Nem todas as suas funcionalidades foram testadas e algumas delas ainda não foram implementadas. Ele está em \"fase alpha\", por isso eu não recomendo sua utilização em ambientes finais (produção). Ele está sendo disponibilizado \"como está\" apenas para apreciação da comunidade. Sugestões e principalmente relatórios de bugs são bem vindos! Use os comentários abaixo deste artigo para interagir comigo.</p>\r\n<h2>Galeria de imagens</h2>\r\n<p style=\"text-align: center;\"><a class=\"cbg1\" href=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard01.png\"><img src=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard01.png\" width=\"20%\" /></a> <a class=\"cbg1\" href=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard02.png\"><img src=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard02.png\" width=\"20%\" /></a> <a class=\"cbg1\" href=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard04.png\"><img src=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard04.png\" width=\"20%\" /></a> <a class=\"cbg1\" href=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard05.png\"><img src=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard05.png\" width=\"20%\" /></a></p>\r\n<p style=\"text-align: center;\"><a class=\"cbg1\" href=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard06.png\"><img src=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard06.png\" width=\"20%\" /></a> <a class=\"cbg1\" href=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard07.png\"><img src=\"images/zost/ourproducts/delphi/components/ucdxe/gallery/Clipboard07.png\" width=\"20%\" /></a></p>\r\n<p style=\"text-align: center;\"><span style=\"color: #ff0000; font-size: 8pt;\">As telas poderão sofrer alterações sem prévio aviso e poderão ser diferentes daquelas existentes na versão atual do UCDXCE</span></p>\r\n<h2 style=\"text-align: left;\">Diagrama de componentes e ligações</h2>\r\n<p style=\"text-align: left;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/zost/ourproducts/delphi/components/ucdxe/ucdxce.png\" width=\"743\" height=\"537\" /></p>\r\n<p style=\"text-align: center;\"><span style=\"color: #ff0000; font-size: 8pt;\">Este diagrama poderá sofrer alterações sem prévio aviso. Componentes poderão ser removidos, adicionados ou terem seus ícones modificados, tornando-os diferentes daqueles instalados na versão atual do UCDXCE</span></p>\r\n<hr />\r\n<p style=\"text-align: center; font-style: italic;\">Fontes hospedados no<a href=\"https://osdn.net/\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"//osdn.net/sflogo.php?group_id=11343&amp;type=3\" alt=\"OSDN\" width=\"210\" height=\"63\" border=\"0\" /></a></p>',1,82,'2016-07-28 02:54:29',24,'','2019-07-30 00:17:24',24,0,'0000-00-00 00:00:00','2017-07-05 02:54:00','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/zost\\/ourproducts\\/delphi\\/components\\/ucdxe\\/ucdxe.png\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','',78,2,'Delphi, Addicted 2 Delphi, Delphi Experts Consortium, UC, UserControl, User Control, DXC, DXCE, UCDXCE','Esta é a página oficial da evolução do aclamado controle de usuários: UserControl Suite Delphi Experts Consortium Edition, também conhecido como UCDXCE. Esta edição é focada na simplicidade de uso, retrocompatibilidade básica com a versão oficial final (2.31 RC4), organização de código-fonte, correção de bugs e adição de funcionalidades exclusivas',1,10598,'',1,'*','',''),(96,255,'Categorias e Itens de menu','categorias-e-itens-de-menu','<p style=\"text-align: justify;\">Qual a relação entre as categorias e os itens de menu no Joomla? Será mesmo que você está usando estes dois recursos de forma correta? Neste rápido artigo vou abordar com exemplos, qual a relação entre estes dois conceitos essenciais!</p>\r\n','\r\n<p style=\"text-align: justify;\">Você usa corretamente as categorias e os menus no Joomla, mas provavelmente em algum momento de sua vida, ao começar a trabalhar com este CMS houve confusão entre estes dois recursos pelo simples fato de que ambos levam o programador a acreditar que sua estruturas hierarquizadas têm alguma relação direta. Se você é um iniciante, continue lendo!</p>\r\n<p style=\"text-align: justify;\">Deve-se entender que as categorias e os menus no Joomla nada tem a ver um com outro. Você pode criar um site sem qualquer menu e todo categorizado ou o contrário, um site com um menu totalmente hierarquizado e nenhuma categoria. Os menus são apenas isso, links agrupados hierarquicamente que dão acesso a seções do seu site e as categorias são apenas formas de classificar (categorizar) seu conteúdo. Tanto as categorias como os menus podem assumir uma estrutura hierarquizada, mas no caso dos menus esta estrutura organiza mais diretamente as seções do site, já a hierarquização das categorias serve mais como forma de facilitar a organização das próprias categorias. </p>\r\n<p style=\"text-align: justify;\">No Joomla, categorias devem ser usadas como agrupadores de mais de um item, seja de outras categorias, seja de artigos ou outros conteúdos. Imagine categorias como pastas. Para manter o site com os menus e o breadcrumb funcionando corretamente, cada item de menu que for efetivamente conter mais de um item deve ser configurado como Category Blog ou Category List, pois ambos representam a listagem do conteúdo de uma determinada categoria.</p>\r\n<p style=\"text-align: justify;\">Por exemplo, neste site, o menu ZOST contém o item \"Informações\", o qual tem 3 subitens. Cada um destes subitens aponta para um artigo único e cada um desses 3 artigos são da categoria \"Informações\", logo, o item de menu \"Informações\" é do tipo \"Category Blog\", e isso fará com que ao clicar neste item, seus 3 subitens apareçam, um após o outro, no layout de blog. Não é necessário criar uma categoria para cada um destes 3 itens porque como eu disse anteriormente, categorias são como agrupadores de mais de um item. Se um item aparece sozinho em uma categoria e não se planeja incluir mais itens nessa categoria, então este item não deveria ter uma categoria própria. Eu poderia ter ocultado estes 3 itens do menu e ter mantido as coisas mais simples, mas como estes itens são únicos eu resolvi facilitar o seu cesso expondo-os.</p>\r\n<p style=\"text-align: justify;\">Ainda usando este site como exemplo, o menu \"Addicted 2 Delphi\", tem um subitem \"Nossos produtos\", que tem um subitem \"Delphi\", quem tem um subitem \"Componentes\". Cada um destes itens de menu é do tipo \"Category List\". Note que nenhum item final (artigo) está sendo exibido no menu, portanto, todas eles podem ter uma categoria associada, porque todos eles representam agrupadores, como pastas.</p>\r\n<p style=\"text-align: justify;\">Resumindo: Se um item de menu representa uma \"pasta\" na hierarquia, então uma categoria deve ser criada para representá-lo e caso o item de menu não represente uma pasta, mas sim um conteúdo (artigo), não é necessário e nem correto se ter uma categoria para ele.</p>',1,110,'2016-08-06 19:24:26',24,'','2016-08-31 00:20:25',24,0,'0000-00-00 00:00:00','2016-08-06 19:24:26','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',6,2,'','',1,2645,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',0,'*','',''),(97,257,'Serialização de Objetos & Persistência em Arquivos','serializacao-de-objetos-persistencia-em-arquivos','<p style=\"text-align: justify;\">Não importa o quão sofisticada sua aplicação seja, mesmo que ela salve seus dados em bancos de dados ou mesmo na nuvem, frequentemente haverá a necessidade de salvar dados de forma local. Normalmente isso é necessário quando se precisa persistir configurações que só dizem respeito à instância do programa sendo executado no momento. Existem algumas formas de fazer isso, sendo as mais comuns, arquivos INI e registro do Windows. Neste artigo vou ensinar uma forma de salvamento de arquivos a partir de um objeto serializado, que, no meu humilde entendimento, é a melhor forma de se persistir informações localmente.</p>\r\n','\r\n<p style=\"text-align: justify;\">Enquanto está na memória, um objeto é simplesmente uma sequência de bytes, uma estrutura de dados binários. A serialização consiste em pegar todos estes bytes que representam o objeto e seus dados na memória e obter uma representação que possa ser facilmente portada. Normalmente a serialização gera dados em texto plano, que pode ser lido facilmente por humanos e esta seria a forma mais tradicional de serialização, contudo a outra característica da serialização, a portabilidade, faz com que mesmo se salvando os dados binários, desde que estes sejam de alguma forma portáveis, haja uma serialização, portanto, serialização, grosso modo, é sinônimo de portabilidade para objetos.</p>\r\n<h2>RTTI: A base da serialização no Delphi</h2>\r\n<p style=\"text-align: justify;\"><strong>RTTI</strong> significa <em><strong>Runtime Type Information</strong></em>, ou seja, <em><strong>Informações de Tipo em Tempo de Execução</strong></em>, mas o que isso tem a ver com a serialização? Na verdade tem tudo a ver! No Delphi os objetos que contém membros na seção published, automaticamente geram RTTI para tais membros e o mecanismo usado para serializar um objeto só é viável por conta do RTTI. Membros que possuem RTTI exportam em tempo de execução o seu tipo de dado além do dado propriamente dito e assim é possível, por meio de métodos específicos, perguntar a um objeto qual o tipo de uma de suas propriedades e, baseando-se na resposta, é possível formatar uma saída serializada que posteriormente pode ser convertida de volta no objeto sem qualquer esforço.</p>\r\n<p style=\"text-align: justify;\">Na forma como vou apresentar aqui neste artigo, toda essa conversão e obtenção de tipos de dados, bem como a geração de saída serializada, será feita pelo próprio Delphi! Nenhuma linha de código adicional será necessária porque o Delphi já serializa constantemente um de seus mais importantes objetos, os formulários.</p>\r\n<p style=\"text-align: justify;\">Se você não acredita em mim, abra agora um projeto vazio no Delphi e no TForm que vai aparecer, coloque um TButton, um TLabel e um TEdit. Altere algumas das propriedades do TForm e destes controles. Veja abaixo o TForm que eu criei como exemplo:</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000097/Form.png\" width=\"223\" height=\"87\" /></p>\r\n<p style=\"text-align: justify;\">Agora clique com o botão direito do mouse em uma área vazia do TForm e clique em <em>View As Text</em>. A seguir está o que aparece ao clicar em View As Text:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>object FORM1: TFORM1\r\n Left = 0\r\n Top = 0\r\n BorderIcons = [biSystemMenu]\r\n Caption = \'Salvar nome\'\r\n ClientHeight = 57\r\n ClientWidth = 215\r\n Color = clBtnFace\r\n Font.Charset = DEFAULT_CHARSET\r\n Font.Color = clWindowText\r\n Font.Height = -11\r\n Font.Name = \'Tahoma\'\r\n Font.Style = []\r\n Icon.Data = {\r\n 0000010001001010100001000400280100001600000028000000100000002000\r\n 0000010004000000000000000000000000000000000010000000000000000101\r\n 01000C88160037C9630067EB8F00299D310045DB75002586280059E082002692\r\n 2D008AF5A6003FD46E0012911B0078F19A005AE8870024AA31004DDF7B000000\r\n 0000000000000000081111600000000006FA226000000000087A226000000000\r\n 0875A260000008888B75A21666600ED555F55A2222100ECDDDDFF55A22100EC3\r\n 3DDFFF55A2100E9999CDDF737FB004444B9DD5B8666000000493DA8000000000\r\n 0493358000000000049CC7800000000004EEEE80000000000000000000000000\r\n 0000000000000000000000000000000000000000000000000000000000000000\r\n 000000000000000000000000000000000000000000000000000000000000}\r\n OldCreateOrder = False\r\n PixelsPerInch = 96\r\n TextHeight = 13\r\n object LABE1: TLabel\r\n Left = 8\r\n Top = 8\r\n Width = 27\r\n Height = 13\r\n Caption = \'Nome\'\r\n end\r\n object BUTN1: TButton\r\n Left = 132\r\n Top = 27\r\n Width = 75\r\n Height = 21\r\n Caption = \'Salvar\'\r\n TabOrder = 0\r\n OnClick = BUTN1Click\r\n end\r\n object EDIT1: TEdit\r\n Left = 8\r\n Top = 27\r\n Width = 121\r\n Height = 21\r\n TabOrder = 1\r\n Text = \'Carlos\'\r\n end\r\nend</code></pre>\r\n<p style=\"text-align: justify;\">O que vai ser exibido para você pode ser ligeiramente diferente. Mas o que é isso? Isso é o TForm serializado em forma de texto legível e um mecanismo de stream carrega esta definição de uma vez só, toda vez que um TForm é criado, e seta todas as propriedades dele e de todos os controles que são de sua propriedade (tem TForm como Owner). Note o quão variados são os tipos das informações salvas. Neste exemplo básico você vê, números inteiros, strings, a atribuição de um manipulador de evento (na linha 42), um conjunto com um valor (na linha 4), um conjunto vazio (na linha 13), constantes (nas linhas 9 e 10) e por fim, mas não menos importante, um valor binário completo, que representa o ícone do TForm (na linha 14). Sofisticado, não é?</p>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">Como seria legal se eu pudesse salvar meus arquivos de configuração nesse formato e com esta facilidade de carregamento instantâneo não é? Yes! You Can! Continue lendo ;)</p>\r\n<h2>Como salvar objetos completos como arquivos</h2>\r\n<p style=\"text-align: justify;\">Quando se usa arquivos INI ou mesmo o registro do Windows, precisamos lidar com funções específicas para salvamento de dados. É necessário, normalmente, criar uma instância de um objeto manipulador, TIniFile por exemplo, e informar o nome do arquivo, suas seções, suas variáveis e seus respectivos valores. Tudo sendo salvo como String e muitas vezes ocupando mais espaço do que o necessário.</p>\r\n<p style=\"text-align: justify;\">Ao carregar estes dados precisaríamos fazer todo o procedimento oposto e, dependendo do caso, ainda precisaríamos fazer conversões de dados (String para TDateTime por exemplo). Outra enorme desvantagem dos arquivos INI é que no mesmo não é possível salvar textos mais complexos com quebras de linha, muito menos dados binários. A serialização de um objeto e sua posterior persistência em um arquivo resolve todos os problemas de falta de suporte em arquivos INI e registro do Windows e habilita o programador a salvar QUALQUER TIPO DE INFORMAÇÃO num arquivo.</p>\r\n<p style=\"text-align: justify;\">String, Integer, Currency, Double, Float, TDateTime, Extended, conjuntos de dados constantes, constantes, além de coleções de tipos customizados e até mesmo dados binários crus (imagens, ícones, executáveis, dlls). Praticamente qualquer coisa pode ser salva no arquivo. O que você acharia de salvar e carregar um arquivo dessa forma?:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>procedure SalvarConfiguracoes;\r\nbegin\r\n Configuracoes.SaveText;\r\nend;\r\n\r\nprocedure CarregarConfiguracoes;\r\nvar\r\n FileName: TFileName;\r\nbegin\r\n FileName := ChangeFileExt(ParamStr(0),\'.config\');\r\n\r\n Configuracoes.LoadFromTextFile(FileName);\r\n\r\n if not FileExists(FileName) then\r\n Configuracoes.SaveText;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Os dois procedures acima já salvam e carregam <span style=\"text-decoration: underline;\"><strong>todas as configurações contidas no objeto</strong></span> \"Configurações\" <span style=\"text-decoration: underline;\"><strong>de uma só vez</strong></span>! Eu não sei você caro leitor, mas quando eu vi isso pela primeira vez eu achei genial :). Chega de falar, vamos ao que interessa. Primeiramente a unit \"mágica\" que torna tudo isso possível:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>unit UObjectFile;\r\n\r\ninterface\r\n\r\nuses\r\n Classes, SysUtils;\r\n\r\ntype\r\n TObjectFile = class(TComponent)\r\n private\r\n FFileName: String;\r\n public\r\n constructor Create(POwner: TComponent); override;\r\n destructor Destroy; override;\r\n procedure LoadFromTextFile(const aFileName: TFileName);\r\n procedure SaveToTextFile(const PFileName: TFileName);\r\n procedure SaveText;\r\n function ToString: String; override;\r\n procedure FromString(const aTextualRepresentation: String);\r\n end;\r\n\r\nimplementation\r\n\r\n{ TObjectFile }\r\n\r\nprocedure TObjectFile.SaveText;\r\nbegin\r\n if FFileName &lt;&gt; \'\' then\r\n SaveToTextFile(FFileName);\r\nend;\r\n\r\nprocedure TObjectFile.SaveToTextFile(const PFileName: TFileName);\r\nbegin\r\n with TStringList.Create do\r\n try\r\n Text := Self.ToString;\r\n SaveToFile(PFileName);\r\n finally\r\n Free;\r\n end;\r\nend;\r\n\r\nconstructor TObjectFile.Create(POwner: TComponent);\r\nbegin\r\n inherited;\r\n FFileName := \'\';\r\nend;\r\n\r\ndestructor TObjectFile.Destroy;\r\nbegin\r\n SaveText;\r\n\r\n inherited;\r\nend;\r\n\r\nprocedure TObjectFile.LoadFromTextFile(const aFileName: TFileName);\r\nbegin\r\n FFileName := aFileName;\r\n\r\n if FileExists(FFileName) then\r\n with TStringList.Create do\r\n try\r\n LoadFromFile(FFileName);\r\n FromString(Text);\r\n finally\r\n Free;\r\n end;\r\nend;\r\n\r\n{$HINTS OFF}\r\nprocedure TObjectFile.FromString(const aTextualRepresentation: String);\r\nvar\r\n BinStream: TMemoryStream;\r\n StrStream: TStringStream;\r\nbegin\r\n StrStream := TStringStream.Create(aTextualRepresentation);\r\n try\r\n BinStream := TMemoryStream.Create;\r\n try\r\n StrStream.Seek(0, sofrombeginning);\r\n ObjectTextToBinary(StrStream, BinStream);\r\n BinStream.Seek(0, sofrombeginning);\r\n Self := BinStream.ReadComponent(Self) as TObjectFile;\r\n finally\r\n BinStream.Free\r\n end;\r\n finally\r\n StrStream.Free;\r\n end;\r\nend;\r\n{$HINTS ON}\r\n\r\nfunction TObjectFile.ToString: String;\r\nvar\r\n BinStream: TMemoryStream;\r\n StrStream: TStringStream;\r\n S: string;\r\nbegin\r\n inherited;\r\n BinStream := TMemoryStream.Create;\r\n try\r\n StrStream := TStringStream.Create(S);\r\n try\r\n BinStream.WriteComponent(Self);\r\n BinStream.Seek(0, sofrombeginning);\r\n ObjectBinaryToText(BinStream, StrStream);\r\n StrStream.Seek(0, sofrombeginning);\r\n Result := StrStream.DataString;\r\n finally\r\n StrStream.Free;\r\n end;\r\n finally\r\n BinStream.Free\r\n end;\r\nend;\r\n\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Como se pode ver, esta unit não tem muito código. Ela contém apenas wrappers e facilitadores para as funções <strong>ObjectBinaryToText</strong> e <strong>ObjectTextToBinary</strong>. São estas as funções mágicas de fato, as quais manipulam os membros published do objeto fazendo uso do RTTI.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">Para usar esta unit e a classe <strong>TObjectFile</strong> é preciso primeiro criar uma classe com nossos membros que deverão ser serializados. Como estamos usando como exemplo um arquivo de configurações, nada mais justo do que criar uma classe <strong>TConfiguracoes</strong>. A unit completa está abaixo:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>unit UConfiguracoes;\r\n\r\ninterface\r\n\r\nuses\r\n UObjectFile;\r\n\r\ntype\r\n TValorEnum = (veItem0, veItem1, veItem2, veItem3);\r\n\r\n TValorSet = set of TValorEnum;\r\n\r\n TConfiguracoes = class (TObjectFile)\r\n private\r\n FValorCurrency: Currency;\r\n FValorInteger: Integer;\r\n FValorDouble: Double;\r\n FValorDateTime: TDateTime;\r\n FValorString: String;\r\n FValorBoolean: Boolean;\r\n FValorEnum: TValorEnum;\r\n FValorSet: TValorSet;\r\n published\r\n property ValorInteger: Integer read FValorInteger write FValorInteger default 0;\r\n property ValorCurrency: Currency read FValorCurrency write FValorCurrency;\r\n property ValorDouble: Double read FValorDouble write FValorDouble;\r\n property ValorDateTime: TDateTime read FValorDateTime write FValorDateTime;\r\n property ValorString: String read FValorString write FValorString;\r\n property ValorBoolean: Boolean read FValorBoolean write FValorBoolean default False;\r\n property ValorEnum: TValorEnum read FValorEnum write FValorEnum default veItem0;\r\n property ValorSet: TValorSet read FValorSet write FValorSet default [];\r\n end;\r\n\r\nimplementation\r\n\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Esta unit é ainda menor, como esperado, já que todo trabalho duro é feito por TObjectFile. Incluí na classe TConfiguracoes vários membros de tipos de dados comuns. Eu poderia ter colocado um membro binário, mas isso complicaria na hora de atribuir um valor a ele, portanto apenas acredite que seria perfeitamente possível incluir um membro do tipo TImage, por exemplo. Os dados binários da imagem seriam gravados sem qualquer problema. Outras coisas que poderiam existir nesta classe seriam subclasses, incluindo coleções. Se você já criou classes com subclasses, isto é, membros que são de tipos de outras classes, bastaria incluir tais membros e inicializá-los corretamente que os dados da subclasse apareceriam no objeto serializado.</p>\r\n<p style=\"text-align: justify;\">Note que algumas das propriedades possuem a cláusula default. Essa cláusula instrui as funções <strong>ObjectBinaryToText</strong> e <strong>ObjectTextToBinary</strong> que se o valor da propriedade for igual àquele valor default ele não será incluído no objeto final serializado. Por exemplo, ValorInteger tem um valor default = 0. Na hora de serializar o objeto, caso ValorInteger = 0, ele não vai figurar na serialização, porque não é necessário. Isso é bom para economizar espaço no arquivo que vai receber o objeto serializado, pois ele não conterá referências a membros cujos valores são iguais aos seus respectivos valores default. Apesar do valor default ser importante para economizar espaço, infelizmente apenas membros dos tipos integer, boolean, enum e set podem ter valores default, em suma, apenas ordinais e conjuntos podem ter valores padrão, mesmo assim eu considero boa prática usá-los.</p>\r\n<p style=\"text-align: justify;\">Explore o exemplo anexado a este artigo para um melhor entendimento</p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-08-12 05:17:38',24,'','2020-06-26 17:26:14',24,0,'0000-00-00 00:00:00','2016-08-12 05:17:38','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000097\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',19,84,'Delphi, Addicted 2 Delphi, DFM, Resource, RTTI','Não importa o quão sofisticada sua aplicação seja, mesmo que ela salve seus dados em bancos de dados ou mesmo na nuvem, frequentemente haverá a necessidade de salvar dados de forma local. Normalmente isso é necessário quando se precisa persistir configurações que só dizem respeito à instância do programa sendo executado no momento. Existem algumas formas de fazer isso, sendo as mais comuns, arquivos INI e registro do Windows. Neste artigo vou ensinar uma forma de salvamento de arquivos a partir de um objeto serializado, que, no meu humilde entendimento, é a melhor forma de se persistir informações localmente.',1,5788,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(98,258,'Quando o TClientDataSet e o TDataSetProvider são necessários?','devo-usar-tclientdataset-tdatasetprovider','<p style=\"text-align: justify;\">É surpreendente o número de programadores que insistem em seguir o caminho mais complexo por acharem que apenas assim é que se alcança o Nirvana Computacional. A verdade meu caro, é que a menos que você tenha muito tempo livre ou viva para fazer malabarismos com o teclado, digitando códigos e mais códigos sem necessidade aparente, você precisará muitas vezes desenvolver coisas muito rapidamente e não poderá divagar sobre aquilo que está desenvolvendo. O curioso é que, tal como os malabaristas de teclado, existem os malabaristas do arrasta-e-solta. Não é de hoje que tenho visto pessoas usando os componentes <strong>TClientDataSet</strong> e <strong>TDataSetProvider</strong> sem necessidade alguma, apenas porque acham que precisam usá-los a todo custo. <strong>Se você costuma usar o método Post seguido de um ApplyUpdates</strong>, ou se simplesmente você quer saber se está fazendo uso destes poderosos componentes à toa, continue lendo.</p>\r\n','\r\n<p style=\"text-align: justify;\">Pra começar este artigo vou logo dizendo que não, nem sempre estes dois componentes precisam ser usados e o ganho ao usá-los pode não ser compensador pois seu uso pode aumentar a complexidade do seu código indiretamente. Se você está com dúvidas a respeito disso, recomendo fortemente esta leitura.</p>\r\n<p style=\"text-align: justify;\">Antes de mais nada, entenda o que são estes dois componentes e evite gafes como programador, gafes estas que certamente vão introduzir complexidade desnecessária ao seu projeto:</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">O <strong>TClientDataSet</strong> implementa um DataSet independente de banco de dados, representando <strong>um conjunto de dados na memória</strong>. Um TClientDataSet <strong>pode ser usado como</strong>:</p>\r\n<ol style=\"list-style-type: upper-alpha;\">\r\n<li style=\"text-align: justify;\">Um conjunto de dados stand-alone totalmente funcional <strong>baseado em arquivo</strong> para aplicações de banco de dados de camada única (<strong>single-tier</strong>). Quando usado dessa maneira, o TClientDataSet <strong>representa os dados armazenados num arquivo dedicado no disco rígido</strong> do usuário<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Um buffer</strong> local (<strong>na memória</strong>) dos registros <strong>de um outro conjunto de dados</strong> (TQuery, TTable ou similares). Este <em>conjunto de dados de origem</em> pode residir no mesmo TForm ou TDataModule onde o TClientDataSet estiver. <strong>Esta forma de uso é obrigatória quando o DataSet de origem é unidirecional</strong> (os componentes DB Express são, por exemplo), sendo a única maneira de fornecer suporte de navegação e edição para seus dados. O conjunto de dados de origem também pode residir em um sistema separado quando o TClientDataSet é usado para implementar a parte do cliente de um banco de dados, numa aplicação de várias camadas (<strong>n-tier</strong>)<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Um armazenamento temporário</strong> (na memória) <strong>de dados em forma de conjunto de dados</strong> facilmente navegável e com recursos já prontos para inserção, exclusão, seleção e alteração de seus itens, <strong>tornando-se assim um meio prático para manipulação de qualquer tipo de informação</strong>, bem como exibição dessa informação em componentes conscientes de dados, o que permite a interação simplificada do usuário</li>\r\n</ol>\r\n</blockquote>\r\n<p> </p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">O <strong>TDataSetProvider</strong>, como o nome sugere, fornece (provê) dados a partir de um conjunto de dados e resolve atualizações (inserções, exclusões e atualizações propriamente ditas) para esse conjunto de dados, o qual poderá aplicá-las ao SGBD associado a ele. De forma mais detalhada, o TDataSetProvider, empacota os dados de um conjunto de dados e passa este pacote em um ou mais DataPackets transportáveis para o TClientDataSet ou <a title=\"Nunca trabalhei com o XML Broker, mas pelo que pude entender lendo a ajuda do Delphi, ele é como um TClientDataSet, a diferença é que ao invés de converter os dados de um DataPacket, fornecido por um TDataSetProvider, em um conjunto de dados compatível com o TClientDataSet, ele converte esse DataPacket em XML, permitido uma utilização mais ampla de um servidor de aplicação desenvolvido em Delphi. Por exemplo, imagino que usando XML Brokers, seja possível converter os dados providos por um servidor de aplicação em XMLs que podem ser exibidos como páginas simples por um Browser (via XSLT) ou interpretados por outras linguagens, como PHP. Se você já trabalhou com o XML Broker, fique à vontade para comentar abaixo seu uso correto, ou simplesmente confirmar o que eu falei aqui ;)\" href=\"#\" rel=\"bookmark\">XML Broker</a>. O TClientDataSet, por sua vez, reconstrói os dados contidos no DataPacket a fim de criar uma cópia local (na memória), para o acesso simplificado do usuário. Quando o usuário termina de realizar suas ações nos dados (inserir, excluir, alterar), o TClientDataSet reempacota quaisquer dados alterados e envia estas alterações de volta para o TDataSetProvider, o qual aplica as atualizações no conjunto de dados original que por sua vez as replica no SGBD, terminando assim o ciclo \"requisição-resposta\". De forma resumida, um TDataSetProvider <strong>deve ser usado para</strong>:</p>\r\n<ul style=\"list-style-type: square;\">\r\n<li style=\"text-align: justify;\"><strong>Fornecer dados a partir de um conjunto de dados</strong> para um TClientDataSet ou XML Broker e <strong>resolver atualizações (inserções, exclusões e atualizações propriamente ditas)</strong> desse TClientDataSet ou XML Broker de volta para o conjunto de dados e consequentemente ao seu SGBD subjacente. O TDataSetProvider pode ser uma parte da mesma aplicação que o TClientDataSet ou XML Broker, ou <strong>pode ser colocado no servidor de aplicação (middleware) de uma aplicação de várias camadas (n-tier)</strong>. Neste último, ele serve como um provedor de dados, ficando entre um SGBD remoto e um TClientDataSet local (no cliente).</li>\r\n</ul>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Eu espero que você tenha lido com cuidado (e entendido) as definições acima. Elas foram criadas a partir da definição dos componentes na ajuda do próprio Delphi e também a partir de observações minhas e de outras pessoas mais experientes, as quais usam este par de componentes como se deve.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: left;\">Interpretações errôneas sobre a função do TClientDataSet e do TDataSetProvider</h2>\r\n<p style=\"text-align: justify;\">De todas as coisas que eu já ouvi a respeito do uso desse par de componentes, a mais estúpida e absurda é a de que o simples fato de usar estes componentes já torna uma aplicação em uma aplicação n-tier. Sobre isso, eu lamento profundamente, mas se você acha isso eu creio que você não deve ter entendido bem o que é a divisão de uma aplicação em camadas e recomendo que você pare de ler este artigo e vá estudar sobre o assunto, tomando cuidado para não confundir camadas lógicas com camadas físicas. Neste artigo não está sendo aprofundado o assunto \"multicamadas\". Ele é apenas citado porque o TClientDataSet e o TDataSetProvider foram criados para permitir a criação de aplicações multicamadas com o Delphi (DataSnap), logo, é natural que alguns conceitos sejam citados.</p>\r\n<p style=\"text-align: justify;\">Outra coisa falada de forma recorrente a respeito do uso destes componentes fora da arquitetura de n camadas é que usar essa dobradinha economiza recursos de rede e aumenta a performance geral de um programa implementado desta maneira. É justamente por conta desta premissa que eu resolvi escrever este artigo, pois muita gente está usando de forma errada os componentes e achando que eles estão cumprindo seu papel de \"melhoradores de performance\", mas na verdade só estão introduzindo complexidade desnecessária nos programas.</p>\r\n<h2 style=\"text-align: justify;\">Como usar os componentes TClientDataSet e TDataSetProvider</h2>\r\n<p style=\"text-align: justify;\">A fim de tentar explicar os usos corretos desse par de componentes vou descrever abaixo as formas mais comuns de acesso a dados no Delphi:</p>\r\n<ol>\r\n<li style=\"text-align: justify;\">Seu programa acessa diretamente o SGBD utilizando componentes TQuery (ou similares), TUpdateSQL (ou similares) e TDatabase (ou similares). A figura abaixo mostra esta forma clássica de acesso a dados no Delphi, a qual é ensinada em praticamente todos os livros que tratam do assunto. Um iniciante certamente vai utilizar esta forma de acesso e a mesma pode ser usada em qualquer tipo de aplicação, pois é muito eficiente. Neste modelo o TQuery é automaticamente configurado para ser bidirecional (UniDirectional = False), o que permite sua ligação aos mais diversos controles conscientes de dados (DBAware) assim como permite a navegação para frente e para trás nos dados do TQuery, isto é, você pode usar <em>Prior</em> e <em>Next</em>. Aliás, vale salientar que você sempre só foi capaz de navegar no conjunto de dados porque a propriedade UniDirectional, por padrão, vem configurada como false.\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000098/ConexaoDireta.png\" width=\"211\" height=\"153\" /></p>\r\n</li>\r\n<li style=\"text-align: justify;\">Seu programa acessa diretamente o SGBD utilizando componentes TQuery (ou similares), TUpdateSQL (ou similares), TDataSetProvider, TClientDataSet e TDatabase (ou similares). A figura abaixo mostra esta forma de implementação. Nesta implementação <span style=\"background-color: #ffff00;\">o TQuery no qual o TClientDataSet está ligado deve ser configurado como unidirecional</span>, pois isso diminui o consumo de memória deste componente, que passa a utilizar internamente uma lista encadeada simples (onde cada registro conhece apenas o seu sucessor), ao invés de uma lista duplamente encadeada. Esta configuração pode diminuir até 50% do consumo de memória por registro no TQuery. Esta implementação também recomenda que a mecânica de funcionamento das telas seja baseada em múltiplas ações com apenas uma confirmação ao final. Em outras palavras, <span style=\"background-color: #ffff00;\">deve ser permitido ao usuário a realização várias ações (inserção, exclusão e alteração em qualquer ordem), as quais são mantidas em cache, para somente depois serem enviadas de uma só vez ao SGBD por meio de um ApplyUpdates</span>. Outra vantagem desse modelo de conexão é que ele permite que seja implementado um \"modelo de maleta\" (<a href=\"index.php/a2d-mei/articles-a2d-mei/110-modelo-de-maleta\" rel=\"alternate\">Briefcase Model</a>), onde um programa pode ficar totalmente desconectado de qualquer SGBD e apenas em momento oportuno a conexão seria feita e as alterações seriam efetivadas. <span style=\"background-color: #ffff00;\">Os componentes do DB Express (DBX) são unidirecionais, portanto esta forma de implementação é a única forma de trabalhar com eles e, neste caso, o <em>Post</em> pode ser seguido de <em>ApplyUpdates</em> caso você não queira implementar as ações em cache</span>.\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000098/ConexaoIndireta.png\" width=\"403\" height=\"153\" /></p>\r\n</li>\r\n<li style=\"text-align: justify;\">Seu programa acessa indiretamente o SGBD, neste caso, deve existir uma aplicação no meio do caminho entre seus programas-cliente e o SGBD. Isso caracteriza uma aplicação em n camadas e você deve estar usando DataSnap, neste caso você certamente sabe o que faz e por conseguinte deve estar usando o TClientDataSet e o TDataSetProvider de forma correta. A figura abaixo mostra o uso dos componentes TClientDataSet e TDataSetProvider em uma aplicação DataSnap usando SOAP como transporte de dados (existem outros meios de transporte disponíveis no Delphi). Note que tais componentes ficam fisicamente separados (máquinas distintas), ligados apenas pela internet ou intranet. O DataSnap obriga você a usar TDataSetProvider e o TClientDataSet, porque estes componentes são os dois lados da mesma moeda (conexão). Do lado do servidor (middleware) fica o TDataSetProvider e do lado do cliente (thin client) ficam todos os TClientDataSet, cada um deles conectados aos seus TDataSetProviders correspondentes no servidor. É interessante perceber que o TDataSetProvider no servidor (middleware) atua como um TDataSource numa aplicação comum (cliente/servidor). De fato, se o TDataSetProvider fosse substituído por um TDataSource, o middleware seria idêntico a uma aplicação cliente/servidor. Eu costumo dizer que, do ponto de vista do SGBD, o middleware é um simples programa cliente/servidor. A fim de diminuir o consumo de memória no servidor, é recomendável que todos os TQuery sejam configurados como unidirecionais. No cliente, cada TClientDataSet é bidirecional SEMPRE e por isso é possível usá-los conectados a controles conscientes de dados sem qualquer problema\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000098/ConexaoIndiretaDSSS.png\" width=\"721\" height=\"154\" /></p>\r\n</li>\r\n</ol>\r\n<p style=\"text-align: justify;\">O foco deste artigo está nas linhas destacadas no item 2 acima. Vamos finalmente falar a respeito do mal uso dos componentes TClientDataSet e TDataSetProvider.</p>\r\n<h2 style=\"text-align: left;\">Como não usar o TClientDatSet e o TDataSetProvider</h2>\r\n<p style=\"text-align: justify;\">A desculpa inicial para o uso destes componentes é a performance, porque olhando a figura do item 2 se nota que, claramente, a utilização deles não torna a implementação mais simples, muito pelo contrário. Do ponto de vista da performance, existem dois pontos principais, os quais foram destacados de amarelo no item 2. O primeiro deles seria a suposta diminuição do consumo de memória local e o segundo diz respeito à diminuição do tráfego de rede por meio do agrupamento de ações e aplicação das mesmas em lote.</p>\r\n<p style=\"text-align: justify;\">Se sua intenção é diminuir o consumo de memória então você não levou em conta algo muito simples: todo o ganho de memória obtido pelo uso de um cursor unidirecional no TQuery será compensado ou mesmo superado pela introdução do TDataSetProvider e do TClientDataSet, que é bidirecional tornando assim o consumo de memória SEMPRE MAIOR do que ao se usar a forma clássica de conexão (Item 1 acima). A recomendação é que o TQuery seja configurado como unidirecional não como uma vantagem opcional, mas sim como uma necessidade latente, já que a introdução do TClientDataSet duplicaria o consumo de memória e sendo assim, manter o TQuery como bidirecional nesta implementação é desperdício sério de memória e performance. Se nota que a configuração da propriedade UniDirectional no TQuery só faz mesmo sentido num middleware, pois lá não existe nada além de TQuery (ou similares). Considero, pois, que este mito do consumo de memória foi \"detonado\", como diriam os caras do Discovery Channel. Se sua única justificativa era essa, acho bom começar uma refatoração o mais rápido possível no seu código.</p>\r\n<p style=\"text-align: justify;\">Sobre a diminuição do tráfego de rede, preciso explicar que não existe componente mágico pra fazer isso sozinho. Apenas o conjunto \"implementação correta + componentes certos\" pode fazer o tráfego de rede diminuir e neste caso, sim, é possível diminuir drasticamente o consumo de banda da aplicação, mas <span style=\"text-decoration: underline;\">a utilização efetiva deste recurso requer que haja um botão adicional no seu TForm, que teria a função de enviar, por demanda, ao SGBD todas as alterações realizadas de uma só vez</span>, ou seja, a persistência das alterações no SGBD deve ser feita em duas etapas para que essa economia de dados seja efetiva.</p>\r\n<p style=\"text-align: justify;\">Se sua implementação usa recorrentemente <strong>Post + ApplyUpdates</strong>, e seus TDataSet não são unidirecionais por natureza (DBX), então eu lamento informar que você está subutilizando esta implementação e deveria repensar seriamente todo seu projeto. Em outras palavras, fazer Post + ApplyUpdates, transforma o conjunto TQuery + TDataSetProvider + TClientDataSet em uma redundância, pois o mesmo efeito pode ser conseguido com TQuery sozinho sem qualquer perda de performance. Se por acaso você estiver executando o ApplyUpdates TODA VEZ que uma operação for realizada, qual a vantagem de usar TDataSetProvider + TClientDataSet? Nenhuma! Pois seria o mesmo que realizar post ou delete diretamente no TQuery.</p>\r\n<h2>Conclusão</h2>\r\n<ol>\r\n<li style=\"text-align: justify;\">Usar TDataSetProvider + TClientDataSet em uma aplicação cliente/servidor é OBRIGATÓRIO apenas quando se usa componentes cujos TDataSet são unidirecionais, tal como ocorre com os componentes do <a title=\"Gostaria de agradecer aos amigos do grupo Delphi Experts que me alertaram sobre o fato de que os componentes do DB Express são unidirecionais e que apenas usando TDataSetProvider + TClientDataSet habilita seu uso com componentes DBAware\" href=\"#\" rel=\"bookmark\">DB Express</a>.<br /><br /></li>\r\n<li style=\"text-align: justify;\">Usar TDataSetProvider + TClientDataSet em uma aplicação cliente/servidor com o <strong>intuito de diminuir o consumo de memória local é um mito</strong>, e se você estiver usando a dobradinha apenas por conta disso, pare, pense e refaça seu projeto.<br /><br /></li>\r\n<li style=\"text-align: justify;\">Já do ponto de vista da economia de recursos de rede, usar TDataSetProvider + TClientDataSet em uma aplicação cliente/servidor só faz sentido se você pretende permitir ao usuário realizar várias operações e só no final confirmar todas ao mesmo tempo, ou seja, se você não está implementando em seu sistema uma forma de agrupar operações e fazer envio em lote das mesmas então o conjunto TDataSetProvider + TClientDataSet é desnecessário. Se você estiver fazendo Post + ApplyUpdates isso significa que você está subutilizando essa arquitetura e é desejável fazer tudo sem usar esses componentes, pois torna a codificação mais simples.</li>\r\n</ol>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-08-12 15:40:01',24,'','2020-06-26 17:27:03',24,0,'0000-00-00 00:00:00','2016-09-10 03:00:00','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000098\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',74,85,'Delphi, Addicted 2 Delphi, TClientDataSet, TDataSetProvider, Dados em Memória, Cache, ApplyUpdates, Briefcase Model','É surpreendente o número de programadores que insistem em seguir o caminho mais complexo por acharem que apenas assim é que se alcança o Nirvana Computacional. A verdade meu caro, é que a menos que você tenha muito tempo livre ou viva para fazer malabarismos com o teclado, digitando códigos e mais códigos sem necessidade aparente, você precisará muitas vezes desenvolver coisas muito rapidamente e não poderá divagar sobre aquilo que está desenvolvendo. O curioso é que, tal como os malabaristas de teclado, existem os malabaristas do arrasta-e-solta. Não é de hoje que tenho visto pessoas usando os componentes TClientDataSet e TDataSetProvider sem necessidade alguma, apenas porque acham que precisam usá-los a todo custo. Se você costuma usar o método Post seguido de um ApplyUpdates, ou se simplesmente você quer saber se está fazendo uso destes poderosos componentes à toa, continue lendo.',1,9329,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(100,274,'Quando devo registrar uma DLL?','quando-devo-registrar-uma-dll','<p style=\"text-align: justify;\">Existem duas coisas que me deixam profundamente irritado na informática: Pessoas que fecham janelas usando o menu popup (ou o caminho <strong>Arquivo &gt; Fechar</strong>) e pessoas que acham que toda DLL precisa ser registrada antes de ser usada. Este artigo é apenas para orientar os desavisados, porque pasme, já vi programadores experientes falando em registrar DLLs sem necessidade, seguindo e disseminando esse mito estúpido.</p>\r\n','\r\n<p style=\"text-align: justify;\">Para quem não sabe, <strong>DLL</strong> significa <em><strong>Dynamic Link Library</strong></em> ou <em><strong>Biblioteca de Vínculo Dinâmico</strong></em>. Como o nome sugere, uma DLL é ligada (ou vinculada) a algo dinamicamente. Esse \"algo\" seria um programa executável (ou outra DLL) e \"dinamicamente\" refere-se ao fato de que o programa executável utiliza código contido na DLL, que é um módulo separado, de forma dinâmica, sob demanda, pois tal código não foi compilado juntamente com o código do executável. Por falar em módulo separado, costuma-se chamar tanto programas executáveis quanto DLLs simplesmente de módulos porque tecnicamente, uma DLL também é um executável, mas suas características exclusivas fazem com que este \"executável\" não execute sem um \"programa host\", o qual tem a função de carregar a DLL no seu espaço de memória, tornando ambos (EXE e DLL) uma coisa só!</p>\r\n<p style=\"text-align: justify;\">Depois dessa encheção de linguiça inicial eu vou fazer aqui uma revelação. Sei que vai doer em muitos de vocês, mas a verdade tem que ser dita:</p>\r\n<h2>Não! Nem toda DLL precisa ser registrada, aliás, a grande maioria delas não precisa disso!</h2>\r\n<p style=\"text-align: justify;\">Quando você obtém uma DLL qualquer para usar, não há meios diretos de saber se esta DLL precisa ser registrada ou não, por isso, o primeiro passo é usar um pouco de lógica para supor seu uso. Primeiramente, ao obter uma DLL você o faz porque ela contém algo que você precisa, logo, você de algum modo sabe o que ela tem, logo, deveria saber se ela é uma DLL registrável ou não. Normalmente aquele que desenvolveu a DLL sabe exatamente o que ela é, portanto, se você obtiver uma DLL diretamente do fornecedor que a desenvolveu, este, com toda certeza, vai informar se esta DLL precisa ser registrada antes de ser usada de alguma forma. Normalmente, quando nada é especificado, a DLL <strong>NÃO PRECISA</strong> ser registrada e simplesmente exporta funções para uso direto.</p>\r\n<p style=\"text-align: justify;\">DLLs registráveis são todas aquelas que expõem/contém servidores COM, Objetos Active X, Objetos OLE e outras destas tecnologias exóticas que a Microsoft criou e que provavelmente você, que tenta registrar uma DLL qualquer sem saber se isso é necessário, provavelmente não vai usar. Em outras palavras, uma DLL só precisa ser registrada se você tiver certeza disso, do contrário ela não é registrável. Fica a dica.</p>\r\n<h2 style=\"text-align: justify;\">O DLL Export Viewer</h2>\r\n<p style=\"text-align: justify;\">Se mesmo assim você é destes que gosta de seguir receitas de bolo e não quer errar jamais, eu vou dar a dica definitiva pra você não se passar por <strong>noob retardado</strong> nos fóruns/grupos/listas dos quais participa, ao questionar sobre registro de DLLs. Baixe o programa <strong>DLL Export View</strong> (Freeware), que está disponível em <a href=\"http://www.nirsoft.net/utils/dll_export_viewer.html\">http://www.nirsoft.net/utils/dll_export_viewer.html</a>. Este programa simplesmente mostra todas as funções que uma DLL exporta. Como exemplo, eu usei a clássica <strong>capicom.dll</strong>, a qual <strong>é</strong> <strong>registrável</strong>. Veja abaixo a saída do programa:</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000100/DLLRegistravel.png\" width=\"626\" height=\"473\" /></p>\r\n<p style=\"text-align: justify;\">Note que existem duas funções especiais, <strong>DllRegisterServer</strong> e <strong>DllUnregisterServer</strong>. Estas funções são as responsáveis por \"interagir\" com o programa <strong>RegSvr32</strong>, o qual promoverá o registro (ou desregistro) da DLL, portanto, se sua DLL tem estas duas funções, é certeza que ela precisa ser registrada, a não ser que algum <strong>troll zoeiro</strong> tenha criado funções com estes nomes propositalmente para enganar a todos, mas estou descartando esta hipótese.</p>\r\n<p style=\"text-align: justify;\">Só como forma de aparar todas as arestas, para não deixar nenhuma dúvida, segue o screenshot da saída do DLL Export Viewer para uma DLL \"comum\":</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000100/DLLComum.png\" width=\"626\" height=\"473\" /></p>\r\n<p style=\"text-align: justify;\">Note que não existem as duas funções especiais de registro, logo, esta DLL<strong> não é registrável</strong>. Use-a de forma tradicional.</p>\r\n<p style=\"text-align: justify;\">O DLL Export Viewer é uma mão na roda para inspecionar as funções exportadas por uma DLL. Isso é útil para qualquer bisbilhoteiro escovador de bits interessado em expandir seus conhecimentos. Use o programa para inspecionar funções exportadas por algumas DLLs do Windows, como <strong>User32.dll</strong> ou <strong>Kernel32.dll</strong>. Garanto que você verá algumas funções de nomes familiares usadas no Delphi e isso não é coincidência. Muitas das funções usadas no Delphi são funções de API do Windows ou wrappers para funções de API. Por exemplo, Application.MessageBox é uma função wrapper para as funções MessageBoxA ou MessageBoxW, dependendo de qual tipo de String se usa, \"A\" para AnsiStrings ou \"W\" para WideStrings. Estas funções encontram-se em User32.dll, como se pode ver no screenshot abaixo:</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000100/User32.png\" width=\"626\" height=\"473\" /></p>\r\n<p style=\"text-align: justify;\">Note que existem outras versões desta mesma função e garanto que muitos de vocês leitores jamais as viram, portanto, mãos à obra e Google nelas! Falando em Google, não vou ensinar como usar o DLL Export Viewer, use sua inteligência e descubra (ou leia o manual em Inglês que vem junto do seu executável, dentro do zip).</p>\r\n<p style=\"text-align: justify;\">Eu espero, depois deste artigo, não mais ver dúvidas de pessoas nos locais onde eu ando (Fóruns, Grupos de Facebook, Listas de Discussão, Rua, etc.) a respeito de tal função não estar funcionando porque a DLL não foi registrada. Juro que vou responder jogando o link deste artigo. De nada!</p>',1,80,'2016-09-05 02:10:24',24,'','2016-11-18 13:23:20',24,0,'0000-00-00 00:00:00','2016-09-06 03:41:30','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000100\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{}',16,86,'Delphi, Addicted 2 Delphi, DLL, RegSvr32, Registrar, DLL Export Viewer, ActiveX, COM, OLE','Existem duas coisas que me deixam profundamente irritado na informática: Pessoas que fecham janelas usando o menu popup (ou o caminho Arquivo > Fechar) e pessoas que acham que toda DLL precisa ser registrada antes de ser usada. Este artigo é apenas para orientar os desavisados, porque pasme, já vi programadores experientes falando em registrar DLLs sem necessidade, seguindo e disseminando esse mito estúpido.',1,6289,'{}',1,'*','',''),(101,276,'XML Mapper: Convertendo um XML genérico em Data Packet','xml-mapper-convertendo-um-xml-generico-em-data-packet','<p style=\"text-align: justify;\">Então você recebe de um webservice um XML que representa um conjunto de dados e precisa exibir tais dados para o usuário de uma forma humanamente legível. Como você faria isso? Você pode usar <a href=\"index.php?option=com_content&amp;view=article&amp;id=132&amp;catid=80&amp;Itemid=493\" rel=\"alternate\">XPath</a> (ou XML Data Binding) para ler os nós (registros) em um laço e montar de forma livre uma visualização no Delphi, mas isso vai dar um bom trabalho. Prepare-se! E se eu te disser que existe uma forma de converter um XML genérico diretamente em algo que pode ser carregado por um TClientDataSet? Ficou curioso? Então este artigo é para você!</p>\r\n','\r\n<p style=\"text-align: justify;\">O XML Mapper é um utilitário capaz de gerar arquivos de <strong>Transformação XML</strong> (no caso do XML Mapper, arquivos de extensão .xtr). Um arquivo de transformação, como o próprio nome sugere, é um arquivo capaz de transformar, ou traduzir um XLM, gerando como resultado um outro arquivo XML com estrutura específica ou mesmo outro formato, como HTML ou texto plano. No caso do XML Mapper, o arquivo de transformação será responsável por converter um XML qualquer em um <strong>XML Data Packet</strong> que é compatível com o TClientDataSet. Em outras palavras, usando a transformação XML gerada pelo XML Mapper é possível transformar um XML genérico em um XML específico para uso direto no TClientDataSet!</p>\r\n<h2 style=\"text-align: center;\">Como gerar um arquivo de transformação XML</h2>\r\n<p style=\"text-align: justify;\">Utilizar o XML Mapper pode parecer difícil a primeira vista, entretanto é bem mais fácil do que você pensa. Na verdade o único requisito para uso sem maiores problemas é que você saiba interpretar exatamente o XML original. É como a tradução de um texto em outro idioma: não adianta você saber traduzir as palavras individualmente, você precisa entender de fato o que está escrito a fim de fazer uma tradução bem feita.</p>\r\n<p style=\"text-align: justify;\">Não pretendo neste artigo abordar todas as opções e possibilidades do XML Mapper. Cabe a você estudar o programa e descobrir todas as suas funcionalidades. O próprio XML Mapper possui um arquivo de ajuda facilmente acessível no seu menu Help. Não existe desculpa para não aprender.</p>\r\n<p style=\"text-align: justify;\">Para seguir o passo-a-passo de geração de um arquivo de Transformação XML, anexado a este artigo está um XML simples. Abaixo está uma amostra da estrutura geral do arquivo, com apenas um item:</p>\r\n<pre class=\"language-markup\"><code>&lt;Feriados&gt;\r\n &lt;item&gt;\r\n &lt;id&gt;638&lt;/id&gt;\r\n &lt;data&gt;2017-07-16T00:00:00-03:00&lt;/data&gt;\r\n &lt;descricao&gt;Dia da Padroeira Nossa Senhora do Carmo&lt;/descricao&gt;\r\n &lt;esfera&gt;M&lt;/esfera&gt;\r\n &lt;dataFixa&gt;S&lt;/dataFixa&gt;\r\n &lt;cidade&gt;\r\n &lt;id&gt;1&lt;/id&gt;\r\n &lt;nome&gt;Recife&lt;/nome&gt;\r\n &lt;uf&gt;PE&lt;/uf&gt;\r\n &lt;regiaoMetropolitana&gt;true&lt;/regiaoMetropolitana&gt;\r\n &lt;/cidade&gt;\r\n &lt;/item&gt;\r\n&lt;/Feriados&gt;</code></pre>\r\n<p>Como eu falei antes, é preciso entender o XML que se quer converter, por isso vou rapidamente explicar a estrutura do XML de exemplo: </p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Este é um XML com os feriados do ano de 2017. O nó raiz é o nó <strong>&lt;Feriados&gt;</strong>, o qual contém vários nós de nome <strong>&lt;item&gt;</strong>. Cada um destes nós representa um feriado específico, logo, cada nó seria uma linha de nosso DataSet resultante. Cada linha, portanto, possui algumas colunas. No caso estas colunas são obtidas a partir dos nós <strong>&lt;id&gt;</strong>, <strong>&lt;data&gt;</strong>, <strong>&lt;descricao&gt;</strong>, <strong>&lt;esfera&gt;</strong>, <strong>&lt;dataFixa&gt;</strong> e <strong>&lt;cidade&gt;</strong>. O nó <strong>&lt;cidade&gt;</strong>, por sua vez, possui os subnós <strong>&lt;id&gt;</strong>, <strong>&lt;nome&gt;</strong>, <strong>&lt;uf&gt;</strong> e <strong>&lt;regiaoMetropolitana&gt;</strong>. O nó &lt;cidade&gt; é considerado no DataSet final como sendo um <strong>SubDataSet</strong> que é facilmente manipulado por qualquer TClientDataSet e pode ser renderizado em um segundo TDBGrid como um <strong>DataSet de detalhe</strong>! Quero ressaltar que todo XML a ser transformado precisa de um nó raiz. A ausência desse nó impossibilita a utilização do XML Mapper.</p>\r\n</blockquote>\r\n<div> </div>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">Após o entendimento inicial da estrutura do XML de exemplo, vamos ao passo-a-passo para geração do arquivo de transformação XML:</p>\r\n<ol>\r\n<li style=\"text-align: justify;\">Abra o programa xmlmapper.exe, que está localizado na pasta bin do Delphi. A tela do XML Mapper é exibida a seguir:\r\n<p><img src=\"images/add2del/artigos/ID000101/Clipboard01.png\" alt=\"\" width=\"100%\" /></p>\r\n</li>\r\n<li style=\"text-align: justify;\">Acesse o menu <strong>File &gt; Open</strong> e selecione o XML a ser processado. O XML será carregado na área da esquerda da tela (<strong>Document: Feriados.xml</strong>):\r\n<p><img src=\"images/add2del/artigos/ID000101/Clipboard02.png\" alt=\"\" width=\"100%\" /></p>\r\nNote que a estrutura do XML é apresentada em forma de árvore.<br /><br /></li>\r\n<li style=\"text-align: justify;\">Na estrutura do XML identifique os nós que representam sub-conjuntos de dados e, caso existam, selecione-os um a um e clique na aba <strong>Node Properties</strong> na parte de baixo da tela. No nosso exemplo apenas o nó &lt;cidade&gt; representa um SubDataSet, portanto, selecione-o e clique na aba Node Properties. Veja a imagem para maiores detalhes:\r\n<p><img src=\"images/add2del/artigos/ID000101/Clipboard03.png\" alt=\"\" width=\"100%\" /></p>\r\nNa aba Node Properties para o nó  existe uma propriedade chamada <strong>Nested</strong> (aninhado), como este nó representa um SubDataSet, significa que seus dados (subnós) estão aninhados, logo, a propriedade Nested para o nó  precisa ser configurada como True. Note que até mesmo o ícone que representa este nó foi alterado para identificar visualmente que se trata de um nó que aninha subnós. Note que este ícone é igual ao ícone do nó  e, de fato, se você acessar as propriedades do nó , verá que ele tem a propriedade Nested = True!<br /><br /></li>\r\n<li style=\"text-align: justify;\">Aproveitando que a aba Node Properties está ativa, é hora de configurar os nós que você deseja mapear. A configuração básica consiste em informar o tipo de dado que cada nó precisa ter quando for convertido em TField de um TClientDataSet. Abaixo está o exemplo de seleção de tipo para o nó :\r\n<p><img src=\"images/add2del/artigos/ID000101/Clipboard04.png\" alt=\"\" width=\"100%\" /></p>\r\nEste nó contém uma data logo, sua propriedade <strong>DataType</strong> é configurada como Date. Note também que, diferentemente do nó \"aninhador\" , existem várias propriedades disponíveis. Nós de dados realmente possuem mais opções as quais eu não vou explicar aqui. Por ora apenas a propriedade DataType precisa ser alterada. Outras propriedades podem se manter como estiverem.<br /><br />Repita este passo para todos os nós que você deseja mapear, alterando sua propriedade Data Type para corresponder ao tipo de dado correto após a transformação.<br /><br /></li>\r\n<li style=\"text-align: justify;\">Clique novamente na aba Mapping a fim de exibir o mapeamento, o qual você vai definir agora. Você deve executar um duplo clique em cada nó que você deseja transformar em TField. Selecione todos os nós aplicáveis, incluindo os nós aninhados em &lt;cidade&gt;:\r\n<p><img src=\"images/add2del/artigos/ID000101/Clipboard05.png\" alt=\"\" width=\"100%\" /></p>\r\nNa parte de cima da tela estão nós que selecionamos e que serão mapeados para campos (TFields) no DataSet final. Claro que você poderia ignorar completamente a transformação de nós aninhados em &lt;cidade&gt;, mas para manter o artigo abrangente eu considero que neste exemplo você deseja transformar todos os nós disponíveis. As opções que foram destacadas (parte de baixo da tela) são aquelas que precisam estar selecionadas para que a transformação funcione como queremos.<br /><br /></li>\r\n<li style=\"text-align: justify;\">Clique com o botão direito do mouse em uma área vazia na parte da tela que mostra a estrutura do seu XML e nom menu popup selecione Create DataPacket from XML:\r\n<p align=\"center\"><img src=\"images/add2del/artigos/ID000101/Clipboard06.png\" alt=\"\" /></p>\r\nAo clicar nesta opção o mapeamento será criado e a tela do XML Mapper vai refletir esta criação:\r\n<p><img src=\"images/add2del/artigos/ID000101/Clipboard07.png\" width=\"100%\" /></p>\r\nNeste momento nenhum arquivo de transformação foi salvo. Ele ainda está na memória e para provar que a transformação está funcionando como deve, clique no botão <strong>Create and Test Transformation</strong>. A tela abaixo vai aparecer:\r\n<p style=\"text-align: center;\"><img src=\"images/add2del/artigos/ID000101/Clipboard08.png\" /></p>\r\nEsta tela mostra a visualização em um grid dos dados que estão no XML original, utilizando um arquivo de transformação XML na memória! Perceba que a coluna cidade é diferente; ela representa um SubDataSet. Clicando no botão de reticências vai exibir um outro grid com os registros deste SubDataSet. No nosso XML de exemplo, existem apenas 2 registros que contém dados no SubDataSet, eles se encontram no final do grid. Veja abaixo a exibição dos subregistros:\r\n<p style=\"text-align: center;\"><img src=\"images/add2del/artigos/ID000101/Clipboard09.png\" /></p>\r\nVocê pode inclusive navegar por entre os registros e perceber que o comportamento é de um relacionamento Mestre/Detalhe, ou seja, ao selecionar os registros <strong>638</strong> e <strong>639</strong> o grid que contém o SubDataSet vai mostrar os registros de detalhe!<br /><br /></li>\r\n<li style=\"text-align: justify;\">Salve o arquivo de transformação. Para fazer isso, clique com o botão direito do mouse numa área vazia da parte central da tela do XML Mapper e, no popup, clique no item <strong>Save Transformation</strong>:\r\n<p style=\"text-align: center;\"><img src=\"images/add2del/artigos/ID000101/Clipboard10.png\" /></p>\r\nEscolha um nome de arquivo e salve o arquivo .xtr em um local pertinente. Eu recomendo usar o padrão sugerido (<strong>ToDp.xtr</strong>), que significa <strong>To DataPacket</strong>, indicando que trata-se de um arquivo de transformação usado para converter <strong>para um DataPacket</strong>.<br /><br /></li>\r\n<li style=\"text-align: justify;\">Salve o XML de definição dos campos. Este XML é um DataPacket sem dados, apenas com a estrutura. Ele será necessário para a criação dos campos persistentes. Para salvar este DataPacket, clique com o botão direito do mouse numa área vazia da parte direita da tela do XML Mapper e, no popup, clique no item <strong>Save DataPacket</strong>:\r\n<p style=\"text-align: center;\"><img src=\"images/add2del/artigos/ID000101/Clipboard11.png\" /></p>\r\nEscolha um nome de arquivo e salve o arquivo .xml em um local pertinente. Eu recomendo salvar este arquivo com o mesmo nome do arquivo xml original, acrescido do sufixo DP (<strong>Feriados2017DP.xml</strong>)</li>\r\n</ol>\r\n<p>Após executar estes 8 passos, você concluiu a criação e o teste do arquivo de Transformação XML. Agora vamos aprender a usar este arquivo no Delphi :)</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: center;\">Como utilizar um arquivo de transformação XML</h2>\r\n<p style=\"text-align: justify;\">Como exemplo de utilização do arquivo de transformação vamos supor que precisemos exibir os dados contidos no arquivo XML em um programa Delphi. Como nosso conjunto de dados resultante contém um relacionamento mestre/detalhe, o exemplo será composto de dois TDBGrid e um botão que fará o carregamento.</p>\r\n<p style=\"text-align: justify;\">Para começar, crie uma aplicação VCL vazia, e inclua no TForm os dois TDBGrid, dois TClientDataSet, dois TDataSource e um TButton. A tela em tempo de desenvolvimento deve ficar assim:</p>\r\n<p style=\"text-align: center;\"><img src=\"images/add2del/artigos/ID000101/Clipboard12.png\" alt=\"\" /></p>\r\n<p style=\"text-align: justify;\">Renomeie os dois TDBGrid de forma que o de cima seja DBGRMestre e o de baixo seja DBGRDetalhe. Renomeie o botão como BUTNCarregar. Ligue os componentes normalmente: <strong>TDBGrid + TDataSource +</strong><strong> TClientDataSet</strong>. Clique com o botão direito do mouse em CLDSMestre, selecione a opção <strong>Load from MyBase table...</strong> e carregue o arquivo de definição (<strong>Feriados2017DP.xml</strong>). Nesse momento o TClientDataSet será aberto (Active = true), mas não conterá qualquer registro, porque o XML carregado contém apenas a definição dos campos.</p>\r\n<p style=\"text-align: justify;\">O próximo passo é executar um duplo clique neste mesmo TClientDataSet (CLDSMestre) para exibir o <strong>Fields Editor</strong> o qual estará vazio. Clique com o botão direito na área vazia do Fields Editor e selecione a opção Add all fields. Neste momento todos os campos persistentes serão criados, incluindo o campo especial cidade, do tipo TDataSetField:</p>\r\n<p style=\"text-align: center;\"><img src=\"images/add2del/artigos/ID000101/Clipboard13.png\" alt=\"\" /></p>\r\n<p style=\"text-align: justify;\">Agora selecione o segundo TClientDataSet (<strong>CLDSDetalhe</strong>), e configure sua propriedade <strong>DataSetField</strong> de forma que ele seja o campo cidade de CLDSMestre. Se você criou os campos automaticamente e não renomeou nenhum deles, a única opção disponível para escolha será <strong>CLDSMestrecidade</strong>, que é o campo persistente <strong>cidade</strong> de <strong>CLDSMestre</strong>.</p>\r\n<p style=\"text-align: justify;\">Ao realizar a configuração da propriedade DataSetField em CLDSDetalhe, ele será automaticamente aberto. Neste momento você já deve ter notado que, em tempo de desenvolvimento, os TDBGrid já mostram as colunas tanto do CLDSMestre, quanto do CLDSDetalhe:</p>\r\n<p style=\"text-align: center;\"><img src=\"images/add2del/artigos/ID000101/Clipboard14.png\" alt=\"\" /></p>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">Apesar de isso parecer bom, vamos fechar os TClientDataSet pois é uma boa prática manter todos os DataSets sempre fechados. Para fazer isso, basta fechar CLDSMestre, pois CLDSDetalhe faz parte de CLDSMestre. Ao fazer isso as colunas dos dois TDBGrids vão sumir e isso é natural, pois elas estavam sendo criadas dinamicamente após a abertura dos TClientDataSets. Para manter as colunas visíveis mesmo após o fechamento dos TClientDataSets, execute um duplo clique em cada TDBGrid a fim de exibir o <strong>Columns editor</strong> e nele, clique no botão Add All Fields. Veja a figura abaixo, onde o botão Add All Fields encontra-se destacado e as colunas estão criadas:</p>\r\n<p style=\"text-align: center;\"><img src=\"images/add2del/artigos/ID000101/Clipboard15.png\" alt=\"\" /></p>\r\n<p style=\"text-align: justify;\">Agora é hora de criar o código que faz a mágica acontecer. Coloque os arquivos xml e xtr na mesma pasta onde está sendo gerado seu executável. O arquivo XML é o arquivo original o qual você quer carregar (feriados2017.xml) e o arquivo XTR é o arquivo de transformação XML que você criou na seção anterior.</p>\r\n<p style=\"text-align: justify;\">Todo o código será posto no evento OnClick do botão BTNCarregar. Segue o código completo:</p>\r\n<pre id=\"transform\" class=\"line-numbers language-pascal\"><code>procedure TForm1.BUTNCarregarClick(Sender: TObject);\r\nvar\r\n XMLTransform: TXMLTransform;\r\nbegin\r\n XMLTransform := TXMLTransform.Create(Self);\r\n try\r\n XMLTransform.SourceXmlFile := ExtractFilePath(ParamStr(0)) + \'Feriados2017.xml\';\r\n XMLTransform.TransformationFile := ExtractFilePath(ParamStr(0)) + \'ToDp.xtr\';\r\n CLDSMestre.XMLData := XMLTransform.Data;\r\n finally\r\n XMLTransform.Free;\r\n end;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Este código que já é minúsculo ficaria ainda menor se eu tivesse usado o componente TXMLTransform de forma visual. Eu decidi criá-lo dinamicamente apenas para que vocês entendam melhor o que ele faz. Na <a href=\"#transform.5\" rel=\"alternate\">linha 5</a> instanciamos o componente de transformação. Na <a href=\"#transform.7\" rel=\"alternate\">linha 7</a> informamos ao componente qual o XML que será transformado. Na <a href=\"#transform.8\" rel=\"alternate\">linha 8</a> informamos ao componente qual é o arquivo de transformação XML. Na <a href=\"#transform.9\" rel=\"alternate\">linha 9</a> nós atribuímos o valor da propriedade Data do componente de transformação à propriedade XMLData do Client DataSet mestre. Esta linha faz duas coisas ao mesmo tempo. Primeiramente, ao ler o valor da propriedade Data, automaticamente é feita a transformação do XML original e o valor da propriedade Data é um XML DataPacket correspondente. Em segundo lugar, ao atribuir esse valor à propriedade XMLData do Client DataSet o mesmo é automaticamente aberto (Active = true). A <a href=\"#transform.11\" rel=\"alternate\">linha 11</a>, finalmente, destrói o componente de transformação, que não é mais necessário.</p>\r\n<h2 style=\"text-align: center;\">Onde está o componente TXMLTransform?</h2>\r\n<p style=\"text-align: justify;\">Como eu disse anteriormente eu decidi não usar o componente TXMLTransform, mas mesmo assim eu fui conferir suas propriedades e não o encontrei na paleta de componentes. Eu tinha certeza que ele existia e por isso fiz uma busca e descobri que o pacote que faz este e outros componentes para manipulação de XML aparecerem não estava registrado na IDE. Estou usando Delphi XE5 e não sei porque ele estava assim, mas a solução foi muito simples. Acessei <strong>Components &gt; Install Packages</strong> na IDE, cliquei o botão <strong>Add...</strong> e selecionei o BPL <strong>dclwbm190.bpl</strong> que registrou o pacote <strong>Embarcadero InternetExpress Components</strong>, o qual contém o componente :)</p>\r\n<p style=\"text-align: justify;\">Se seu Delphi não for o XE5 e você não estiver achando este componente, procure pelo BPL adequado trocando o número pelo correspondente para seu Delphi. Se você não sabe qual é o número que corresponde ao seu Delphi, eu recomendo o artigo <a href=\"index.php?option=com_content&amp;view=article&amp;id=127&amp;catid=80&amp;Itemid=493\">Diretivas de compilação e versões do Delphi</a>. Nele, há uma tabela em cuja coluna <strong>Library Suffix / Package Version</strong> estão os números das versões dos pacotes. Use-a para identificar a versão correta para seu Delphi e assim buscar o BPL correto.</p>\r\n<h2 style=\"text-align: center;\">O caminho oposto...</h2>\r\n<p style=\"text-align: justify;\">Se você analisou bem o XML Mapper enquanto o usava, deve ter se perguntado ou desconfiado se não seria possível transformar os dados de um DataPacket em um XML qualquer e a resposta, claro, é sim! Isso não será abordado aqui, mas fique sabendo que com um pouco de dedicação é perfeitamente possível fazer algo que, por exemplo, receba o resultado de um WebService, liste-o em um TDBGrid e, posteriormente envie esse resultado de volta, modificado, ao WebService. É claro que isso dá um pouco de trabalho, mas é perfeitamente viável!</p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-09-06 21:01:15',24,'','2020-07-10 20:57:30',24,0,'0000-00-00 00:00:00','2017-07-29 21:06:50','0000-00-00 00:00:00','{\"image_intro\":\"\",\"float_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000101\\/FullArticle.png\",\"float_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"article_layout\":\"\",\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_associations\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_page_title\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',49,87,'Delphi, Addicted 2 Delphi, DataPacket, XML, XML Mapper, TClientDataSet, dclwbm190.bpl, dclwbm, Embarcadero InternetExpress Components','Então você recebe de um webservice um XML que representa um conjunto de dados e precisa exibir tais dados para o usuário de uma forma humanamente legível. Como você faria isso? Você pode usar XPath (ou XML Data Binding) para ler os nós (registros) em um laço e montar de forma livre uma visualização no Delphi, mas isso vai dar um bom trabalho. Prepare-se! E se eu te disser que existe uma forma de converter um XML genérico diretamente em algo que pode ser carregado por um TClientDataSet? Ficou curioso? Então este artigo é para você!',1,8911,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(102,284,'Como usar corretamente uma barra de progresso (TProgressBar)?','como-usar-corretamente-uma-barra-de-progresso','<p style=\"text-align: justify;\">Chutando por baixo, acredito que 90% dos programadores usam o componente TProgressBar de forma errada. At&#233; mesmo eu o usei de forma errada porque a forma correta &#233; trabalhosa. Mesmo assim eu acredito que vale muito a pena conhecer o modo correto de se trabalhar com esse componente e caso seu programa fa&#231;a uso constante dele, porque n&#227;o criar uma classe que facilita a implementa&#231;&#227;o correta? &#201; isso que pretendo mostrar neste artigo.</p>\r\n','\r\n<p style=\"text-align: justify;\">Quando comecei a programar, uma das coisas mais legais e que me chamavam sempre a aten&#231;&#227;o eram as barras de progresso. Eu n&#227;o sei porque, mas eu sempre olhava a barrinha enchendo e achava muito engenhoso. No in&#237;cio, eu queria porque queria, que meus projetos utilizassem aquilo e cheguei at&#233; mesmo a utilizar uma barra de progresso na inicializa&#231;&#227;o de meus programas, na tela de <em>splash</em>, para indicar o progresso do carregamento de cada um dos TForm que eram criados automaticamente. Aboli este uso quando descobri que n&#227;o era correto carregar todos os TForm na inicializa&#231;&#227;o e foi a&#237; que eu entendi que uma barra de progresso s&#243; faz sentido quando ela &#233; realmente necess&#225;ria, ou seja, n&#227;o adianta for&#231;ar seu uso apenas para tornar seu programa mais legal de alguma forma.</p>\r\n<p style=\"text-align: justify;\">Use uma barra de progresso APENAS quando seu programa precisar indicar ao usu&#225;rio o andamento de uma tarefa realmente demorada <span style=\"text-decoration: underline;\">da qual voc&#234; conhe&#231;a o evento no qual o processamento termina</span>. Este \"evento\" pode ser qualquer coisa que seja representada por um n&#250;mero.&#160;<strong>O processamento dos bytes de um arquivo</strong>&#160;<span style=\"text-decoration: underline;\">de tamanho conhecido</span>, <strong>o processamento de arquivos num diret&#243;rio</strong> <span style=\"text-decoration: underline;\">quando voc&#234; previamente sabe a quantidade de arquivos</span>, <strong>a valida&#231;&#227;o de um arquivo XML com um arquivo XSD</strong>, <span style=\"text-decoration: underline;\">desde que voc&#234; obtenha previamente a quantidade total de n&#243;s dentro do XML</span>,&#160;etc., s&#227;o exemplos de processamento onde uma barra de progresso pode ser usada. Sua utiliza&#231;&#227;o &#233; v&#225;lida tamb&#233;m quando voc&#234; n&#227;o conhece o evento final, mas tem meios de consegu&#237;-lo de forma muito r&#225;pida, por exemplo, &#233; poss&#237;vel contar de forma relativamente r&#225;pida a quantidade de arquivos dentro de uma estrutura de diret&#243;rios, dada uma raiz inicial, recursivamente ou mesmo a quantidade total de n&#243;s dentro de um arquivo XML, usando XPATH. O exemplo anexado a este artigo, o qual vou explicar passo-a-passo, faz uso da contagem pr&#233;via de arquivos dentro de uma estrutura de diret&#243;rios recusivamente, e em um teste ele contou 300.000 arquivos em pouco menos de 10 segundos.</p>\r\n<p style=\"text-align: justify;\">A regra b&#225;sica &#233;: se voc&#234; <strong>n&#227;o tiver</strong>&#160;condi&#231;&#245;es de saber o evento no qual o processamento termina <strong>antes&#160;do processamento come&#231;ar</strong>&#160;ou <strong>se a obten&#231;&#227;o deste evento for muito complexa ou demorada</strong>, ent&#227;o uma barra de progresso n&#227;o se aplica e voc&#234; deve informar ao usu&#225;rio que algo est&#225; acontecendo, mas que n&#227;o tem meios de dizer quando vai acabar. Normalmente nesse caso, se usa uma anima&#231;&#227;o qualquer para indicar que algo est&#225; sendo feito em segundo plano.</p>\r\n<p style=\"text-align: justify;\">A seguir vou come&#231;ar a explicar como converter uma rotina que realiza um processamento lento para que ela possa ser usada com uma barra de progresso da forma correta. Quero salientar que todos os procedimentos que eu vou explicar foram criados por mim para diminuir a complexidade dessa convers&#227;o e que existem outros meios de se fazer a mesma coisa.&#160;Por exemplo, aqui eu recomendo que as rotinas demoradas sejam agrupadas em um m&#233;todo simples que faz todo o processamento, mas isso n&#227;o &#233; requerido, apenas escolhi esse meio porque <strong>EU</strong> achei mais f&#225;cil. O importante &#233; entender o que est&#225; sendo feito e seguir minha recomenda&#231;&#227;o ou criar seu pr&#243;prio modo de fazer a mesma coisa. Fica a seu crit&#233;rio, caro leitor.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">Uma thread, claro!</h2>\r\n<p style=\"text-align: justify;\">N&#227;o existe m&#225;gica alguma, quando eu falei que era mais complexo usar uma barra de progresso da forma correta, era disso que eu estava falando: <strong>processos demorados devem ser postos em uma thread</strong>. N&#227;o s&#227;o todos que tem paci&#234;ncia ou mesmo conhecimento para&#160;criar threads da forma correta, quanto mais uma apenas para fazer funcionar uma simples barra de progresso. Muitos de n&#243;s, eu me inclu&#237;a neste meio, preferimos usar <em>Application.ProcessMessages</em> e obter o mesmo efeito, contudo, esta sensa&#231;&#227;o de que est&#225; tudo bem, &#233; falsa! O uso indiscriminado de <em>ProcessMessages</em> pode causar problemas&#160;dif&#237;ceis de debugar, access violations inesperados e outros tipos de comportamentos bizarros em programas mais complexos. J&#225; tive problemas em um programa que fazia uso de FTP ass&#237;ncrono e s&#243; depois de muito penar, descobri que ele era causado pelos <em>ProcessMessages</em>. Resolvi todos os problemas passando a usar threads. No artigo <a href=\"index.php?option=com_content&amp;view=article&amp;id=103&amp;catid=80&amp;Itemid=493\">O lado negro do Application.ProcessMessages</a>, s&#227;o apresentados alguns motivos para evitar seu uso indiscriminado.</p>\r\n<p style=\"text-align: justify;\">Eu falei no&#160;par&#225;grafo anterior que eu me incu&#237;a no rol de pessoas que usam ProcessMessages, mas ao escrever este artigo eu bolei uma forma mais simples de implementar de forma correta o uso de&#160;barras de progresso e por isso, vou passar a usar sempre. Eu s&#243; tenho a ganhar e voc&#234; tamb&#233;m, caro leitor, caso entenda como tudo foi feito e tenha um pouco menos de pregui&#231;a ;) Abaixo eu lhes apresento uma unit com uma classe que configura uma thread b&#225;sica e introduz alguns eventos, propriedades e m&#233;todos que v&#227;o ser de grande ajuda para fazer tudo funcionar como um rel&#243;gio:</p>\r\n<pre id=\"UProgressThread\" class=\"line-numbers language-pascal\"><code>unit UProgressThread;\r\n\r\ninterface\r\n\r\nuses\r\n Classes;\r\n\r\ntype\r\n TOnProgress = procedure (const PText: String; const PNumber: Cardinal) of object;\r\n TOnMax = procedure (const PMax: Int64) of object;\r\n\r\n TProgressThread = class (TThread)\r\n private\r\n FText: String;\r\n FNumber: Cardinal;\r\n FOnProgress: TOnProgress;\r\n FMax: Int64;\r\n FOnMax: TOnMax;\r\n procedure CallOnProgress;\r\n procedure CallOnMax;\r\n protected\r\n procedure DoProgress;\r\n procedure DoMax;\r\n property Text: String read FText write FText;\r\n property Number: Cardinal read FNumber write FNumber;\r\n property Max: Int64 read FMax write FMax;\r\n public\r\n constructor Create; reintroduce;\r\n property OnProgress: TOnProgress read FOnProgress write FOnProgress;\r\n property OnMax: TOnMax read FOnMax write FOnMax;\r\n end;\r\n\r\nimplementation\r\n\r\n{ TProgressBarThread }\r\n\r\nprocedure TProgressThread.CallOnMax;\r\nbegin\r\n if Assigned(FOnMax) then\r\n FOnMax(FMax);\r\nend;\r\n\r\nprocedure TProgressThread.CallOnProgress;\r\nbegin\r\n if Assigned(FOnProgress) then\r\n FOnProgress(FText,FNumber);\r\nend;\r\n\r\nprocedure TProgressThread.DoMax;\r\nbegin\r\n if Assigned(FOnMax) then\r\n Synchronize(CallOnMax);\r\nend;\r\n\r\nprocedure TProgressThread.DoProgress;\r\nbegin\r\n if Assigned(FOnProgress) then\r\n Synchronize(CallOnProgress);\r\nend;\r\n\r\nconstructor TProgressThread.Create;\r\nbegin\r\n inherited Create(True);\r\n FreeOnTerminate := True;\r\nend;\r\n\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">A classe <strong>TProgressThread </strong>&#233; simplesmente uma descendente de <strong>TThread</strong>&#160;(linha <a href=\"#UProgressThread.12\" rel=\"alternate\">12</a>) que cria uma thread em estado suspenso (linha <a href=\"#UProgressThread.63\" rel=\"alternate\">63</a>) por padr&#227;o e instrui a mesma a ser liberada da mem&#243;ria automaticamente (linha <a href=\"#UProgressThread.64\" rel=\"alternate\">64</a>). Dessa forma, basta fazer <strong>TProgressThread.Create</strong> e automaticamente &#160;a thread &#233; criada em modo suspenso e ser&#225; liberada da mem&#243;ria t&#227;o logo ela termine. Esta classe exp&#245;e dois eventos essenciais nesta implementa&#231;&#227;o: <strong>OnProgress</strong> e <strong>OnMax</strong> (linhas <a href=\"#UProgressThread.29\" rel=\"alternate\">29</a> e <a href=\"#UProgressThread.30\" rel=\"alternate\">30</a>). O primeiro &#233; executado a cada itera&#231;&#227;o do processo demorado e o segundo &#233; executado quando h&#225; a necessidade de contagem pr&#233;via da quantidade total de itera&#231;&#245;es, permitindo a configura&#231;&#227;o da barra de progresso antes do in&#237;cio do processamento. As propriedades <strong>Text</strong>, <strong>Number</strong> e <strong>Max</strong> (linhas <a href=\"#UProgressThread.24\" rel=\"alternate\">24</a>, <a href=\"#UProgressThread.25\" rel=\"alternate\">25 </a>e <a href=\"#UProgressThread.26\" rel=\"alternate\">26</a>) s&#227;o retornadas&#160;pelos eventos OnProgress e OnMax. Os m&#233;todos <strong>DoProgress</strong> e <strong>DoMax</strong> (linhas <a href=\"#UProgressThread.22\" rel=\"alternate\">22 </a>e <a href=\"#UProgressThread.23\" rel=\"alternate\">23</a>) s&#227;o m&#233;todos que, ao serem executados, executam os eventos correspondentes.</p>\r\n<p style=\"text-align: justify;\">Esta classe n&#227;o foi feita para ser usada diretamente, ela precisa ser estendida para cada rotina demorada que precisa habilitar barras de progresso. Eu sei que ainda est&#225; nebuloso at&#233; aqui, mas continue lendo, no final tudo ficar&#225; claro.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">Agrupar &#233; preciso</h2>\r\n<p style=\"text-align: justify;\">Se voc&#234; j&#225; tem um projeto que, em algum ponto, realiza algum processamento demorado o primeiro passo &#233; pegar este processamento e coloc&#225;-lo dentro de um m&#233;todo (function ou procedure) adaptado para que o mesmo, sozinho, realize todo o processamento. Foi exatamente isso que foi feito com uma rotina que obt&#233;m o tamanho de todos os arquivos dentro de uma estrutura de diret&#243;rios recursivamente. &#201; uma fun&#231;&#227;o simples, com um par&#226;metro para passar o diret&#243;rio inicial e que, ao final, retorna o valor dos tamanhos de todos os arquivos encontrados somados. Veja como ficou:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function DirectoryTreeSize(PInitialDir: String): Int64;\r\nvar\r\n SearchRecord: TSearchRec;\r\nbegin\r\n Result := 0;\r\n\r\n if FindFirst(PInitialDir + \'*.*\', faAnyFile, SearchRecord) = 0 then\r\n try\r\n repeat\r\n if ((SearchRecord.Attr and faDirectory) = faDirectory) then\r\n begin\r\n if (SearchRecord.Name &lt;&gt; \'.\') and (SearchRecord.Name &lt;&gt; \'..\') then\r\n Inc(Result,DirectoryTreeSize(PInitialDir + SearchRecord.Name + \'\\\' + ExtractFileName(PInitialDir)));\r\n end\r\n else\r\n Inc(Result,FileSize(PInitialDir + SearchRecord.Name));\r\n until FindNext(SearchRecord) &lt;&gt; 0;\r\n finally\r\n FindClose(SearchRecord)\r\n end;\r\nend;\r\n</code></pre>\r\n<p style=\"text-align: justify;\">Esta fun&#231;&#227;o &#233; bem demorada, porque ela usa um m&#233;todo redundante&#160;para obter o tamanho de um arquivo. Este&#160;m&#233;todo &#233; redundante porque o tamanho dos arquivos j&#225; poderia ser obtido instantaneamente lendo <strong>SearchRecord.FileSize</strong>. Utilizei uma fun&#231;&#227;o para leitura gen&#233;rica do tamanho de um arquivo apenas para desacelerar o processo de forma a fazer sentido o uso de uma barra de progresso. &#160;A fun&#231;&#227;o FileSize, para leitura gen&#233;rica do tamanho de um arquivo, &#233; definida como segue:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function FileSize(PFileName: TFileName): Cardinal;\r\nvar\r\n FileHandle: THandle;\r\nbegin\r\n FileHandle := CreateFile(PChar(PFileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);\r\n try\r\n Result := GetFileSize(FileHandle, nil);\r\n finally\r\n CloseHandle(FileHandle);\r\n end;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Como foi dito anteriormente, a fun&#231;&#227;o&#160;DirectoryTreeSize percorre recursivamente uma estrutura de diret&#243;rios e l&#234; o tamanho de cada arquivo encontrado. N&#227;o h&#225; como saber quantos arquivos existem dentro de um diret&#243;rio recursivamente, logo, a &#250;nica solu&#231;&#227;o &#233; varrer a mesma estrutura e simplesmente contar cada arquivo. Isso parece est&#250;pido, pois vamos acabar percorrendo a mesma estrutura de diret&#243;rios duas vezes, uma para obter a quantidade de arquivos e a outra exatamente igual, para process&#225;-los, mas a varredura para contar os arquivos &#233; extremamente r&#225;pida, pelo menos &#233; MUITO MAIS R&#193;PIDA que a obten&#231;&#227;o do tamanho de cada arquivo e sendo assim esse procedimento &#233; v&#225;lido para este exemplo. Abaixo est&#225; a fun&#231;&#227;o que retorna o total de arquivos a processar dentro da estrutura de diret&#243;rios:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>function TDirectoryTreeSize.DirectoryTreeFileCount(PInitialDir: String): Cardinal;\r\nvar\r\n SearchRecord: TSearchRec;\r\nbegin\r\n Result := 0;\r\n\r\n if FindFirst(PInitialDir + \'*.*\', faAnyFile, SearchRecord) = 0 then\r\n try\r\n repeat\r\n if ((SearchRecord.Attr and faDirectory) = faDirectory) then\r\n begin\r\n if (SearchRecord.Name &lt;&gt; \'.\') and (SearchRecord.Name &lt;&gt; \'..\') then\r\n Inc(Result,DirectoryTreeFileCount(PInitialDir + SearchRecord.Name + \'\\\' + ExtractFileName(PInitialDir)));\r\n end\r\n else\r\n Inc(Result);\r\n until FindNext(SearchRecord) &lt;&gt; 0;\r\n finally\r\n FindClose(SearchRecord)\r\n end;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Note que a fun&#231;&#227;o&#160;<strong>DirectoryTreeFileCount</strong> &#233; praticamente igual &#224; fun&#231;&#227;o <strong>DirectoryTreeSize</strong>. Isso era esperado neste caso, j&#225; que precisamos passar pelos arquivos que ser&#227;o processados, mas apenas precisamos cont&#225;-los.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">Estendendo a classe&#160;TProgressThread</h2>\r\n<p style=\"text-align: justify;\">Agora que temos o procedimento demorado isolado em uma fun&#231;&#227;o, e temos uma segunda fun&#231;&#227;o que nos dar&#225; de forma r&#225;pida a quantidade de elementos que ser&#227;o processados, podemos adapt&#225;-las dentro de uma classe filha da classe TProgressThread. O nome da classe e o nome da unit que a cont&#233;m eu convenciono como sendo o mesmo nome da fun&#231;&#227;o original, logo, a classe se chama <strong>TDirectoryTreeSize</strong> e sua unit <strong>UDirectoryTreeSize</strong>. A implementa&#231;&#227;o completa voc&#234; v&#234; abaixo:</p>\r\n<pre id=\"udirectorytreesize\" class=\"line-numbers language-pascal\"><code>unit UDirectoryTreeSize;\r\n\r\ninterface\r\n\r\nuses\r\n UProgressThread;\r\n\r\ntype\r\n TDirectoryTreeSize = class (TProgressThread)\r\n private\r\n FInitialDir: String;\r\n FResult: Int64;\r\n function DirectoryTreeSize(PInitialDir: String): Int64;\r\n function DirectoryTreeFileCount(PInitialDir: String): Cardinal;\r\n public\r\n procedure Execute; override;\r\n property InitialDir: String write FInitialDir;\r\n property Result: Int64 read FResult;\r\n end;\r\n\r\nimplementation\r\n\r\nuses\r\n Windows, SysUtils;\r\n\r\nfunction FileSize(PFileName: TFileName): Cardinal;\r\nvar\r\n FileHandle: THandle;\r\nbegin\r\n FileHandle := CreateFile(PChar(PFileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);\r\n try\r\n Result := GetFileSize(FileHandle, nil);\r\n finally\r\n CloseHandle(FileHandle);\r\n end;\r\nend;\r\n\r\n{ TDirectoryTreeSize }\r\n\r\nfunction TDirectoryTreeSize.DirectoryTreeFileCount(PInitialDir: String): Cardinal;\r\nvar\r\n SearchRecord: TSearchRec;\r\nbegin\r\n Result := 0;\r\n\r\n if FindFirst(PInitialDir + \'*.*\', faAnyFile, SearchRecord) = 0 then\r\n try\r\n repeat\r\n if ((SearchRecord.Attr and faDirectory) = faDirectory) then\r\n begin\r\n if (SearchRecord.Name &lt;&gt; \'.\') and (SearchRecord.Name &lt;&gt; \'..\') then\r\n Inc(Result,DirectoryTreeFileCount(PInitialDir + SearchRecord.Name + \'\\\' + ExtractFileName(PInitialDir)));\r\n end\r\n else\r\n Inc(Result);\r\n until FindNext(SearchRecord) &lt;&gt; 0;\r\n finally\r\n FindClose(SearchRecord)\r\n end;\r\nend;\r\n\r\nfunction TDirectoryTreeSize.DirectoryTreeSize(PInitialDir: String): Int64;\r\nvar\r\n SearchRecord: TSearchRec;\r\nbegin\r\n Result := 0;\r\n\r\n if FindFirst(PInitialDir + \'*.*\', faAnyFile, SearchRecord) = 0 then\r\n try\r\n repeat\r\n if ((SearchRecord.Attr and faDirectory) = faDirectory) then\r\n begin\r\n if (SearchRecord.Name &lt;&gt; \'.\') and (SearchRecord.Name &lt;&gt; \'..\') then\r\n Inc(Result,DirectoryTreeSize(PInitialDir + SearchRecord.Name + \'\\\' + ExtractFileName(PInitialDir)));\r\n end\r\n else\r\n begin\r\n Text := SearchRecord.Name;\r\n Number := FileSize(PInitialDir + Text);\r\n DoProgress;\r\n Inc(Result,Number);\r\n end;\r\n until FindNext(SearchRecord) &lt;&gt; 0;\r\n finally\r\n FindClose(SearchRecord)\r\n end;\r\nend;\r\n\r\nprocedure TDirectoryTreeSize.Execute;\r\nbegin\r\n inherited;\r\n Max := DirectoryTreeFileCount(FInitialDir);\r\n DoMax;\r\n FResult := DirectoryTreeSize(FInitialDir);\r\nend;\r\n\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Calma, calma, n&#227;o entre em p&#226;nico!&#160;Eu vou explicar tudo o que foi feito nesta unit da maneira mais simples e direta quanto for poss&#237;vel.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">Como TDirectoryTreeSize foi implementada?&#160;(E como criar a sua pr&#243;pria classe)&#160;</h2>\r\n<p style=\"text-align: justify;\">Anteriormente eu falei que seria necess&#225;rio agrupar em um m&#233;todo (function ou procedure) todo o processamento demorado, portanto estou considerando que voc&#234; j&#225; fez isso. Siga os passos a seguir e olhe com cuidado a classe TDirectoryTreeSize. Use os links nos n&#250;meros das linhas para pular para o ponto exato no c&#243;digo da classe acima.</p>\r\n<ol>\r\n<li style=\"text-align: justify;\"><strong>Declare na se&#231;&#227;o private o m&#233;todo que &#233; o respons&#225;vel por gerar o processamento demorado</strong>, doravante chamado simplesmente de <strong>MD</strong> (M&#233;todo Demorado). No nosso exemplo, este m&#233;todo &#233; o <strong>DirectoryTreeSize</strong>, o qual pode ser visto declarado na linha <a href=\"#udirectorytreesize.13\" rel=\"alternate\">13</a>. A linha <a href=\"#udirectorytreesize.14\" rel=\"alternate\">14</a> mostra a declara&#231;&#227;o de nosso m&#233;todo&#160;adicional (doravante chamado simplesmente de <strong>MA</strong>) que tem o objetivo de contar a quantidade de elementos que ser&#227;o processados (<strong>DirectoryTreeFileCount</strong>). Ainda na&#160;se&#231;&#227;o private declare quaisquer outros m&#233;todos adicionais (<strong>MAs</strong>) que ser&#227;o necess&#225;rios para que o MD possa funcionar. Use a classe como voc&#234; faria com qualquer classe comum, declarando todos os m&#233;todos como privados. N&#227;o use vari&#225;veis globais ou locais na unit UDirectoryTreeSize. Caso precise de vari&#225;veis, use campos da classe e todos eles tamb&#233;m privados e com propriedades p&#250;blicas correspondentes. Se voc&#234; j&#225; criou uma classe em sua vida, vai notar que nada disso &#233; novo ou especial;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Altere a defini&#231;&#227;o do seu MD, de forma a incluir nele o m&#233;todo DoProgress</strong>, o qual gera o evento <strong>OnProgress</strong>&#160;no qual o <strong>TProgressBar</strong> pode ser atualizado&#160;na aplica&#231;&#227;o. O local exato da coloca&#231;&#227;o do DoProgress depende da sua implementa&#231;&#227;o. Normalmente ele deve ser posto dentro de um&#160;loop que vai \"rodar\" na mesma quantidade de vezes que o n&#250;mero de itera&#231;&#245;es informado, conhecido ou, no caso do nosso exemplo, obtido pelo MA de contagem (linha <a href=\"#udirectorytreesize.92\" rel=\"alternate\">92</a>). As linha <a href=\"#udirectorytreesize.78-80\" rel=\"alternate\">78 a 80</a>, mostram como deve ser feita&#160;a implementa&#231;&#227;o do m&#233;todo DoProgress. O evento OnProgress tem dois par&#226;metros, um par&#226;metro <strong>PText</strong> do tipo String e outro par&#226;metro <strong>PNumber</strong> do tipo&#160;Cardinal (DWord). Toda vez que o evento OnProgress &#233; ativado sua aplica&#231;&#227;o pode ler os valores de PText e PNumber a fim de atualizar um TLabel com informa&#231;&#245;es relevantes, por exemplo.&#160;Por este motivo, antes de executar o DoProgress as propriedades <strong>Text</strong> e <strong>Number</strong> (declaradas em&#160;<strong>TProgressThread</strong>) foram preenchidas, com o nome do arquivo e seu tamanho, respectivamente. Assim, quando o evento OnProgress for ativado pelo DoProgress, os par&#226;metros PText e PNumber estar&#227;o preenchidos com informa&#231;&#245;es que podem ser exibidas na aplica&#231;&#227;o. <strong>Usar estes dois par&#226;metros &#233; absolutamente <span style=\"text-decoration: underline;\">opcional</span></strong>. Eles existem apenas como um b&#244;nus. O mais importante &#233; chamar DoProgress, porque ele gerar&#225; o evento que informa que o TProgressBar precisa ser atualizado, usando seu m&#233;todo StepIt, ou incrementando sua propriedade Position;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Para cada par&#226;metro do MD, crie um campo privado na classe, que seja do mesmo tipo do par&#226;metro</strong>. No nosso exemplo o MD possui o par&#226;metro <strong>PInitialDir</strong>, logo, criamos um campo <strong>FInitialDir</strong>&#160;(linha <a href=\"#udirectorytreesize.11\" rel=\"alternate\">11</a>). Caso houvesse mais par&#226;metros, cada um deles se transformaria em um campo na classe;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Para cada par&#226;metro dos MAs,&#160;crie campos privados que sejam dos mesmos tipos de cada par&#226;metro</strong>. No nosso exemplo o MA <strong>DirectoryTreeFileCount</strong> possui o mesmo par&#226;metro que o MD (<strong>PInitialDir</strong>), logo, a cria&#231;&#227;o de um campo privado para ele (<strong>FInitialDir</strong>) j&#225; foi coberta no passo 3;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Para cada campo criado nos passos 3&#160;e 4 crie uma&#160;propriedade correspondente</strong>. Na linha <a href=\"#udirectorytreesize.17\" rel=\"alternate\">17</a> foi criada&#160;a propriedade <strong>InitialDir</strong>. A&#160;classe precisa conhecer todas as vari&#225;veis que ser&#227;o necess&#225;rias para que seus m&#233;todos internos possam funcionar, portanto, se seus m&#233;todos internos precisarem de mais informa&#231;&#245;es, cada uma destas precisa ser informada na classe e isso deve ser feito por meio de propriedades. Note que a propriedade InitialDir possui uma restri&#231;&#227;o de acesso, ou seja, esta propriedade &#233; somente para escrita (<strong>write-only</strong>). Isso n&#227;o &#233; necess&#225;rio, mas como o par&#226;metro <strong>PInitialDir &#233; somente de entrada</strong>, eu resolvi criar&#160;a propriedade mantendo o mesmo padr&#227;o. <strong>Se houvesse um par&#226;metro out</strong>, a propriedade correspondente deveria ser read-write. Em suma, por motivos &#243;bvios, as propriedades que representam par&#226;metros devem ter sempre acesso para que possam ser \"escritas\", elas nunca devem ser read-only, mas podem ser write-only;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Caso seu MD seja uma fun&#231;&#227;o (retorna um valor) crie um campo privado de nome FResult</strong>, do mesmo tipo do retorno do MD; Na linha <a href=\"#udirectorytreesize.12\" rel=\"alternate\">12</a> podemos ver a declara&#231;&#227;o do campo privado. Note que ele tem o mesmo tipo do retorno do MD;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Caos voc&#234; tenha precisado executar o passo 6, crie agora uma propriedade Result correspondente</strong> (linha <a href=\"#udirectorytreesize.18\" rel=\"alternate\">18</a>). Esta propriedade precisa ser somente leitura para evitar utiliza&#231;&#227;o incorreta da classe, al&#233;m disso, faz todo sentido que esta propriedade seja somente leitura, j&#225; que ela representa o <span style=\"text-decoration: underline;\">retorno</span> do MD;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Declare o m&#233;todo Execute&#160;na se&#231;&#227;o public da classe</strong> (linha <a href=\"#udirectorytreesize.16\" rel=\"alternate\">16</a>). Este &#233; o m&#233;todo que deve conter a l&#243;gica da thread, em outras palavras &#233; nele onde o MD precisa ser executado. <strong>V&#225; para a implementa&#231;&#227;o do m&#233;todo Execute e realize os dois passos a seguir</strong>;<br /><br />\r\n<ol style=\"list-style-type: upper-roman;\">\r\n<li style=\"text-align: justify;\"><strong>Caso voc&#234; tenha precisado implementar um MA para calcular a quantidade de itera&#231;&#245;es do seu MD</strong>, chame primeiramente o MA e retorne na <strong>propriedade Max</strong> a quantidade de itera&#231;&#245;es calculadas, em seguida, execute o <strong>m&#233;todo DoMax</strong>. Ao executar o m&#233;todo DoMax, um <strong>evento OnMax ser&#225; ativado</strong> e sua propriedade <strong>PMax</strong> conter&#225; aquilo que foi retornado na propriedade Max. Na sua aplica&#231;&#227;o, no manipulador do evento OnMax, <strong>voc&#234; configura a propriedade Max do seu TProgressBar</strong>. As linhas <a href=\"#udirectorytreesize.92-93\" rel=\"alternate\">92 e 93</a>&#160;mostram&#160;como deve ser feito;&#160;primeiro chamamos o m&#233;todo <strong>DirectoryTreeFileCount</strong>, o qual retorna na propriedade Max e logo em seguida h&#225; a chamada ao m&#233;todo DoMax, o qual vai gerar o evento OnMax, que pode ser manipulado na aplica&#231;&#227;o principal para configurar o TProgressBar. Note que DirectoryTreeFileCount foi chamada com o par&#226;metro FInitialDir, o qual deve ter sido&#160;configurado ap&#243;s a cria&#231;&#227;o da inst&#226;ncia de TDirectoryTreeSize. <strong>N&#227;o se preocupe, isso ser&#225; mostrado&#160;posteriormente</strong>;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Imediatamente ap&#243;s a execu&#231;&#227;o do MA, caso haja um, &#233; hora de chamar o MD</strong>. Caos seu MD seja uma fun&#231;&#227;o, execute-o exatamente como no exemplo (linha <a href=\"#udirectorytreesize.94\" rel=\"alternate\">94</a>). Note que o retorno da fun&#231;&#227;o &#233; colocado em <strong>FResult</strong>, o que &#233; esperado, j&#225; que a propriedade associada (<strong>Result</strong>) serve exatamente para isso. Caso seu MD n&#227;o precise retornar nenhum valor, apenas processar algo, ent&#227;o haveria apenas a chamada ao MD e nem mesmo FResult (ou sua propriedade) teria sido criado (passos 6 e 7)<br /><br /></li>\r\n</ol>\r\n</li>\r\n<li style=\"text-align: justify;\"><strong style=\"font-family: var(--text-font-family); color: var(--text-color); font-size: 1rem;\">Caso alguns de seus campos privados&#160;precisem de inicializa&#231;&#227;o pr&#233;via (cria&#231;&#227;o), voc&#234; deve, na se&#231;&#227;o public da classe, declarar o m&#233;todo construtor da seguinte forma \"constructor Create; override;\"</strong><span style=\"font-family: var(--text-font-family); background-color: var(--module-background-color); color: var(--text-color); font-size: 1rem;\">&#160;e n&#227;o se esque&#231;a de implementar o m&#233;todo destrutor \"</span><strong style=\"font-family: var(--text-font-family); color: var(--text-color); font-size: 1rem;\">destructor Destroy; override;</strong><span style=\"font-family: var(--text-font-family); background-color: var(--module-background-color); color: var(--text-color); font-size: 1rem;\">\" tamb&#233;m na se&#231;&#227;o public, destruindo (Free), cada um dos campos criados no construtor.&#160;</span></li>\r\n</ol>\r\n<div>&#160;</div>\r\n<hr class=\"adsensesnippet\" />\r\n<h2>Como utilizar a classe TDirectoryTreeSize? (ou sua pr&#243;pria classe, como preferir)</h2>\r\n<p style=\"text-align: justify;\">Esta &#233; a parte mais f&#225;cil e mais compensadora deste artigo. Abaixo est&#225; uma parte da unit&#160;<strong>UFORMPrincipal</strong>, que cont&#233;m a classe&#160;<strong>TFORMPrincipal</strong>, da qual removi algumas partes para facilitar o entendimento:</p>\r\n<pre id=\"uformprincipal\" class=\"line-numbers language-pascal\"><code>unit UFORMPrincipal;\r\n\r\ninterface\r\n\r\nuses\r\n Forms, StdCtrls, Classes, Controls, UDirectoryTreeSize, ExtCtrls, ComCtrls;\r\n\r\ntype\r\n TFORMPrincipal = class(TForm)\r\n BUTNProcessamentoPesado: TButton;\r\n EDITDiretorioInicial: TEdit;\r\n PRBAProgresso: TProgressBar;\r\n LABEArquivo: TLabel;\r\n LABEPercentual: TLabel;\r\n procedure BUTNProcessamentoPesadoClick(Sender: TObject);\r\n private\r\n { Private declarations }\r\n FStartTime: TTime;\r\n FDirectoryTreeSize: TDirectoryTreeSize;\r\n procedure DoProgress (const PText: String; const PNumber: Cardinal);\r\n procedure DoMax(const PMax: Int64);\r\n procedure DoTerminate(PSender: TObject);\r\n public\r\n { Public declarations }\r\n end;\r\n\r\nvar\r\n FORMPrincipal: TFORMPrincipal;\r\n\r\nimplementation\r\n\r\n{$R *.dfm}\r\n\r\nuses\r\n Windows, SysUtils, Dialogs;\r\n\r\n{ TFORMPrincipal }\r\n\r\nprocedure TFORMPrincipal.BUTNProcessamentoPesadoClick(Sender: TObject);\r\nbegin\r\n FDirectoryTreeSize := TDirectoryTreeSize.Create;\r\n\r\n with FDirectoryTreeSize do\r\n begin\r\n InitialDir := EDITDiretorioInicial.Text;\r\n OnMax := DoMax;\r\n OnProgress := DoProgress;\r\n OnTerminate := DoTerminate;\r\n\r\n LABEPercentual.Caption := \'0.00%\';\r\n FStartTime := Now;\r\n BUTNProcessamentoPesado.Enabled := False;\r\n EDITDiretorioInicial.Enabled := False;\r\n\r\n Resume;\r\n end;\r\nend;\r\n\r\nprocedure TFORMPrincipal.DoMax(const PMax: Int64);\r\nbegin\r\n PRBAProgresso.Step := 1;\r\n PRBAProgresso.Position := 0;\r\n PRBAProgresso.Max := PMax;\r\n PRBAProgresso.DoubleBuffered := True;\r\nend;\r\n\r\nprocedure TFORMPrincipal.DoProgress(const PText: String; const PNumber: Cardinal);\r\nbegin\r\n PRBAProgresso.StepIt;\r\n LABEArquivo.Caption := \'Arquivo \' + IntToStr(PRBAProgresso.Position) + \' / \' + IntToStr(PRBAProgresso.Max) + \': \' + FormatFloat(\'(###,###,###,###,##0 bytes) \',PNumber) + PText;\r\n LABEPercentual.Caption := FormatFloat(\'##0.00%\',PRBAProgresso.Position / PRBAProgresso.Max * 100);\r\nend;\r\n\r\nprocedure TFORMPrincipal.DoTerminate(PSender: TObject);\r\nbegin\r\n Application.MessageBox(PChar(\'O tamanho total dos arquivos contidos na estrutura de diret&#243;rios \"\' + EDITDiretorioInicial.Text + \'\" &#233; \' + FormatFloat(\'###,###,###,###,##0 bytes\',FDirectoryTreeSize.Result)),PChar(Format(\'Processamento conclu&#237;do em %s\',[FormatDateTime(\'hh:nn:ss\',Now - FStartTime)])),MB_ICONINFORMATION);\r\n BUTNProcessamentoPesado.Enabled := True;\r\n EDITDiretorioInicial.Enabled := True;\r\nend;\r\n\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Para um entendimento ainda melhor, abra o exemplo anexado neste artigo. Abaixo segue a explica&#231;&#227;o detalhada:</p>\r\n<ol>\r\n<li style=\"text-align: justify;\"><strong>Declare um campo privado na classe TFORMPrincipal do tipo TDirectoryTreeSize</strong>. O campo <strong>FDirectoryTreeSize</strong> pode ser visto na linha <a href=\"#uformprincipal.19\" rel=\"alternate\">19</a>;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Declare o manipulador do evento OnProgress</strong> (<strong>DoProgress</strong>) na se&#231;&#227;o private de <strong>TFORMPrincipal</strong> (linha <a href=\"#uformprincipal.20\" rel=\"alternate\">20</a>);<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Implemente o manipulador DoProgress</strong> (linhas <a href=\"#uformprincipal.69-71\" rel=\"alternate\">69&#160;a&#160;71</a>). No nosso exemplo,&#160;este manipulador &#233; ativado sempre que um arquivo tem seu tamanho lido. Na linha <a href=\"#uformprincipal.69\" rel=\"alternate\">69</a> <strong>o valor de TProgressBar &#233; incrementado</strong>. <strong>TProgressBar.StepIt &#233; um m&#233;todo que adiciona o valor da propriedade TProgressBar.Step &#224; propriedade TProgressBar.Position</strong>. Existem pessoas que preferem atribuir diretamente &#224; propriedade&#160;TProgressBar.Position seu valor&#160;+1, mas eu prefiro ser mais pr&#225;tico e usar&#160;TProgressBar.StepIt, que j&#225; faz isso. Na linha <a href=\"#uformprincipal.70\" rel=\"alternate\">70</a> o <strong>nome do arquivo</strong>, contido em <strong>PText</strong>, e seu <strong>tamanho</strong>, contido em <strong>PNumber</strong>, s&#227;o atribu&#237;dos de forma formatada a um TLabel. Na linha <a href=\"#uformprincipal.71\" rel=\"alternate\">71</a>, como uma caracter&#237;stica adicional, eu fa&#231;o um <strong>calculo simples que retorna em um TLabel o percentual atual</strong>, porque todos n&#243;s amamos percentuais que incrementam :);<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Declare o manipulador do evento OnMax</strong> (<strong>DoMax</strong>) na se&#231;&#227;o private de&#160;<strong>TFORMPrincipal</strong> (linha <a href=\"#uformprincipal.21\" rel=\"alternate\">21</a>);<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Implemente o manipulador DoMax</strong> (linhas <a href=\"#uformprincipal.61-64\" rel=\"alternate\">61 a 64</a>). Em nosso exemplo, evento <strong>OnMax</strong> &#233; ativado quando o MA de contagem de arquivos retorna a quantidade total de arquivos que ser&#227;o processados. Este evento tem apenas um par&#226;metro (<strong>PMax</strong>), o qual retorna esta quantidade. &#201; neste evento onde devemos configurar o <strong>TProgressBar</strong>. Na linha <a href=\"#uformprincipal.61\" rel=\"alternate\">61</a>&#160;<strong>&#233; configurado o Step</strong> (passo) do TProgressBar <strong>como 1</strong>, de forma que ao se usar <strong>TProgressBar.StepIt</strong>, <strong>TProgressbar.Position seja automaticamente incrementada em 1 unidade</strong>. Na linha <a href=\"#uformprincipal.62\" rel=\"alternate\">62</a> <strong>TProgressBar.Position &#233; configurada como zero</strong> para que o TProgressBar \"esvazie\". Na linha <a href=\"#uformprincipal.63\" rel=\"alternate\">63</a> &#233; configurada a propriedade <strong>TProgressBar.Max com o valor do par&#226;metro PMax</strong>. &#201; exatamente para isso que o evento OnMax serve! Finalmente, na linha <a href=\"#uformprincipal.64\" rel=\"alternate\">64</a>, <strong>a fim de evitar flickering, a propriedade TProgressBar.DoubleBuffered &#233; configurada como True</strong>;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Declare o manipulador do evento OnTerminate</strong> (<strong>DoTerminate</strong>) na se&#231;&#227;o private de&#160;<strong>TFORMPrincipal</strong> (linha <a href=\"#uformprincipal.22\" rel=\"alternate\">22</a>);<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Implemente o manipulador DoTerminate</strong> (linhas <a href=\"#uformprincipal.76-78\" rel=\"alternate\">76 a 78</a>).&#160;O evento <strong>OnTerminate</strong> &#233; ativado <strong>quando o m&#233;todo Execute de uma thread termina</strong>. O fim do m&#233;todo Execute <strong>sinaliza o fim do MD</strong> e por isso, caso o MD seja uma fun&#231;&#227;o, <strong>&#233; neste evento onde devemos obter o valor retornado pela mesma</strong>. &#201; tamb&#233;m neste evento onde devemos <strong>reabilitar controles que foram desabilitados antes do in&#237;cio da thread</strong>. Na linha <a href=\"#uformprincipal.76\" rel=\"alternate\">76</a> exibimos uma mensagem para o usu&#225;rio informando o tamanho total de todos os arquivos somados na estrutura de diret&#243;rios. Esse valor est&#225; em <strong>FDirectoryTreeSize.Result</strong>. Exibimos tamb&#233;m o tempo que o procedimento levou para ser conclu&#237;do (<strong>Now - FStartTime</strong>). As linhas <a href=\"#uformprincipal.77-78\" rel=\"alternate\">77 e 78</a> <strong>reabilitam os controles que foram desabilitados antes do in&#237;cio da thread</strong> (veja mais adiante neste passo-a-passo);<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Crie um m&#233;todo para iniciar e configurar a inst&#226;ncia de TDirectoryTreeSize</strong>. Isso tamb&#233;m pode ser feito diretamente num manipulador de eventos, como eu fiz, mas admito que criar um m&#233;todo para isso &#233; mais elegante. Como o intuito aqui &#233; ser o mais direto poss&#237;vel nas explica&#231;&#245;es, codificar tudo no manipulador do evento OnClick de um bot&#227;o &#233; perfeitamente v&#225;lido e vai funcionar &#224; contento.&#160;As linhas <a href=\"#uformprincipal.41-56\" rel=\"alternate\">41 a 56</a>&#160;mostram exatamente tudo que precisa ser feito. Abaixo segue o detalhamento;<br /><br />\r\n<ol style=\"list-style-type: upper-roman;\">\r\n<li style=\"text-align: justify;\"><strong>Instancie a classe TDirectoryTreeSize</strong> (linha <a href=\"#uformprincipal.41\" rel=\"alternate\">41</a>) no campo criado para este prop&#243;sito (<strong>FDirectoryTreeSize</strong>);<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Configure as propriedades requeridas pela classe</strong> <strong>TDirectoryTreeSize</strong> (linha <a href=\"#uformprincipal.45\" rel=\"alternate\">45</a>). No caso do exemplo, existe apenas uma propriedade (<strong>InitialDir</strong>), a qual deve ser preenchida com o diret&#243;rio inicial, a partir do qual ser&#225; feita a varredura recursiva. <strong>EDITDiretorioInicial</strong> &#233; um componente <strong>TEdit</strong> que existe em <strong>TFORMPrincipal</strong> para este fim. Se houvessem mais propriedades, elas deveriam ser preenchidas aqui, uma ap&#243;s a outra, a fim de manter o c&#243;digo organizado;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Atribua os manipuladores de evento aos eventos da classe</strong> <strong>TDirectoryTreeSize</strong> (linhas <a href=\"#uformprincipal.46-48\" rel=\"alternate\">46 a 48</a>). O manipulador do evento <strong>OnMax</strong> (<strong>DoMax</strong>) s&#243; precisa ser criado e&#160;atribu&#237;do&#160;<strong>caso haja um m&#233;todo interno que calcula a quantidade de elementos (itens) iter&#225;veis ANTES da realiza&#231;&#227;o do MD</strong>. Caso a quantidade de elementos iter&#225;veis seja conhecida previamente, voc&#234; n&#227;o deve ter criado um MA para calcular este valor e consequentemente o evento OnMax nunca ser&#225; ativado, porque n&#227;o &#233; necess&#225;rio. Como no nosso exemplo n&#243;s precisamos calcular a quantidade total de arquivos dos quais o tamanho deve ser retornado, ent&#227;o <strong>o evento OnMax ser&#225; ativado assim que o total de arquivos for obtido </strong>e o par&#226;metro <strong>PMax conter&#225; este total</strong>.&#160;O evento <strong>OnProgress</strong> ser&#225; ativado a cada vez que um arquivo tiver seu tamanho obtido. No manipulador desse evento (<strong>DoProgress</strong>) &#233; poss&#237;vel obter o nome do arquivo e o seu tamanho individual nos par&#226;metros <strong>PText</strong> e <strong>PNumber</strong>. O evento <strong>OnTerminate</strong>, manipulado pelo m&#233;todo <strong>DoTeminate</strong>, &#233; um evento de <strong>TThread</strong> e <strong>&#233; ativado sempre que uma thread termina, e imediatamente ANTES dela ser liberada da mem&#243;ria (caso ela tenha sido criada com FreeOnTerminate = True)</strong>. <strong>&#201; neste evento onde obtemos o valor de retorno do MD</strong>, pois ele sinaliza que o MD terminou de realizar sua tarefa, logo, caso o MD seja uma fun&#231;&#227;o, seu retorno j&#225; &#233; conhecido;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Execute procedimentos \"pr&#233;-MD\"</strong>. As linhas <a href=\"#uformprincipal.50-53\" rel=\"alternate\">50 a 53</a> cont&#233;m c&#243;digo que <strong>deve ser executado ANTES do MD ser executado na&#160;thread</strong> e o que vai nestas linhas, depende de sua implementa&#231;&#227;o. N&#227;o h&#225; nada de muito especial. No caso do exemplo,&#160;um TLabel&#160;est&#225; sendo \"zerado\" na linha <a href=\"#uformprincipal.50\" rel=\"alternate\">50</a>, um campo de contagem de tempo est&#225; sendo preenchido com o valor atual (Now)&#160;na linha <a href=\"#uformprincipal.51\" rel=\"alternate\">51</a>, o TButton&#160;onde todo este c&#243;digo est&#225;, &#233; desabilitado (para evitar cliques m&#250;ltiplos) na linha <a href=\"#uformprincipal.52\" rel=\"alternate\">52</a>&#160;e, finalmente, o TEdit onde se digita o par&#226;metro InitialDir &#233; desabilitado (linha <a href=\"#uformprincipal.53\" rel=\"alternate\">53</a>);<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Inicialize TDirectoryTreeSize (TThread) usando o m&#233;todo Resume</strong> (linha <a href=\"#uformprincipal.55\" rel=\"alternate\">55</a>). Como por padr&#227;o nossa classe <strong>TDirectoryTreeSize &#233; uma neta&#160;de TThread com CreateSuspended = True</strong>, ent&#227;o, para iniciar a thread, simplesmente executamos seu m&#233;todo Resume, o qual tem por finalidade continuar a execu&#231;&#227;o de uma thread suspensa. Se voc&#234; tem um Delphi mais recente, no lugar de Resume coloque Start, pois o m&#233;todo Resume foi&#160;depreciado;</li>\r\n</ol>\r\n</li>\r\n</ol>\r\n<p>Ap&#243;s realizar todos estes passos, execute e teste! Use o exemplo anexado a este artigo para ver exatamente como tudo foi implementado e comece agora mesmo a converter seus m&#233;todos demorados para usarem corretamente o TProgressBar e TThreads :)</p>\r\n<h2>Esse trabalho todo, compensa?</h2>\r\n<p style=\"text-align: justify;\">Voc&#234; agora deve estar pensando que isso tudo foi feito apenas para implementar o uso correto de uma simples TProgressBar, mas n&#227;o &#233; bem assim. Primeiramente, n&#227;o &#233; tanto trabalho como parece. O artigo ficou grande porque ele &#233; did&#225;tico, mas ao olhar o c&#243;digo fonte do exemplo anexado, se nota que n&#227;o tem muita coisa escrita e se voc&#234; levar em conta que precisa implementar apenas uma classe (TDirectoryTreeSize no exemplo) e depois us&#225;-la, menos c&#243;digo ainda vai sobrar para ser escrito.</p>\r\n<p style=\"text-align: justify;\">Em segundo lugar, mas n&#227;o menos importante, apesar do t&#237;tulo do artigo falar do TProgressBar, este &#233; apenas a ponta do iceberg dentro do contexto, em outras palavras, o \"pretexto\", para se usar um TProgressBar de forma correta &#233; que deve ser levado em conta: <strong>o uso de uma thread para executar um processamento demorado</strong>.</p>\r\n<p style=\"text-align: justify;\">A dica de ouro &#233;: <strong>Sempre que voc&#234; tiver algo que vai demorar um tempo consider&#225;vel, &#233; preciso colocar esse \"algo\" em uma thread, isso garante que sua aplica&#231;&#227;o permane&#231;a responsiva e processando outras mensagens (Windows Messages) importantes sem imprevistos. Al&#233;m disso, para n&#227;o deixar seu usu&#225;rio a ver navios, voc&#234; precisa mant&#234;-lo informado acerca do andamento da tarefa e &#233; a&#237; onde o TProgressBar entra.</strong></p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-09-11 23:33:55',24,'','2020-12-10 18:58:22',24,0,'0000-00-00 00:00:00','2016-09-23 00:42:15','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000102\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',42,88,'Delphi, Addicted 2 Delphi, TProgressBar, Barra de Progresso, Indicador de Andamento, Processo Demorado, Threads','Chutando por baixo, acredito que 90% dos programadores usam o componente TProgressBar de forma errada. Até mesmo eu uso de forma errada porque a forma correta é trabalhosa. Mesmo assim eu acredito que vale muito a pena conhecer o modo correto de se trabalhar com esse componente e caso seu programa faça uso constante dele, porque não criar uma classe que facilita a implementação correta? É isso que pretendo mostrar neste artigo',1,11004,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(103,286,'O lado negro do Application.ProcessMessages','o-lado-negro-do-application-processmessages','<p style=\"text-align: justify;\">Esses dias eu estava escrevendo um artigo e cheguei num ponto onde eu estava justificando o uso de uma thread em detrimento do Application.ProcessMessages. Nesse momento eu lembrei que este comando não deve ser usado de forma leviana, só não tinha uma boa explicação, então fui atrás e achei um artigo muito bom a respeito. Esta é mais uma tradução/versão by myself ;)</p>\r\n','\r\n<p style=\"text-align: justify;\">Mais uma vez quero dizer que parte deste texto é uma tradução/versão do texto original disponível em <a href=\"http://delphi.about.com/od/objectpascalide/a/delphi-processmessages-dark-side.htm\" rel=\"alternate\">http://delphi.about.com/od/objectpascalide/a/delphi-processmessages-dark-side.htm</a>. Eu tomei a liberdade de incluir mais informações sobre mensagens do Windows e corrigir alguns pontos que ficaram nebulosos.</p>\r\n<hr />\r\n<h2>Você usa Application.ProcessMessages? Deveria reconsiderar!</h2>\r\n<p style=\"text-align: justify;\">Ao programar um manipulador de eventos no Delphi (como o evento OnClick de um TButton), chega o momento em que a sua aplicação precisa ficar ocupada por um tempo, por exemplo, o código precisa escrever um grande arquivo ou compactar alguns dados. Se você fizer isso você vai perceber que a sua aplicação parece ficar bloqueada. Seu formulário não pode mais ser movido e os botões não mostram sinais de vida. A aplicação parece ter travado.</p>\r\n<p style=\"text-align: justify;\">A razão para isso acontecer é que uma aplicação Delphi tem apenas uma thread. O código que você está escrevendo representa apenas um monte de procedimentos que são executados pela thread principal da aplicação sempre que o evento ocorre. O resto do tempo esta thread principal está manipulando mensagens de sistema e outras coisas tais como funções de manipulação de forms e componentes. Então, se dentro de um manipulador de eventos você executar uma tarefa demorada, você vai terminar impedindo sua aplicação de manipular estas mensagens e uma solução comum para este tipo de problema é realizar chamadas a <strong>Application.ProcessMessages</strong>, doravante referido simplesmente como <strong>ProcessMessages</strong>.</p>\r\n<p style=\"text-align: justify;\">O ProcessMessages manipula todas as mensagens que estão \"aguardando\" para serem manipuladas, tais como movimentação de janelas, cliques em botões, dentre outras. Ele é usado normalmente como uma solução simples que mantém sua aplicação rodando sem ficar bloqueada. Infelizmente o mecanismo por trás do ProcessMessages tem suas próprias características, as quais podem causar uma grande confusão!</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2>Mas exatamente o que o ProcessMessages faz?</h2>\r\n<p style=\"text-align: justify;\">O Windows usa mensagens (<a href=\"https://msdn.microsoft.com/pt-br/library/windows/desktop/ff381405(v=vs.85).aspx\" rel=\"alternate\">Windows Messages</a>) para \"conversar\" com todas as aplicações que estão em execução. As interações do usuário são enviadas ao formulário através mensagens, as quais são processadas no <a href=\"https://en.wikipedia.org/wiki/Message_loop_in_Microsoft_Windows\" rel=\"alternate\">loop de mensagens da aplicação</a>. Sim! Todo programa tem um loop infinito que fica ativo enquanto o programa estiver em execução e a cada iteração deste loop um item da fila de mensagens é processado. Por exemplo, se o botão esquerdo do mouse for pressionado enquanto o cursor se encontra em cima de um TButton, uma mensagem (<a href=\"https://msdn.microsoft.com/pt-br/library/windows/desktop/ms645607(v=vs.85).aspx\" rel=\"alternate\">WM_LBUTTONDOWN</a>) é enviada ao TButton. Além da mensagem relacionada ao clique em si, existem outras que serão automaticamente geradas para complementar esta ação realizando tudo que precisa ser feito. Uma delas é a pintura (<a href=\"https://msdn.microsoft.com/pt-br/library/windows/desktop/dd145213(v=vs.85).aspx\" rel=\"alternate\">WM_PAINT</a>) do TButton para um estado \"pressionado\" (baixo relevo) e, ao soltar o botão do mouse (ou sair da área-cliente deste botão), uma outra mensagem de pintura é enviada ao botão, de forma que ele seja repintado no estado \"não pressionado\" (alto relevo). Como o TButton pertence ao programa, estas mensagens são enviadas para a fila de mensagens da aplicação em questão e serão processadas em um momento qualquer dentro das iterações do loop de mensagens da aplicação.</p>\r\n<p style=\"text-align: justify;\">Ao executar um procedimento demorado o loop de mensagens da aplicação para até que o procedimento demorado termine. Como o loop de mensagens está parado, a aplicação não processará qualquer mensagem da fila e é por isso que a aplicação parece ter travado, ela fica literalmente sem responder a nenhuma mensagem. Nesta situação o ProcessMessages faz exatamente o que seu nome diz, ele percorre TODAS as mensagens que estão na fila aguardando processamento, processa TODAS elas (executa seus manipuladores) e depois volta o controle para a thread principal, que continua o procedimento demorado. </p>\r\n<p style=\"text-align: justify;\">Para tentar entender melhor, verifique o pseudocódigo abaixo:</p>\r\n<pre id=\"message-loop\" class=\"line-numbers language-pascal\"><code>while true do \r\nbegin\r\n GetMessage(Msg, 0, 0, 0);\r\n case Msg of\r\n WM_LBUTTONDOWN: begin\r\n for i := 0 to 99999999999 do \r\n begin\r\n ResolvaOsProblemasDoMundo(i);\r\n CalcularPiComUmBilhaoDeCasasDecimais;\r\n Application.ProcessMessages;\r\n end;\r\n end;\r\n end;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">O loop mais externo (linha <a href=\"#message-loop.1\" rel=\"alternate\">1</a>) seria o loop de mensagens. Note que a primeira coisa que ele faz (linha <a href=\"#message-loop.3\" rel=\"alternate\">3</a>) é obter uma mensagem da fila de mensagens. Esta mensagem é então verificada por um seletor (linha <a href=\"#message-loop.4\" rel=\"alternate\">4</a>) e o código do manipulador é executado (linhas <a href=\"#message-loop.6-11\" rel=\"alternate\">6 a 11</a>). Se o código do manipulador for muito demorado o loop mais externo não vai completar sua iteração até que este código termine, logo, outras mensagens essenciais que seriam obtidas pelo <strong>GetMessage</strong> não serão obtidas e a aplicação fica travada.</p>\r\n<p style=\"text-align: justify;\">Ao utilizar o ProcessMessages (linha <a href=\"#message-loop.10\" rel=\"alternate\">10</a>), (muito) grosso modo, o que acontece é que o processo demorado (neste caso o loop interno) é suspenso, e todos os manipuladores de todas as mensagens da fila de mensagens serão executados, até que a fila se esvazie. Quando isso acontece o controle volta para o processo demorado e a linha de código subsequente à linha do ProcessMessages será executada.</p>\r\n<p style=\"text-align: justify;\">À primeira vista isso não parece ser algo muito crítico, mas olhando mais de perto o que acontece, é possível notar que <span style=\"text-decoration: underline;\">existe uma sequencia de acontecimentos e que por mais que você pense que várias coisas estão acontecendo ao mesmo tempo, na verdade elas ocorrem de forma ordenada, sequencial.</span> <strong>Ao usar o ProcessMessages, esse ordem é quebrada</strong>. No momento em que o procedimento demorado é suspenso, mensagens que deveriam ser processadas apenas após seu término serão processadas imediatamente.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">Por que eu devo evitar o ProcessMessages?</h2>\r\n<p style=\"text-align: justify;\">Pode ser que você nunca tenha problemas, mas certamente furar a fila de execução das mensagens pode causar uma grande confusão, pois executar ProcessMessages de qualquer maneira pode habilitar chamadas recursivas para qualquer manipulador de eventos novamente. Use o exemplo anexado a este artigo para entender melhor.</p>\r\n<p style=\"text-align: justify;\">No trecho de código simplificado a seguir temos o manipulador de um evento OnClick. O loop FOR simula um processamento longo com chamadas ao ProcessMessages sendo executadas a cada iteração:</p>\r\n<pre id=\"onclick1\" class=\"line-numbers language-pascal\"><code>{in MyForm:}\r\n WorkLevel: Integer; \r\n{OnCreate:} \r\n WorkLevel := 0;\r\n\r\nprocedure TForm1.WorkBtnClick(Sender: TObject);\r\nvar\r\n cycle: Integer; \r\nbegin\r\n inc(WorkLevel);\r\n \r\n for cycle := 1 to 5 do\r\n begin\r\n Memo1.Lines.Add(\'- Work \' + IntToStr(WorkLevel) + \', Cycle \' + IntToStr(cycle);\r\n Application.ProcessMessages;\r\n sleep(1000) ; // ou alguma outra coisa demorada\r\n end;\r\n\r\n Memo1.Lines.Add(\'Work \' + IntToStr(WorkLevel) + \' ended.\');\r\n \r\n dec(WorkLevel);\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\"><strong>Sem ProcessMessages</strong> (linha <a href=\"#onclick1.15\" rel=\"alternate\">15</a>) as seguintes linhas serão escritas no TMemo, se o botão for pressionado <strong>duas vezes</strong> em um curto intervalo de tempo:</p>\r\n<pre><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 1 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 2 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 3 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 4 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 5 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">Work 1 ended. </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 1 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 2 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 3 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 4 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 5 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">Work 1 ended.</span></pre>\r\n<p style=\"text-align: justify;\">Enquanto o processamento estiver em execução, o formulário não mostra qualquer reação, mas o segundo clique foi colocado na fila de mensagens pelo Windows, logo, imediatamente após o manipulador do evento OnClick terminar ele será chamado novamente.</p>\r\n<p style=\"text-align: justify;\"><strong>Incluindo o ProcessMessages</strong>, a saída será muito diferente, veja:</p>\r\n<pre style=\"text-align: justify;\"><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 1 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 2 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 3 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 2, Cycle 1 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 2, Cycle 2 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 2, Cycle 3 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 2, Cycle 4 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 2, Cycle 5 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">Work 2 ended. </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 4 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">- Work 1, Cycle 5 </span><br /><span style=\"font-family: \'courier new\', courier, monospace;\">Work 1 ended.</span></pre>\r\n<p style=\"text-align: justify;\">Desta vez o formulário aparenta estar funcionando e aceita qualquer interação do usuário, então o botão é pressionando mais uma vez, mas agora, no meio do caminho durante o primeiro processamento. O ProcessMessages fará com que este segundo clique seja manipulado imediatamente. Todos os eventos de entrada serão manipulados, bem como quaisquer outras chamadas de função. Na teoria, durante cada chamada a ProcessMessages, qualquer quantidade de cliques e mensagens em geral serão manipuladas instantaneamente.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">Além deste efeito de sobreposição de resultados, existe um efeito colateral mais evidente provocado indiretamente pelo ProcessMessages. No exemplo anexado a este artigo, após clicar no botão \"Begin Work\" com \"Enable ProcessMessages\" habilitado, mova a janela. Note que o processamento para completamente e só retorna quando você deixa de mover a janela. Isso acontece porque o ProcessMessages está tentando esvaziar a fila de mensagens, mas você, ao mover a janela, está introduzindo, a cada pixel movido, mais mensagens (<a href=\"https://msdn.microsoft.com/pt-br/library/windows/desktop/ms632631(v=vs.85).aspx\" rel=\"alternate\">WM_MOVE</a>) à fila. Neste caso curioso, o ProcessMessages acaba se tornando o processamento demorado, porque dentro do loop, ao executá-lo, ele só vai devolver o controle ao mesmo, quando a fila de mensagens esvaziar, só que a fila nunca vai esvaziar enquanto mais mensagens de movimento estiverem entrando nela. Acho que já deu para entender que o ProcessMessages não serve para ser usado dessa forma, <strong>então, seja muito cuidadoso com seu código, ao usá-lo</strong>.</p>\r\n<p style=\"text-align: justify;\">Vamos ver um exemplo diferente. Observe este simples pseudocódigo:</p>\r\n<pre id=\"filewrite\" class=\"line-numbers language-pascal\"><code>procedure OnClickFileWrite();\r\nvar \r\n myfile := TFileStream;\r\nbegin\r\n myfile := TFileStream.create(\'myOutput.txt\');\r\n \r\n try\r\n while BytesReady &gt; 0 do\r\n begin\r\n myfile.Write(DataBlock);\r\n dec(BytesReady,sizeof(DataBlock));\r\n DataBlock[2] := #13;\r\n Application.ProcessMessages;\r\n DataBlock[2] := #13;\r\n end;\r\n finally\r\n myfile.free;\r\n end;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Este procedure escreve uma grande quantidade de dados e tenta manter a aplicação descongelada usando o ProcessMessages a cada vez que um bloco de dados é escrito.</p>\r\n<p style=\"text-align: justify;\">Se o usuário clicar no botão que executa este procedure, enquanto ele já estiver em execução, o mesmo código será executado enquanto o arquivo ainda está sendo escrito. O arquivo não pode ser aberto uma segunda vez e consequentemente o procedure falha. Como resultado dessa falha, talvez haja alguma implementação para liberar buffers, então, DataBlock estaria vazio e uma possível terceira chamada ao procedure (ou uma chamada que já estivesse em execução anteriormente) iria subitamente levantar um Access Violation ao tentar acessá-lo. Neste caso, o código na linha <a href=\"#filewrite.12\" rel=\"alternate\">12</a> poderia funcionar, mas o código na linha <a href=\"#filewrite.14\" rel=\"alternate\">14</a> iria falhar. Mais uma vez se nota que a quebra na ordem dos acontecimentos (furar a fila de mensagens com o ProcessMessages) causa uma imensa bagunça e torna a lógica impossível de se entender.</p>\r\n<p style=\"text-align: justify;\">Uma forma fácil de se contornar estes problemas seria bloquear o acesso aos botões do form configurando sua propriedade Enabled como false. Isso bloquearia qualquer interação com o usuário mas manteria os controles visivelmente ativos, o que não é uma boa ideia. Uma ideia melhor seria desabilitar cada um dos controles do formulário, menos aqueles que porventura precisem ficar ativos (um botão para cancelar o processamento, por exemplo), mas isso é mais complexo, pois seria necessário percorrer todos os controles e desabilitar por demanda cada um, além disso, ao terminar o processamento demorado, os controles precisariam ser percorridos novamente para serem habilitados, mas, digamos, você precisaria manter alguns deles desabilitados, caso estes já estivessem desabilitados ANTES do procedimento demorado. Resumindo, é muito trabalho pra permanecer usando algo que, na maioria dos contextos, não é correto.</p>\r\n<h2 style=\"text-align: justify;\">Não tenha preguiça, use threads!</h2>\r\n<p style=\"text-align: justify;\">Desde o começo deste artigo eu apenas falei de algo demorado sendo feito ao clicar num botão, mas poderia ser um TMenuItem o um TAction. Não importa o que o procedimento demorado faça, na grande maioria das vezes ele é desencadeado por algo simples, como um clique. O OnClick é um evento do tipo <strong>TNotifyEvent</strong> e como o próprio nome da classe diz, ele deveria ser usado para procedimentos de curta duração. Para código pesado, a melhor forma é mover toda a codificação lenta para sua própria thread e manter no evento de curta duração apenas a criação e execução desta thread.</p>\r\n<p style=\"text-align: justify;\">Levando em conta os problemas decorrentes do uso indiscriminado do ProcessMessages, bem como o trabalho adicional de ter que habilitar/desabilitar controles para manter o usuário na linha, o uso de uma segunda thread parece não ser assim tão complicado. Lembre-se de que mesmo poucas linhas de código podem travar uma aplicação por alguns segundos, por exemplo, abrir um arquivo do disco e ter que esperar pelo spin-up do disco terminar. Não seria muito legal se sua aplicação parecesse travada por conta de algum HD lento ou problemático não é mesmo?</p>\r\n<h2 style=\"text-align: justify;\">Afinal o ProcessMessages é inútil?</h2>\r\n<p style=\"text-align: justify;\">Não, eu nunca disse isso! O que deve ser evitado é seu uso indiscriminado, mais especificamente, se você o utiliza dentro de um loop ou dentro de algo que é executado dentro de um loop, com certeza você está fazendo isto errado e seria melhor alterar seu código para usar uma thread separada. Regra geral, fica a dica: <strong>Se seus ProcessMessages estiverem em um loop, então está errado e deve ser evitado</strong>.</p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-09-14 00:21:59',24,'','2020-06-26 17:31:02',24,0,'0000-00-00 00:00:00','2016-09-16 21:13:37','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000103\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',29,89,'Delphi, Addicted 2 Delphi, Threads, ProcessMessages, Windows Messages, Messages Loop','Esses dias eu estava escrevendo um artigo e cheguei num ponto onde eu estava justificando o uso de uma thread em detrimento do Application.ProcessMessages. Nesse momento eu lembrei que este comando não deve ser usado de forma leviana, só não tinha uma boa explicação, então fui atrás e achei um artigo muito bom a respeito. Esta é mais uma tradução/versão by myself ;)',1,13800,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(104,288,'Entendendo a instalação de componentes','como-instalar-corretamente-componentes','<p style=\"text-align: justify;\">Existem na web centenas de artigos que ensinam a criar componentes no Delphi e apesar de eu poder fazer um artigo abordando este tema no futuro, no momento eu prefiro mesmo é falar a respeito de algo que é tão importante quanto o próprio componente em si. Muitas pessoas acham que sabem instalar corretamente um componente, mas o fato de ter o componente funcionando não significa que a instalação foi correta. Você agora deve estar se perguntando \"Ora, mas se está funcionando, porque eu preciso me preocupar?\". Continue lendo e descubra.</p>\r\n','\r\n<p style=\"text-align: justify;\">Então, por que você deveria se preocupar, se um componente está funcionando corretamente? Sabemos que essa pergunta, proveniente do Axioma #19 do <a href=\"http://www.gohorseprocess.com.br/extreme-go-horse-(xgh)\" rel=\"alternate\">XGH</a> não se aplica muito bem no munto real. Um programador responsável não quer apenas fazer com que seus programas funcionem, eles se interessam em entender COMO eles funcionam. A curiosidade é o motor que nos torna especialistas em nossa profissão e se você não tem curiosidade, lamento muito, mas seu lugar não é aqui e te aconselho a parar de ler imediatamente. Se você for um programador Delphi de verdade, ou se considera como tal, pode continuar!</p>\r\n<p style=\"text-align: justify;\">Eu deveria ter escrito este artigo ANTES de começar a falar a respeito do <a href=\"index.php?option=com_content&amp;view=article&amp;id=122&amp;catid=80&amp;Itemid=493\">Open Tools API</a>, mas o OTA é tão interessante que eu não resisti e terminei publicando o artigo sobre ele de forma prematura. Prematura apenas porque um dos pré-requisitos do OTA é saber o mínimo sobre instalação de pacotes no Delphi. Bom, antes tarde do que nunca, vou falar a respeito disso.</p>\r\n<h2>A quem este artigo é destinado?</h2>\r\n<p style=\"text-align: justify;\">Este artigo é destinado a desenvolvedores comuns e desenvolvedores de componentes. Eu estou especificando bem isso porque tanto um como outro podem tirar proveito das explicações que vou fazer.</p>\r\n<p style=\"text-align: justify;\">Desenvolvedores comuns precisam entender como os componentes são instalados a fim de poderem resolver eventuais problemas de instalação devido a componentes mal desenvolvidos (por desenvolvedores de componentes) e com documentação insuficiente.</p>\r\n<p style=\"text-align: justify;\">Desenvolvedores de componente precisam entender como os componentes são instalados a fim de poderem desenvolver e distribuir seus componentes de forma correta, causando assim o mínimo de impacto aos usuários finais (desenvolvedores comuns).</p>\r\n<p style=\"text-align: justify;\">O que é o Library Path? O que é o Search Path? Onde os arquivos são salvos e onde eles precisam estar? Estas são algumas das perguntas que eu pretendo responder neste artigo.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2>Considerações iniciais (O básico)</h2>\r\n<p style=\"text-align: justify;\">Antes de falar a respeito da instalação de componentes é necessário citar alguns conceitos que são relacionados aos componentes (e ao Delphi) e que muitos de vocês simplesmente ignoram porque querem apenas começar a programar o mais rápido possível. Primeiramente o termo \"componente\" que eu utilize no título deste artigo é errado. Usei-o propositalmente para chamar a atenção de programadores iniciantes, mas para falar a verdade este artigo trata da instalação de pacotes no Delphi. Calma! Não queira me matar, você não foi enganado. Vou explicar melhor.</p>\r\n<p style=\"text-align: justify;\">Todo componente é encapsulado em um pacote, mas nem todo pacote contém componentes de fato! Quando você instala um componente, na verdade você está instalando um pacote e este pacote pode conter nenhum, um, ou mais componentes. Convenciona-se, para facilitar, dizer \"instalar um componente\", mas na verdade você está instalando um pacote que pode conter bem mais que o componente em si sendo instalado. Tenha sempre isso em mente a fim de não cometer erros ao referenciar componentes ou pacotes. Em suma, pacotes podem conter componentes</p>\r\n<hr class=\"system-pagebreak\" title=\"Tipos de arquivo que o Delphi manipula\" alt=\"Tipos de arquivo que o Delphi manipula\" />\r\n<h2>Tipos de arquivo que o Delphi manipula</h2>\r\n<p style=\"text-align: justify;\">Gostaria de fazer uma revisão sobre os tipos de arquivos que o Delphi gera ou manipula dentro do contexto dos pacotes. É importante conhecê-los para não cometer erros ao instalar componentes e, principalmente, conhecer seus significados a fim de saber como referenciá-los no Library Path. Sem mais delongas, ei-los:</p>\r\n<div class=\"autooverflowx\">\r\n<table>\r\n<thead>\r\n<tr>\r\n<th style=\"width: 10%;\">Extensão</th>\r\n<th style=\"width: 90%;\">Descrição</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n<th style=\"text-align: right;\" colspan=\"2\">Arquivos de código-fonte (arquivos de texto plano)</th>\r\n</tr>\r\n<tr style=\"display: none;\">\r\n<td colspan=\"2\"> </td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.dpk</td>\r\n<td style=\"text-align: justify;\"><strong>Delphi Package Project File</strong> - Este é o arquivo de projeto principal de um pacote. Em comparação com um projeto padrão do Delphi, este arquivo equivale ao arquivo .dpr (Delphi Project). O arquivo de projeto de um pacote contém várias configurações adicionais, ao contrário do arquivo .dpr. A maioria destas configurações pode ser modificada por meio da caixa de diálogo \"Project Options\". Algumas das configurações mais básicas encontram-se no item (ou aba) \"Description\" desta caixa de diálogo, cujos campos que merecem destaque são: Description, Usage options e LIB suffix. Falarei mais a respeito destas opções posteriormente neste artigo</td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.pas</td>\r\n<td style=\"text-align: justify;\"><strong>Pascal Source File</strong> - Todo e qualquer projeto Delphi, seja um pacote, um executável, ou uma biblioteca, normalmente possui ao menos um arquivo de fonte. Bibliotecas (dll) ou aplicações de console, quando muito simples, podem conter apenas o arquivo .dpr, sem qualquer arquivo .pas. O arquivo .dpr, pode conter o fonte completo, mas por organização você não vai querer que todo o seu programa seja escrito em um único arquivo, logo, é desejável sempre ter ao menos um arquivo .pas para organizar seu código-fonte. Arquivos .pas são compiláveis e ao serem compilados geram arquivos .dcu como resultado</td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.inc</td>\r\n<td style=\"text-align: justify;\"><strong>Include File</strong> - Um arquivo de inclusão pode ter qualquer extensão, mas convenciona-se que arquivos desta natureza tenham a extensão .inc. O uso mais comum dos arquivos de inclusão é o agrupamento de diretivas de compilação, mas eles são bem mais que isso. Arquivos de inclusão são arquivos de texto plano que podem conter basicamente qualquer coisa que você colocaria em um arquivo .pas, mas com uma diferença muito importante: arquivos usados como arquivos de inclusão NÃO SÃO COMPILADOS SOZINHOS como os arquivos .pas, eles precisam ser incluídos em algum fonte compilável. Esta característica, que a princípio parece ser uma desvantagem, na verdade permite que arquivos de inclusão possuam trechos completos de código, classes, constantes, variáveis e qualquer outro tipo de código-fonte que podem ser incluídos em pontos específicos de arquivos .pas, sem necessidade de replicar código.<br /><br />Como um exemplo bem simples e, por isso mesmo, não tão usual, suponha que você possui uma mensagem que precisa ser exibida da mesma forma em vários locais distintos dentro do seu código-fonte. Você pode escrever um arquivo <strong>minhamensagem.inc</strong> da seguinte maneira:<br />\r\n<pre class=\"language-pascal\"><code>Application.MessageBox(\'Eu fui definida em um arquivo .inc\'\r\n ,\'Mensagem do arquivo de inclusão\'\r\n ,MB_ICONWARNING);</code></pre>\r\nPosteriormente você pode em um código-fonte propriamente dito (arquivo .pas) inclur este seu arquivo .inc, em um ponto do código-fonte onde ele faz sentido, isto é, em um local onde o conteúdo do arquivo .inc, <strong>SE FOSSE DIGITADO</strong>, seria correto. Como no arquivo .inc há apenas a chamada a Application.MessageBox, nós podemos incluir nosso arquivo em um local onde a chamada a esta função pode ser aplicada, por exemplo, o clique de um botão:<br />\r\n<pre class=\"language-pascal\"><code>procedure TForm1.Button1Click(Sender: TObject);\r\nbegin\r\n {$i minhamensagem.inc}\r\nend;</code></pre>\r\nComo se pode ver, para referenciar um arquivo de inclusão, se usa a diretiva {$I nomedoarquivo.inc}. A grande vantagem dos arquivos de inclusão só é percebida quando se entende que o trecho de código acima, na verdade é interpretado pelo compilador como:<br />\r\n<pre class=\"language-pascal\"><code>procedure TForm1.Button1Click(Sender: TObject);\r\nbegin\r\n Application.MessageBox(\'Eu fui definida em um arquivo .inc\'\r\n ,\'Mensagem do arquivo de inclusão\'\r\n ,MB_ICONWARNING);\r\nend;</code></pre>\r\nComo se pode observar o conteúdo do arquivo de inclusão é \"digitado\" no local onde ele for incluído, logo, o ponto no código-fonte onde arquivos de inclusão podem ser colocados depende unicamente de seu conteúdo. Você poderia colocar uma classe inteira, com todos os seus métodos definidos, dentro de um arquivo de inclusão, no entanto ele não poderia ser incluído dentro do clique de um botão, pois se você substituir, na posição do include, o conteúdo do arquivo de inclusão, o fonte .pas não vai compilar.</td>\r\n</tr>\r\n<tr>\r\n<th style=\"text-align: right;\" colspan=\"2\">Arquivos gerados (arquivos binários)</th>\r\n</tr>\r\n<tr style=\"display: none;\">\r\n<td colspan=\"2\"> </td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.dcu</td>\r\n<td style=\"text-align: justify;\"><strong>Delphi Compiled Unit</strong> - Todo arquivo .pas, ao ser compilado, gera um arquivo correspondente .dcu. Arquivos .dcu, em outras palavras, são a versão binária de um arquivo .pas. Linguagens como C e C++, possuem um arquivo chamado .obj que são gerados a partir de arquivos .c ou .cpp. Pode-se dizer que arquivos .dcu estão para arquivos .pas assim como arquivos .obj estão para arquivos .c ou .cpp. Arquivos .dcu são diferentes quando compilados por compiladores diferentes, isto é, se você tem um mesmo fonte .pas e compila este fonte no Delphi 2006 e no Delphi XE5, os arquivos .dcu gerados serão completamente diferentes, apesar de fazerem a mesma coisa. É este um dos motivos pelos quais não se pode instalar componentes sem fontes (sem arquivos .pas) em qualquer Delphi, porque os arquivos .pas, .bpl e .dcp são dependentes da versão do compilador e simplesmente são incompatíveis. Você só consegue instalar componentes sem fontes, quando estes 3 arquivos foram compilados exclusivamente para a versão do Delphi onde você pretende instalá-los</td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.bpl</td>\r\n<td style=\"text-align: justify;\"><strong>Borland Package Library</strong> - Arquivos de projeto .dpr geram arquivos .exe ou .dll. Arquivos de projeto .dpk geram sempre dois arquivos. Um deles é o arquivo .bpl o outro é o arquivo .dcp (vide próximo ítem). Arquivos .bpl estão para projetos .dpk assim como arquivos .exe e .dll estão para projetos .dpr, logo, os arquivos .bpl são o resultado final mais importante da compilação de um pacote. Eles se assemelham estruturalmente a uma dll e como tal podem ser carregados por executáveis (Runtime Packages) e, no seu uso mais simples, carregadas pelo próprio Delphi (Designtime Packages). O carregamento por executáveis não será coberto neste artigo, apenas o carregamento simples, pela IDE, o qual é essencial para o entendimento de como ocorre a instalação de pacotes. Quando um pacote é instalado, na verdade, nós estamos informando ao Delphi que aquele determinado arquivo .bpl gerado, deve ser carregado pela IDE. A instalação deste arquivo fará com que componentes apareçam na paleta de componentes e experts/wizards sejam registrados (vide artigo <a href=\"index.php?option=com_content&amp;view=article&amp;id=122&amp;catid=80&amp;Itemid=493\">Open Tools API</a>).</td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.dcp</td>\r\n<td style=\"text-align: justify;\"><strong>Delphi compiled package</strong> - O arquivo .dcp é gerado juntamente com o arquivo .bpl. Ele é um arquivo binário especial que contém o cabeçalho do pacote BPL e a concatenação de todos os arquivos .dcu deste pacote, bem como toda informação necessária requerida pelo compilador e pelo ligador (linker).<br /><br />Como se pode observar, o arquivo .dcp é como se fosse um resumo binário de todo o código-fonte do pacote, por isso, uma característica muito interessante deste arquivo é que ele substitui a presença de todos os arquivos .dcu que ele contém! De fato, quando um pacote faz referência em sua cláusula <strong>requires</strong> (veja mais adiante) a um arquivo .dcp, este pacote pode usar units que estão neste arquivo .dcp, como se as units compiladas (.dcu) estivessem fisicamente disponíveis! Em suma, eu não preciso ter nenhum arquivo .dcu disponível, caso eu possua o arquivo .dcp que o contenha.<br /><br />O papel do arquivo .dcp é muito relevante dentro desenvolvimento de pacotes no Delphi. Os arquivos .dpk possuem uma seção especial chamada <strong>requires</strong>, na qual outros pacotes são referenciados, veja:<br /><br /><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/requires.jpg\" alt=\"\" /><br />Como se pode ver, este pacote possui 3 dependências de outros pacotes. Significa que dentro do pacote nós estamos usando componentes, funções ou recursos de uma forma geral que estão disponíveis em outros pacotes. No pacote do exemplo acima eu estou fazendo referências ao TClientDataSet, o qual é definido na unit <em>DBClient</em> que foi compilada no pacote <a title=\"Dependendo da configuração da propriedade Lib Suffix o XXX pode ser substituído por uma string qualquer. Normalmente o Lib Suffix indica a versão do compilador. Por exemplo, se este pacote tivesse sido compilado no Delphi 2006, o nome do BPL gerado deveria ser dsnap100.bpl, pois a versão do compilador do Delphi 2006 é 10\" href=\"#\" rel=\"bookmark\">dsnapXXX.bpl</a> e por isso eu preciso fazer referência direta ao \"resumo\" deste pacote. Para isso eu uso seu arquivo .dcp correspondente (<a title=\"Arquivos DCP, ao contrário dos arquivos BPL não recebem o Lib Suffix, o qual é usado apenas nas saídas principais do compilador, isto é, apenas arquivos .exe, .dll e .bpl utilizam o Lib Suffix para formar o nome do arquivo\" href=\"#\" rel=\"bookmark\">dsnap.dcp</a>) na cláusula requires do .dpk, pois dentro de dsnap.dcp existe o arquivo DBClient.dcu.<br /><br />Apesar de eu estar falando que o arquivo .dcu está dentro do arquivo .dcp, isso é apenas para facilitar o entendimento. Arquivos .dcp não são contêineres, logo, não é possível extrair um arquivo .dcu de dentro do arquivo .dcp. O arquivo .dcp possui todo conteúdo binário de todos os arquivos .dcu de um pacote. O Delphi, seu compilador e seu linker fazem referência a parte desse código de forma nomeada, usando o mesmo nome da unit original, por exemplo, dentro de um pacote que requer dsnap.dcp, caso em uma unit eu declare na cláusula uses DBClient, o Delphi vai saber que o código desta unit está disponível. No momento da compilação, o compilador entende que deve procurar o código binário de DBClient dentro de dsnap.dcp.<br /><br />Arquivos .dcp são muito importantes quando precisamos nos referenciar ao nosso pacote a partir de outro, logo, isso não é algo que um iniciante no desenvolvimento de componentes vá fazer, portanto, não precisa se preocupar com os arquivos .dcp no momento. Apenas entenda que eles são necessários e não devem ser negligenciados.</td>\r\n</tr>\r\n<tr>\r\n<th style=\"text-align: right;\" colspan=\"2\">Arquivos de recurso (arquivos binários e de texto plano)</th>\r\n</tr>\r\n<tr style=\"display: none;\">\r\n<td colspan=\"2\"> </td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.res / .dcr</td>\r\n<td style=\"text-align: justify;\"><strong>Resource File</strong> - Arquivos de recurso com a extensão .res ou .dcr <span style=\"text-decoration: underline;\">são arquivos binários</span> que podem conter ícones, cursores, imagens, strings, teclas de atalho, menus, caixas de diálogo, dados binários sem formato específico (raw) e informações de versão. Normalmente no Delphi você não precisa se preocupar com arquivos de recurso, pois eles são criados automaticamente e incluídos no binário final (.exe, .dll, .bpl), contudo, ao desenvolver componentes especificamente, eventualmente você vai precisar incluir ao menos recursos de imagem e ícones no seu BPL e a única forma de fazer isso é por meio do uso de arquivos de recurso. Você pode criar um arquivo .res / .dcr usando um editor de recursos e vincular este arquivo de recurso ao seu projeto, incluindo a diretiva <strong>{$R nomedoarquivo.res}</strong> ou <strong>{$R nomedoarquivo.dcr}</strong> em algum fonte do mesmo (ou no seu arquivo de projeto). A inclusão pode usar um arquivo de texto plano também (vide .rc abaixo). Não existem diferenças práticas entre um .dcr e um .res, apenas convenciona-se usar a extensão .dcr para indicar um arquivo de recurso que possui os ícones de um componente que serão apresentados na paleta de componente do Delphi. DCR significa \"Delphi Component Resource\"</td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.rc</td>\r\n<td style=\"text-align: justify;\"><strong>Compiler Resource File</strong> - Arquivos de recurso com a extensão .rc <span style=\"text-decoration: underline;\">são arquivos de texto plano</span> e que podem ser considerados \"código-fonte\" de recurso, pois é possível gerar um arquivo .res a partir de um arquivo .rc utilizando um compilador de recurso (<strong>Resource Compiler</strong>). A extensão .rc, portanto, remete ao nome Resource Compiler, já que este arquivo sempre precisa ser compilado antes de ser usado. Arquivos .rc podem conter exatamente os mesmos itens que um arquivo .res, a diferença é que estes ítens são incluídos no arquivo .rc de forma textual, \"legível por humanos\". Arquivos .rc não podem ser vinculados diretamente a um binário, tal como acontece com os arquivos .res, é necessário compilá-lo antes e, por este motivo, o Delphi possui uma sintaxe especial da diretiva {$R} que permite compilar e vincular um arquivo .rc. Utilizando <strong>{$R nomedoarquivo.res nomedoarquivo.rc}</strong>, <strong>nomedoarquivo.rc</strong> será automaticamente compilado e gerará o arquivo <strong>nomedoarquivo.res</strong>, o qual, finalmente, será vinculado pela diretiva</td>\r\n</tr>\r\n<tr>\r\n<td style=\"vertical-align: top;\">.dfm</td>\r\n<td style=\"text-align: justify;\"><strong>Delphi Form File</strong> - Você deve estar se perguntando porque arquivos .dfm estão incluídos nesta lista de recursos, bom, se segure na cadeira meu caro, mas <strong>arquivos .dfm são arquivos de recurso</strong>! Você não leu errado. Arquivos .dfm são arquivos de recurso especiais e exclusivos do Delphi e que contém referências a todas as propriedades publicadss do TForm e de todos os componentes nele incluídos. Propriedades publicadas, ou published, são propriedades que geram Informações de Tipo em Tempo de Execução, ou RTTI. Se você abrir um executável, por exemplo, em um editor de recurso, e for na seção <strong>RCData</strong>, você verá todos os seus formulários lá, veja:<br /><br /><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/rcdata.jpg\" alt=\"\" /><br />Note que as propriedades publicadas do TForm aparecem, bem como as propriedades publicadas de outros componentes inseridos no TForm, tal como o TButton que se pode ver na imagem acima. Você deve estar pensando agora se é possível modificar algumas destas propriedades diretamente no arquivo binário usando o editor de recurso, e, bem, sim, isso é totalmente possível! Qualquer uma destas propriedades pode ser alterada e salva diretamente no arquivo binário, no caso no .exe, sem necessidade de recompilá-lo, e isso vai alterar o resultado da aplicação em tempo de execução, porém, devo alertar que algumas coisas não devem ser alteradas, sob pena de você inutilizar seu executável. Arquivos .dfm podem ser salvos de forma binária o que não é recomendável, mas mesmo quando eles são salvos desta forma, dentro do binário compilado sua representação sempre é textual. Isso significa que o modo binário de salvamento de arquivos .dfm só afeta de fato o arquivo em si e não a forma como ele é vinculado ao projeto finalizado (.exe, .dll ou .bpl).<br /><br />Outra prova inegável de que arquivos .dfm são, também, arquivos de recuso, é a forma como eles são vinculados aos projetos. Se você criar um projeto novo e olhar o código-fonte do único formulário você verá a seguinte linha <strong>{$R *.dfm}</strong>. A diretiva {$R} você já conhece, ela serve para vincular um arquivo de recurso ao projeto, logo, o nome de arquivo que vem depois dela, é um arquivo de recurso. Mas tem algo diferente, você diz, \"não existe um nome de arquivo, mas sim um caractere curinga com a extensão .dfm\". Se você pensou isso você está errado. O asterisco parece, mas <strong>não é um caractere curinga e *.dfm não significa, portanto, todos os arquivos com extensão .dfm</strong>. A diretiva {$R} interpreta o asterisco como sendo o nome-base da unit atual, isto é, o nome da unit sem a extensão, logo, se sua unit se chama UFormPrincipal.pas, usar a diretiva {$R *.dfm} nela vai instruir ao Delphi para vincular ao projeto o arquivo UFormPrincipal.dfm. É por este motivo que toda vez que você salva a unit de um TForm, um arquivo .dfm de mesmo nome será gerado juntamente com ela. Se você alterar a diretiva e colocar o nome do arquivo .dfm completo, vai funcionar do mesmo modo, no entanto, isso é desencorajado, na maioria das situações, quando você não sabe o que está fazendo, pois, caso você altere o nome da unit, um novo .dfm será criado para ela, mas, internamente, ela estará vinculada a um outro arquivo .dfm, o que pode gerar uma grande confusão. O caractere especial \"*\" também pode ser usado ao fazer referência a um arquivo .res propriamente dito, tendo, pois, o mesmo significado!<br /><br />Para saber um pouco mais sobre RTTI, arquivos .dfm e um uso inusitado dessa combinação de tecnologias, leia o artigo <a href=\"index.php?option=com_content&amp;view=article&amp;id=97&amp;catid=80&amp;Itemid=493\">Serialização de Objetos &amp; Persistência em Arquivos</a>.</td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n<hr class=\"adsensesnippet\" />\r\n<h2>Arquivos gerados pela compilação de um pacote</h2>\r\n<p style=\"text-align: justify;\">Ao contrário de um executável ou de um biblioteca (dll) a compilação de um pacote gera 3 tipos de arquivo importantes:</p>\r\n<ul>\r\n<li style=\"text-align: justify;\"><strong>Arquivo BPL</strong> - Cada pacote gera um e apenas um arquivo BPL. Arquivos BPL podem ser compilados de acordo com o uso que eles terão. Esta configuração chama-se \"Usage Option\" e está disponível no item (ou aba) \"Description\" da caixa de diálogo \"Project Options\". Existem 3 formas de uso para um BPL:<br /><br />\r\n<ul>\r\n<li style=\"text-align: justify;\"><strong>Designtime Only</strong> - BPLs desse tipo são desenvolvidos para serem instalados na IDE e por este motivo eles normalmente possuem apenas código que só faz sentido dentro da IDE. Este código inclui basicamente editores de componente, editores de propriedade e código de registro que normalmente é colocado dentro do procedure especial \"Register\". Não é comum desenvolver um BPL deste tipo que não contenha alguns destes códigos específicos. Se sua intenção é desenvolver um pacote que contenha apenas units e funções utilizáveis por projetos, você deve desenvolver um pacote \"Runtime Only\"<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Runtime Only</strong> - BPLs desse tipo são desenvolvidos para serem usados como pacote em tempo de execução por executáveis ou internamente pela IDE em tempo de desenvolvimento por projetos. Este tipo de BPL não é instalável, então ele não pode conter códigos dos tipos mencionados no item anterior (Designtime Only). Pacotes deste tipo podem conter, portanto, todo o restante de possíveis códigos, incluindo o código de componentes que são instalados em pacotes do tipo Designtime Only<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Designtime and Runtime</strong> - BPLs deste tipo possuem uma junção de características dos dois tipos anteriores. Em outras palavras, pacotes deste tipo contém componentes e também podem ser carregados por executáveis em tempo de execução</li>\r\n</ul>\r\n</li>\r\n<li style=\"text-align: justify;\"><strong>Arquivo DCP</strong> - Concatenação binária de todas as units de um pacote sendo compilado (BPL). Vide explicação sobre arquivos .dcp na tabela acima.</li>\r\n</ul>\r\n<ul>\r\n<li style=\"text-align: justify;\"><strong>Arquivo DCU</strong> - Arquivos de fonte compilados (arquivos .pas compilados). Vide explicação sobre arquivos .dcu na tabela acima. Os arquivos .dcu são tão importantes em um pacote quanto o próprio arquivo .bpl que é gerado. Enquanto para um executável normal ou biblioteca, arquivos .dcu são apenas arquivos intermediários, para os pacotes estes arquivos são essenciais para que o componente possa ser usado. Arquivos .dcu de um pacote são SEMPRE arquivos intermediários de outros projetos que usam units desse pacote. De forma mais simples, arquivos .dcu de um componente são arquivos intermediários quando compilamos projetos que usam este componente e é por isso que os arquivos .dcu de um pacote precisam sempre estar disponíveis via \"paths do Delphi\" (leia mais adiante)</li>\r\n</ul>\r\n<hr class=\"system-pagebreak\" title=\"Como o Delphi encontra os arquivos de um pacote?\" alt=\"Como o Delphi encontra os arquivos de um pacote?\" />\r\n<h2>Como o Delphi encontra os arquivos de um pacote?</h2>\r\n<p style=\"text-align: justify;\">É de extrema importância conhecer como o Delphi funciona para poder saber resolver problemas comuns. Entender como o Delphi encontra os arquivos de um pacote é um requisito básico, seja você um desenvolvedor comum ou um desenvolvedor de componentes. Como desenvolvedor comum, por exemplo, se você já programa há algum tempo e já instalou componentes, certamente você já deve ter visto uma mensagem parecida com esta:</p>\r\n<blockquote>\r\n<p style=\"text-align: center;\">Unit MinhaUnit was compiled with a different version of MinhaOutraUnit.TMinhaClasse</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Este é um dos erros mais frequentes causados por uma instalação inadequada de pacotes e pode ocorrer até mesmo dentro de projetos comuns (não pacotes), sendo mais raros nestes últimos. Ao entender como o Delphi encontra os arquivos de um projeto qualquer ficará mais fácil resolver problemas como este, bem como evitar que eles ocorram, fazendo sempre o uso correto das configurações de \"Paths\" que o Delphi considera.</p>\r\n<p style=\"text-align: justify;\">A forma como o Delphi procura os arquivos de um pacote é a mesma forma que ele usa para encontrar os arquivos de qualquer projeto que nele se compila. Antes de explicar como isso é feito, é necessário entender que uma unit pode estar disponível de forma compilada (.dcu) ou código-fonte (.pas) e o Delphi tem preferências especiais quando encontra estes arquivos:</p>\r\n<ul>\r\n<li style=\"text-align: justify;\">Se estiver disponível apenas o arquivo .pas ele será usado, sendo compilado normalmente<br /><br /></li>\r\n<li style=\"text-align: justify;\">Se estiver disponível apenas o arquivo .dcu ele será usado, mas não será compilado, porque não é necessário<br /><br /></li>\r\n<li style=\"text-align: justify;\">Se ambos os arquivos estiverem disponíveis, será usado sempre o arquivo .pas, o qual será compilado normalmente</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">De agora em diante ao me referenciar a <strong>uma unit</strong> eu estarei falando de um arquivo que representa uma unit do Delphi e que pode ser, ou um .dcu, ou um .pas. Isso servirá apenas para que eu não tenha que me referir aos dois arquivos, que no final representam a mesma coisa de formas diferentes.</p>\r\n<p style=\"text-align: justify;\">Finalmente, conhecendo a ordem de preferência que o Delphi usa para selecionar uma unit, podemos agora verificar de forma simples e linear (em ordem de busca), como ele faz para achar as units que estão referenciadas nas cláusulas uses:</p>\r\n<ol>\r\n<li style=\"text-align: justify;\">Procura dentre as units listadas explicitamente no arquivo .dpk<br /><br /></li>\r\n<li style=\"text-align: justify;\">Procura no mesmo diretório que contém o arquivo .dpk<br /><br /></li>\r\n<li>Procura dentre os caminhos listados no Search Path (projeto)<br /><br /></li>\r\n<li>Procura dentre os caminhos listados no Library Path (global)</li>\r\n</ol>\r\n<p>Caso uma unit não esteja em nenhum destes 4 lugares o seu projeto não vai compilar de jeito nenhum, mas gostaria de relatar aqui algo que constatei. Vou até deixar este relato destacado.</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Nos testes que eu fiz para determinar os 4 locais de busca, aconteceu algo muito estranho. Eu compilei um projeto de teste com uma unit na mesma pasta do arquivo .dpk, em seguida a apaguei esperando receber um erro de compilação, já que a unit não existia mais. Para minha surpresa o projeto compilou normalmente e até mesmo o arquivo .dcu correspondente ao arquivo .pas que eu apaguei foi criado! Não consegui descobrir como o compilador fez isso, mas ao fechar e abrir o Delphi o comportamento foi o esperado, isto é, o projeto não mais compilou. Até onde eu sei, isso é um comportamento muito bizarro, portanto, fica como dica de boa prática, fechar o Delphi e abri-lo novamente, sempre que se mover ou apagar units, principalmente quando se estiver desenvolvendo componentes, já que é imprescindível que os arquivos sendo referenciados pelo projeto existam de fato e não por conta de algum <strong>cache sobrenatural</strong></p>\r\n</blockquote>\r\n<div> </div>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">Além de units, um esquema de busca semelhante é usado para outros arquivos do projeto, tais como arquivos .res, .dfm e .inc. Estes arquivos são incluídos no projeto por diretivas especiais {$R}, para arquivos .res e .dfm, e {$I}, para arquivos .inc. Ambas as diretivas aceitam o caractere especial \"*\" (veja o significado deste caractere na explicação sobre arquivos .dfm, mais acima) e também um caminho que pode ser absoluto ou relativo. Vejamos alguns pormenores de cada uma destas formas de uso das diretivas:</p>\r\n<ul>\r\n<li style=\"text-align: justify;\">Ao usar caminhos absolutos, caso este caminho contenha espaços, aspas simples devem ser usadas em volta do mesmo. Referências por caminhos absolutos, como é de se imaginar, não têm ambiguidades, logo não há dúvidas de que um arquivo referenciado desta forma deve existir impreterivelmente no local indicado e ponto final. Já ao usar caminhos relativos, será considerado como ponto de partida o diretório onde a unit com a referência a {$R} ou {$I} estiver. A regra de uso de aspas simples também se aplica aqui, bem como o fato de que o caminho é totalmente conhecido e sem ambiguidades<br /><br /></li>\r\n<li style=\"text-align: justify;\">Ao usar o caractere <strong>*</strong> é necessário entender que ele se transforma meramente no nome da unit onde a referência a {$R} ou {$I} estiver, portanto, <strong>{$I *.inc}</strong> dentro de uma unit de nome <strong>teste.pas</strong>, na verdade deve ser lida como <strong>{$I teste.inc}</strong></li>\r\n</ul>\r\n<p>No segundo uso mostrado acima, como teremos apenas o nome de um arquivo, sem caminhos relativos ou completos, a busca por ele seguirá a seguinte regra:</p>\r\n<ol>\r\n<li style=\"text-align: justify;\">Procura no mesmo diretório onde a unit com a referência a {$R} ou {$I} estiver<br /><br /></li>\r\n<li style=\"text-align: justify;\">Procura no mesmo diretório que contém o arquivo .dpk<br /><br /></li>\r\n<li>Procura dentre os caminhos listados no Search Path (projeto)<br /><br /></li>\r\n<li>Procura dentre os caminhos listados no Library Path (global)</li>\r\n</ol>\r\n<p style=\"text-align: justify;\">Foi detectado que o problema do <em>cache sobrenatural</em> descrito no quadro acima também afeta estes arquivos especiais, só que, ao contrário das units, o problema ocorreu ao remover um arquivo .inc que estava na mesma pasta de uma unit que o usava. Mesmo sem o arquivo .inc presente, ainda assim a compilação foi bem sucedida e o arquivo .inc carregado era aquele que estaria na pasta. Bizarro! Ao reiniciar o Delphi o comportamento foi o esperado mais uma vez, portanto, muito cuidado ao mover ou excluir tais arquivos. Lembre-se sempre de reiniciar o Delphi.</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">O Delphi não busca arquivos em caminhos listados no Path do Windows. Isso é uma má prática hoje em dia. Não polua seu sistema ou o de seus clientes com arquivos desnecessariamente. <strong>Apenas arquivos .bpl que são carregados pelo Delphi</strong>, precisam estar em caminhos listados no Path do Windows e mesmo assim estes caminhos são exclusivos no Delphi, ou seja, não espere encontrar arquivos .bpl dentro de pastas como C:\\Windows\\System32, C:\\Windows\\System ou C:\\Windows\\SysWOW64. <strong>A prática de colocar estes arquivos nestes caminhos de sistema foi felizmente abolida! Se você é um programador das antigas, já deve ter colocado algumas BPLs na pasta System ou System32, mas não faça mais isso!</strong> Existe uma pasta específica para este tipo de arquivo. Esta pasta pode ser configurada no mesmo local onde se encontram o Library Path e o Browsing Path (explicados posteriormente neste artigo). A configuração se chama <strong>Package Output Directory</strong>. Os nome desta configuração pode variar um pouco, mas não será difícil identificá-la.</p>\r\n</blockquote>\r\n<hr class=\"system-pagebreak\" title=\"Apresentando os vilões: Search Path &amp; Library Path\" alt=\"Apresentando os vilões: Search Path &amp; Library Path\" />\r\n<h2>Apresentando os vilões: Search Path &amp; Library Path</h2>\r\n<p style=\"text-align: justify;\">Sim! Estas duas configurações são maliciosas e são a causa de muitos problemas de instalação de pacotes e até mesmo de compilação de projetos em geral. Na verdade a maior vantagem de ambas é também sua maior desvantagem: <strong>a versatilidade!</strong> De tão versáteis, estas propriedades se tornam perigosas aos incautos e eu vou explicar o porquê.</p>\r\n<p style=\"text-align: justify;\">Bem, um path funciona listando vários diretórios (pastas) de busca. Suponha que você tenha uns 100 diretórios listados, neste caso os arquivos que o Delphi procura podem estar em quaisquer destes diretórios, inclusive em mais de um deles e é aí onde está o lado negro dessa propriedade. Se você faz referência a <strong>MinhaUnit</strong> em uma cláusula uses e existe MinhaUnit.pas (ou .dcu) em mais de um desses 100 diretórios, o Delphi vai usar aquele que achar primeiro, segundo a ordem da lista de diretórios. Veja, por exemplo, a lista de um Library Path típico:</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/LibraryPath.jpg\" width=\"479\" height=\"305\" /></p>\r\n<p style=\"text-align: justify;\">Acima, a ordem que o Delphi realiza a busca por um arquivo é, de fato, de cima para baixo. Se nossa unit estiver em todos estes caminhos, o Delphi vai usar a versão que está no primeiro diretório listado e vai ignorar a presença de tal unit nos demais. Se a versão contida no primeiro diretório for mais antiga do que aquela que está nos outros, podem haver erros de compilação ou comportamentos errados de um programa que use esta unit.</p>\r\n<p style=\"text-align: justify;\">Caso no path escolhido pelo Delphi esteja uma versão compilada (.dcu) ela será usada, tal como foi dito anteriormente, mas caso este arquivo .dcu seja uma compilação de uma versão diferente daquela que se espera, a famosa mensagem \"Unit MinhaUnit was compiled with a different version of MinhaOutraUnit.TMinhaClasse\" será exibida! Ignore a posição de MinhaUnit nesta mensagem. Ela poderia estar no lugar de MinhaOutraUnit, não importa, o que importa é que este tipo de erro acontece por existirem versões de arquivos .dcu diferentes daquelas que deveriam existir.</p>\r\n<p style=\"text-align: justify;\">Se você estiver tendo alguns dos problemas listados aqui, o primeiro passo é procurar nos caminhos listados tanto no Library Path como no Search Path do projeto as units problemáticas e verificar se elas não se repetem, mantendo apenas uma cópia de cada uma no seu caminho correto. Procure sempre por nomedaunit.* e mantenha inicialmente apenas arquivos .pas. Isso é uma regra geral para ajudar a resolver problemas rapidamente, mas como eu explicarei posteriormente neste artigo, devemos manter no Library Path apenas arquivos .dcu, .res, .inc e .dfm, enquanto que no Search Path só devem existir arquivos .pas.</p>\r\n<p style=\"text-align: justify;\">A fim de tirar você da ignorância quanto à instalação de pacotes eu fiz este artigo e a partir deste ponto eu vou explicar para que servem cada uma das configurações, de forma que você as use corretamente e possa amaldiçoar quem as usa de forma errada.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2>O que é o Search Path?</h2>\r\n<p style=\"text-align: justify;\">Todos os projetos no Delphi possuem o Search Path que nada mais é do que uma coleção de caminhos (paths) onde o Delphi busca por arquivos que são referenciados dentro do projeto em cláusulas uses ou diretivas {$R} ou {$I}. O Search Path deve ser considerado como uma lista privada de caminhos acessíveis apenas pelo projeto onde ele for configurado.</p>\r\n<p style=\"text-align: justify;\">Suponha que você possui um sistema dividido em módulos executáveis. Neste caso você terá n projetos Delphi, e caso dentro deste sistema, projetos distintos utilizem uma mesma unit, esta unit pode ficar em um único local e ser referenciada por cada um dos projetos por meio do Search Path, configurado individualmente em cada um deles.</p>\r\n<p style=\"text-align: justify;\">Um exemplo de uso real disso é quando se desenvolve uma aplicação DataSnap, a qual tem no mínimo dois projetos, um para o cliente (Thin Client) e outro para a camada do meio (MiddleWare). Ambos os módulos fazem parte de um único sistema (separado pelo DataSnap), logo eles podem compartilhar arquivos entre si e estes arquivos devem ser referenciadas por meio do Search Path de cada um dos projetos (Thin Client e MiddleWare).</p>\r\n<p style=\"text-align: justify;\">Falando especificamente de units, tanto arquivos .dcu como arquivos .pas podem existir nos caminhos do Search Path, no entanto, como ele é uma coleção de <strong>caminhos de busca de um projeto específico</strong>, fica claro que <strong>arquivos listados no Search Path fazem parte do código-fonte do projeto</strong> onde ele for definido, logo, as units presentes nos caminhos de um Search Path <strong>devem ser arquivos .pas</strong>.</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/SearchPath.jpg\" alt=\"\" /></p>\r\n<p style=\"text-align: justify;\">Acima podemos ver, mais ao fundo, a caixa de diálogo Project Options, que pode ser acessada através do menu <strong>Project &gt; Options</strong> ou através da combinação de teclas <strong>Shift+Ctrl+F11</strong>. A localização da configuração do Search Path varia de acordo com a versão do Delphi. A imagem mostra a versão do Delphi XE5, mas se seu Delphi não apresentar a caixa de diálogo como na imagem, certamente será fácil de achar esta configuração.</p>\r\n<p style=\"text-align: justify;\">Clicando no botão de reticências vai exibir o editor do Search Path, o qual, na imagem, mostra 3 caminhos. Ao compilar este projeto estes 3 caminhos serão vasculhados em busca de arquivos referenciados em cláusulas uses e diretivas {$R} e {$I}.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">O papel do Search Path nos pacotes</h2>\r\n<p style=\"text-align: justify;\">O Search Path parece ser bem útil para organização de projetos, no entanto, no tocante aos pacotes especificamente, seu uso deve ser limitado apenas para indicar caminhos com arquivos .res, .dcr, .rc, .dfm e .inc. O motivo disso é que o Delphi não permite que pacotes distintos contenham units compartilhadas entre si.</p>\r\n<p style=\"text-align: justify;\">Se você usar o Search Path para compartilhar units entre pacotes distintos, duas coisas vão acontecer. No caso mais brando será emitido um aviso ao compilar, e no caso mais grave seu pacote não será compilado ou não poderá ser carregado pelo Delphi.</p>\r\n<p style=\"text-align: justify;\">No primeiro caso, a referência indireta por meio de Search Path vai gerar o seguinte aviso:</p>\r\n<blockquote>\r\n<p style=\"text-align: center;\">[dcc32 Warning] MeuPacote.dpk(88): W1033 Unit \'UMinhaUnit\' implicitly imported into package \'MeuPacote\'</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">A ajuda do Delphi é clara a respeito deste aviso, quando diz \"<em>This message will help the programmer avoid violating the rule that a unit may not reside in more than one related package</em>\", ou seja, pacotes relacionados não podem conter referências às mesmas units. Um pacote está relacionado a outro quando, por exemplo, <strong>PackageB.bpl</strong> depende de <strong>PackageA.bpl</strong>, porque <strong>PackageB.bpl</strong> contém em sua cláusula requires uma referência a <strong>PackageA.dcp</strong>. Quando dois pacotes estão relacioados desta forma apenas um dos pacotes precisa conter diretamente todas as units, por exemplo, suponha que <strong>PackageA.bpl</strong> contenha as units <strong>Unit1.pas</strong>, <strong>Unit2.pas</strong> e <strong>Unit3.pas</strong>. Suponha que <strong>PackageB.bpl</strong> também precise destas units (todas ou algumas delas, não importa). Neste caso, é suficiente que <strong>PackageB.bpl</strong> contenha na sua cláusula requires uma referência a <strong>PackageA.dcp</strong> para que ele \"enxergue\" e use todas as units que existem em <strong>PackageA.bpl</strong>, sem necessidade de duplicar ou compartilhar units.</p>\r\n<p style=\"text-align: justify;\">O segundo problema vai acontecer se você carregar (instalar) um pacote e depois tentar compilar um outro pacote que tem units compartilhadas com o primeiro, o seguinte erro de compilação vai aparecer:</p>\r\n<blockquote>\r\n<p style=\"text-align: center;\">[dcc32 Error] MeuPacote.dpk(45): E2200 Package \'MeuOutroPacote\' already contains unit \'MinhUnit\'</p>\r\n</blockquote>\r\n<p>Caso você consiga compilar os dois pacotes com units compartilhadas entre si e carregar um deles, ao tentar carregar o segundo, o seguinte erro será exibido:</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/SearchPathErro.jpg\" alt=\"\" /></p>\r\n<p>Em suma, o Delphi vai dar um jeito de evitar que você faça esse tipo de coisa, portanto, vou deixar abaixo um aviso importante:</p>\r\n<blockquote>\r\n<p style=\"text-align: center;\"><span style=\"color: #ffff00;\">NÃO USE O SEARCH PATH PARA COMPARTILHAR UNITS ENTRE PACOTES. AO INVÉS DISSO, COLOQUE TODAS AS UNITS COMPARTILHÁVEIS EM UM DOS PACOTES E FAÇA COM QUE OUTROS PACOTES DEPENDAM DELE</span></p>\r\n</blockquote>\r\n<h2>O que é o Library Path?</h2>\r\n<p style=\"text-align: justify;\">O Library Path é uma configuração que afeta todos os projetos (configuração global) e sua definição é basicamente a mesma do Search Path. Ele é, pois, uma coleção de paths onde o Delphi busca por arquivos. A diferença entre o Library Path e o Search Path é apenas quanto aos tipos de units que devem ser encontradas em cada um deles e quanto a sua especificidade.</p>\r\n<p style=\"text-align: justify;\">Quanto aos tipos de units, enquanto no Search Path devemos ter apenas units não compiladas (.pas), no Library Path devemos ter apenas units compiladas (.dcu). A especificidade refere-se ao quão as units estão relacionadas ao projeto. Observe a imagem a seguir:</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/ordemdebusca.png\" width=\"400\" height=\"400\" /></p>\r\n<p style=\"text-align: justify;\">Nesta imagem, a busca por units se dá no sentido das setas. Inicialmente se busca dentre as units do próprio projeto, em seguida se tenta encontrar as units no Search Path e por fim no Library Path. <strong>Units que pertencem diretamente ao projeto devem ser exclusivamente arquivos .pas</strong>, pois estes arquivos podem ser editados por nós e compilados posteriormente para gerar o arquivo final de nosso projeto (.exe, .dll, .bpl, etc.).</p>\r\n<p style=\"text-align: justify;\"><strong>Units que estão no Search Path também devem ser arquivos .pas, e também pertencem ao projeto, pois o Search Path é uma configuração específica de cada projeto</strong>, a diferença é que as units acessadas via Search Path também podem ser usadas por outros projetos em um sistema que utiliza vários módulos (várias DLLs, vários executáveis, etc.). Faz sentido que projetos distintos façam uso de units em comum, quando estas units compartilharem código que pertença exclusivamente a estes projetos, seja por conterem regras de negócios específicas, seja por fazerem parte de algum framework customizado para alguns projetos apenas. Estas units, portanto, podem ser colocadas em diretórios que estão fora da hierarquia de diretórios dos projetos e poderão ser encontradas via Search Path.</p>\r\n<p style=\"text-align: justify;\"><strong>Units que estão no Library Path devem ser exclusivamente arquios .dcu</strong>. Esta é a dica de ouro que justifica a existência de todo este artigo. Observe novamente a tela de edição do Library Path:</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/LibraryPath.jpg\" alt=\"\" /></p>\r\n<p style=\"text-align: justify;\">O primeiro path referenciado no Library Path informa ao Delphi onde encontrar todas as units compiladas (.dcu) de sistema. Units de sistema são units do próprio Delphi, que nós usamos frequentemente, tais como StdCtrls, SysUtils, StrUtils, dentre outras. Se você remover este path o Delphi vai parar de funcionar. No caminho indicado, caso você tenha curiosidade, todos os arquivos presentes são binários e lá, além de encontrarmos arquivos .dcu, também encontraremos alguns arquivos .dfm.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">O Library Path, claro, também funciona apontando para caminhos com arquivos .pas, no entanto, por esta ser uma configuração global, não faz sentido que projetos completamente distintos compilem esses arquivos .pas toda vez, que é justamente o que acontece quando arquivos .pas existem no Library Path.</p>\r\n<p style=\"text-align: justify;\">Isso mesmo, arquivos de código-fonte .pas encontrados via Library Path são compilados separadamente por cada projeto que fizer referência a suas units, e estas units são salvas no <strong>Unit Output Directory</strong> (veja mais adiante neste artigo) do projeto, dando a falsa impressão de que aquela unit pertence ao projeto diretamente, quando na verdade ela deveria ter sido apenas referenciada de forma binária indiretamente, para apenas ser usada na fase de linking de um projeto.</p>\r\n<p style=\"text-align: justify;\">Units encontradas via Library Path não precisam ser modificadas e consequentemente não precisam ser compiladas, além disso, elas podem ser usadas por quaisquer de nossos projetos, portanto, elas não podem estar ligadas a projetos específicos, como acontece com o Search Path.</p>\r\n<p style=\"text-align: justify;\">Quando o Library Path está configurado corretamente, nenhuma de suas units será incluída no Unit Output Directory de nenhum projeto e consequentemente projetos diferentes não conterão sua própria versão de um .dcu que deveria ser único, por nunca precisar sofrer alterações por ser genérico (utilizável em vários projetos diferentes).</p>\r\n<p style=\"text-align: justify;\">O fato de encontrarmos apenas units compiladas no Library Path nos leva a concluir que em algum momento elas foram compiladas, portanto, units presentes no Library Path foram criadas durante a compilação de algum pacote, logo, normalmente os caminhos existentes no Library Path pertencem, de uma forma ou de outra, a pacotes. </p>\r\n<hr class=\"system-pagebreak\" title=\"Outros paths que o Delphi considera\" alt=\"Outros paths que o Delphi considera\" />\r\n<h2>O que é o Browsing Path?</h2>\r\n<p style=\"text-align: justify;\">O Browsing Path é uma configuração global que nada mais é do que uma lista de diretórios onde nós encontramos os códigos-fonte correspondentes de units que estão disponíveis para compilação apenas em formato binário. Se você configurou corretamente o seu Library Path para apontar apenas para arquivos .dcu, vai notar que, dentro de algum projeto, ao tentar acessar uma unit disponível no Library Path via CTRL+ENTER, ou executar um CTRL+Clique em um identificador declarado numa destas units, o Delphi vai emitir um aviso igual ao da imagem abaixo (ou mostrará uma caixa de diálogo padrão \"Open File\"):</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/Browsing.jpg\" alt=\"\" /></p>\r\n<p style=\"text-align: justify;\"><strong>O Browsing Path informa à IDE onde estão os arquivos .pas com o intuito apenas de abrí-los, e não de compilá-los!</strong> O Browsing Path pode até ficar vazio, no entanto você não terá a facilidade de navegar (browse) facilmente através das units. Se você quiser ter essa facilidade enquanto estiver trabalhando, certifique-se de que no Browsing Path estejam incluídos todos os diretórios que contém os arquivos .pas correspondentes aos arquivos .dcu encontrados via Library Path.</p>\r\n<h2>O que é o Package Output Directory?</h2>\r\n<p style=\"text-align: justify;\">O Package Output Directory é uma configuração global na qual é definido o caminho padrão onde o Delphi salva e encontra arquivos BPL. Ao compilar um pacote o arquivo BPL gerado é posto sempre neste diretório. É possível alterar o local de salvamento do BPL nas opções específicas do projeto de um pacote (arquivo .dpk), por meio da configuração \"Output Directory\" (veja mais adiante), mas isso é totalmente desencorajado! O correto é sempre definir o Package Output Directory e manter o Output Directory dos projetos de pacotes sempre em branco.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2>O que é o DCP Output Directory?</h2>\r\n<p style=\"text-align: justify;\">O DCP Output Directory é uma configuração global e local (a nível de projeto) na qual é definido o caminho padrão onde o Delphi salva e encontra arquivos DCP. Ao compilar um pacote o arquivo DCP gerado é posto sempre neste diretório. É possível alterar o local de salvamento do DCP nas opções específicas do projeto de um pacote (arquivo .dpk), por meio da configuração de mesmo nome (DCP Output Directory), mas isso é totalmente desencorajado! O correto é sempre definir o DCP Output Directory global e manter o DCP Output Directory dos projetos de pacotes sempre em branco.</p>\r\n<h2>O que é o Output Directory?</h2>\r\n<p style=\"text-align: justify;\">O Output Directory é uma configuração local de cada projeto e serve para indicar o local onde os binários finalizados serão salvos. Um binário finalizado é o resultado final de uma compilação. Para projetos comuns este binário pode ser um executável ou uma dll. Para projetos de pacotes são gerados dois binários, um é o BPL e o otro é o DCP. A recomendação é que, em projetos comuns, o Output Directory seja sempre configurado como um diretório que esteja preferencialmente dentro da hierarquia do projeto e para projetos de pacotes a recomendação é que esta configuração seja deixada em branco, de forma que os arquivos BPL sejam gerados no local especificado pela configuração global \"Package Output Directory\".</p>\r\n<h2>O que é o Unit Output Directory?</h2>\r\n<p style=\"text-align: justify;\">O Unit Output Directory é uma configuração local de cada projeto e serve para indicar o local onde as units compiladas (arquivos .dcu) serão salvas. Apenas para projetos de pacotes, o Unit Output Directory deve ser incluído no Library Path a fim de que outros projetos que utilizem tais units as encontrem. Em outras palavras, quando instalamos componentes construídos de forma correta, sempre é necessário incluir no Library Path o caminho definido no Unit Output Directory do projeto do pacote, de forma que projetos que fazem uso destes componentes compilem sem problemas.</p>\r\n<hr class=\"system-pagebreak\" title=\"Paths e tipos de arquivo (resumo)\" alt=\"Paths e tipos de arquivo (resumo)\" />\r\n<h2>Paths e tipos de arquivo (resumo)</h2>\r\n<p style=\"text-align: justify;\">Abaixo está uma tabela que resume quais arquivos podem existir em cada um dos paths que o Delphi considera. Obviamente caso seu projeto não utilize alguns dos arquivos mencionados, não significa que você precisa usá-los, mas caso faça uso deles, esta tabela será sua grande amiga daqui pra frente. Eu não vou chamar esta tabela de recomendação pessoal, porque eu tenho visto muita gente fazendo uso dos paths de forma totalmente errada, portanto, eu considero a minha abordagem como um <strong>guia definitivo para minimizar problemas</strong>. Tem horas que, para o bem maior, a gente não pode ser um \"falso modesto\". Use a tabela ou continue fazendo errado. A escolha é sua</p>\r\n<div class=\"autooverflowx\">\r\n<table>\r\n<thead>\r\n<tr>\r\n<th style=\"width: 34%;\"> </th>\r\n<th style=\"width: 6%;\">.pas</th>\r\n<th style=\"width: 6%;\">.dcu</th>\r\n<th style=\"width: 6%;\">.res</th>\r\n<th style=\"width: 6%;\">.rc</th>\r\n<th style=\"width: 6%;\">.dcr</th>\r\n<th style=\"width: 6%;\">.dfm</th>\r\n<th style=\"width: 6%;\">.inc</th>\r\n<th style=\"width: 6%;\">.bpl</th>\r\n<th style=\"width: 6%;\">.dcp</th>\r\n<th style=\"width: 6%;\">.exe</th>\r\n<th style=\"width: 6%;\">.dll</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n<td>Search Path</td>\r\n<td>X *</td>\r\n<td> </td>\r\n<td>X</td>\r\n<td>X</td>\r\n<td>X</td>\r\n<td>X</td>\r\n<td>X</td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n</tr>\r\n<tr>\r\n<td>Library Path</td>\r\n<td> </td>\r\n<td>X</td>\r\n<td>X **</td>\r\n<td> </td>\r\n<td> </td>\r\n<td>X **</td>\r\n<td>X **</td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n</tr>\r\n<tr>\r\n<td>Browsing Path</td>\r\n<td>X</td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n</tr>\r\n<tr>\r\n<td>Package Output Directory</td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td>X</td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n</tr>\r\n<tr>\r\n<td>DCP Output Directory</td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td>X</td>\r\n<td> </td>\r\n<td> </td>\r\n</tr>\r\n<tr>\r\n<td>Output Directory</td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td>X</td>\r\n<td>X</td>\r\n</tr>\r\n<tr>\r\n<td>Unit Output Directory</td>\r\n<td> </td>\r\n<td>X</td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n<td> </td>\r\n</tr>\r\n</tbody>\r\n<caption>* Este arquivo só deve ser incluído no Search Path caso você esteja compilando um projeto normal. O Search Path não deve ser usado para referenciar units em projetos de pacotes porque o Delphi não gosta de units com o mesmo nome em pacotes (BPLs) distintos.<br /> <br />** Este arquivo só deve ser incluído no Library Path caso ele seja requerido durante a compilação de outros projetos. No caso dos arquivos res e inc, se eles forem utilizados apenas pelo pacote no momento da geração do BPL, eles não precisam ser incluídos no Library Path. No caso dos arquivos dfm, se eles representarem telas que são acessadas apenas em tempo de projeto, tais como telas de editores de propriedade e de componentes, eles não precisam ser incluídos no Library Path</caption></table>\r\n</div>\r\n<hr class=\"adsensesnippet\" />\r\n<h2>Proposta de organização de projetos e pacotes</h2>\r\n<p style=\"text-align: justify;\">Organização é tudo, então resolvi terminar esse artigo com uma proposta de organização de pastas de projetos que vai facilitar sua vida, caso você decida começar a usar os paths da forma certa.</p>\r\n<h2>Para projetos</h2>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/projeto.png\" alt=\"\" /></p>\r\n<p><em><strong>Detalhes</strong></em></p>\r\n<ul>\r\n<li style=\"text-align: justify;\"><strong>Meu Projeto\\bin</strong> - Arquivo binário finalizado. Configure esta pasta como <strong>Output Directory</strong> nas propriedades do projeto<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Projeto\\bin\\dcu</strong> - Units compiladas. Configure esta pasta como <strong>Unit Output Directory</strong>, nas propriedades do projeto<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Projeto\\dev</strong> - Arquivos de suporte relacionados ao projeto, mas que não são compiláveis<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Projeto\\dev\\doc</strong> - Arquivos de documentação a respeito do projeto<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Projeto\\prj</strong> - Arquivo .dpr do projeto e todos os arquivos que forem criados automaticamente pelo Delphi<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Projeto\\res</strong> - Arquivos de recurso que são utilizados pelo projeto na hora da compilação do mesmo. Podem ser arquivos .res, imagens, ícones ou qualquer outro tipo de arquivo que é carregado pelo projeto e é incluído no binário finalizado, isso inclui textos, scripts, etc. Você pode incluir esta pasta no <strong>Search Path</strong> do projeto a fim de facilitar a referência dos recursos<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Projeto\\src</strong> - Arquivos de código-fonte .pas exclusivos deste projeto</li>\r\n</ul>\r\n<p><em><strong>Observações</strong></em></p>\r\n<ul>\r\n<li style=\"text-align: justify;\">Se seu projeto tiver mais de um módulo ele conterá mais de um arquivo de projeto (.dpr). Todos estes arquivos deverão estar em <strong>Meu Projeto\\prj</strong>. Você pode, se quiser, colocar cada arquivo de projeto em uma pasta separada abaixo de <strong>prj</strong> a fim de separar cada projeto. Você pode também, neste caso, diretamente em <strong>Meu Projeto\\prj</strong>, colocar o arquivo de grupo de projetos (.bdsgroup ou .groupproj), o qual vai aglutinar todos os projetos de módulos individuais de forma que você possa ter acesso simplificado a partir da IDE a todos os projetos<br /><br /></li>\r\n<li style=\"text-align: justify;\">Se seu projeto tiver mais de um módulo ele conterá fontes de mais de um projeto. Todos estes arquivos deverão estar em Meu Projeto\\src, mas você pode, se quiser, criar pastas abaixo de <strong>Meu Projeto\\src</strong> a fim de separar os códigos-fonte dos módulos<br /><br /></li>\r\n<li style=\"text-align: justify;\">Se mais de um módulo utilizar um mesmo código-fonte .pas, crie a pasta <strong>Meu Projeto\\lib</strong> e dentro desta coloque tais arquivos. Em seguida, em cada projeto, inclua no <strong>Search Path</strong> a pasta Meu Projeto\\lib</li>\r\n</ul>\r\n<h2>Para pacotes</h2>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000104/pacote.png\" alt=\"\" /></p>\r\n<p><em><strong>Detalhes</strong></em></p>\r\n<ul>\r\n<li style=\"text-align: justify;\"><strong>Meu Pacote\\dcu</strong> - Units compiladas. Configure esta pasta como <strong>Unit Output Directory</strong>, nas propriedades do projeto e inclua ela no <strong>Library Path</strong> do Delphi<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Pacote\\dev</strong> - Arquivos de suporte relacionados ao pacote, mas que não são compiláveis<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Pacote\\dev\\doc</strong> - Arquivos de documentação a respeito do pacote<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Pacote\\prj</strong> - Nesta pasta deve haver uma subpasta para cada versão de Delphi na qual este pacote é utilizável e dentro de cada uma destas subpasta deve ser colocado o arquivo de projeto do pacote específico daquela versão de Delphi (arquivo .dpk)<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Pacote\\res</strong> - Arquivos de recurso que são utilizados pelo pacote na hora da compilação do mesmo. Podem ser arquivos .res, .dcr, imagens, ícones ou qualquer outro tipo de arquivo que é carregado pelo projeto do pacote e é incluído no binário finalizado (.bpl), isso inclui textos, scripts, etc. Você pode incluir esta pasta no <strong>Search Path</strong> do projeto do pacote a fim de facilitar a referência dos recursos. Ela também poderá ser incluída no <strong>Library Path</strong> dependendo da interpretação de seu conteúdo. Por favor, veja as observações mais adiante<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Pacote\\res\\dfm</strong> - Arquivos de formulário exclusivamente. Por favor, veja as observações a seguir<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Meu Pacote\\src</strong> - Arquivos de código-fonte .pas exclusivos deste pacote</li>\r\n</ul>\r\n<p><em><strong>Observações</strong></em></p>\r\n<ul>\r\n<li style=\"text-align: justify;\">Não configure em hipótese alguma a propriedade <strong>Output Directory</strong> do projeto do pacote. Verifique-a e <strong>mantenha-a em branco</strong><br /><br /></li>\r\n<li style=\"text-align: justify;\">Não configure em hipótese alguma a propriedade <strong>DCP Output Directory</strong> do projeto do pacote. Verifique-a e <strong>mantenha-a em branco</strong><br /> </li>\r\n<li style=\"text-align: justify;\">Na pasta <strong>Meu Pacote\\res</strong> também podem ser incluídos arquivos de recurso que serão utilizados por outros projetos que utilizem o seu pacote. Para entender melhor, devemos considerar que existem dois pontos de vista: ao compilar um pacote e ao compilar um projeto que use seu pacote. Diante destes dois pontos de vista, devemos entender que:\r\n<ul>\r\n<li style=\"text-align: justify;\"><strong>Quando você está desenvolvendo e compilando o seu pacote</strong>, ele está em tempo de projeto, portanto arquivos existentes na pasta <strong>Meu Pacote\\res</strong> poderão ser usados para a geração do BPL final e esta pasta deverá estar listada no <strong>Search Path</strong> do projeto do pacote</li>\r\n<li style=\"text-align: justify;\">No momento em que você compila um pacote e ele é carregado pelo Delphi, ele se torna algo em execução, logo, seu pacote estará em tempo de execução e não mais em tempo de projeto</li>\r\n<li style=\"text-align: justify;\"><strong>Ao compilar um projeto que use seu pacote</strong>, a pasta <strong>Meu Pacote\\res</strong>, deverá estar listada no <strong>Library Path</strong> <span style=\"text-decoration: underline;\">APENAS</span> se ela contiver recursos que precisarão estar disponíveis para o projeto sendo compilado. Imagens e ícones são um bom exemplo disso</li>\r\n</ul>\r\n</li>\r\n<li style=\"text-align: justify;\">A pasta <strong>Meu Pacote\\res\\dfm</strong> tem uma função importante, mas negligenciada. Como se sabe, arquivos .dfm por padrão sempre são salvos juntamente com seu arquivo .pas correspondente. Suponha que seu pacote esteja sendo carregado pelo Delphi e suponha que você esteja desenvolvendo algo que use units de seu pacote. Suponha também que você esteja referenciando alguma unit que contenha um .dfm associado. Neste caso o .dfm precisará estar disponível para seu projeto sendo compilado e para que ele saiba onde estão estes arquivos .dfm eles precisam estar listados no <strong>Library Path</strong> obviamente, mas, você <strong>NÃO PODERÁ</strong> incluir este path no Library Path porque ele contém units não compiladas (arquivos .pas) e isso seria <strong>ERRADO</strong>. Existem duas formas de resolver este problema. A forma clássica é copiar os arquivos .dfm que estão na pasta <strong>Meu Pacote\\src</strong> para a mesma pasta onde os arquivos .dcu são gerados, ou seja, <strong>Meu Pacote\\dcu</strong>. Eu não gosto dessa forma porque eu convencionei que na pasta <strong>Meu Pacote\\dcu</strong> devem existir apenas units compiladas (arquivos .dcu). Para resolver esse problema conceitual, já que arquivos .dfm são arquivos de recurso e não units compiladas, eu crio a pasta <strong>Meu Pacote\\res\\dfm</strong>, copio todos os arquivos .dfm que estão na pasta <strong>Meu Pacote\\src</strong> para lá e em seguida eu adiciono <strong>Meu Pacote\\res\\dfm</strong> ao Library Path. Se eu criar um novo formulário eu preciso apenas me lembrar de copiar o arquivo .dfm para a pasta correta. Uma variação dessa forma, que exige um controle maior, é copiar APENAS os arquivos .dfm que efetivamente precisarão ser vistos fora do projeto do pacote. A desvantagem dessa forma de trabalho com arquivos .dfm é que toda vez que você alterar um formulário, precisará se lembrar de copiar o arquivo .dfm para a pasta <strong>Meu Pacote\\res\\dfm</strong> </li>\r\n</ul>\r\n<h2>Conclusão</h2>\r\n<p style=\"text-align: justify;\">Esta artigo ficou maior do que eu esperava, mas foi necessário devido a grande utilidade do seu conteúdo. Se você leu tudo com cuidado será capaz de entender e notar quando um componente ou pacote está mal elaborado quanto a distribuição de seus artefatos e poderá organizá-lo da forma correta a fim de evitar problemas futuros. Se você é um desenvolvedor de componentes e leu este artigo, não existem mais desculpas para que seus componentes sejam distribuídos de forma errada. Estejam avisados! Eu torço para que as informações compartilhadas aqui atinjam o maior número de pessoas que usam o Delphi. Nós que utilizamos esta ferramenta precisamos conhecê-la a fundo a fim de podermos tirar o máximo de proveito da mesma.</p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-09-17 03:22:35',24,'','2020-07-13 03:22:05',24,0,'0000-00-00 00:00:00','2017-01-20 02:26:51','0000-00-00 00:00:00','{\"image_intro\":\"\",\"float_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000104\\/package.png\",\"float_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"article_layout\":\"\",\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_associations\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_page_title\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',217,53,'Delphi, Addicted 2 Delphi, BPL, DCP, DCU, Arquivo BPL, Arquivo DCP, Arquivo DCU, BPL File, BCP File, DCU File, Pacotes, Packages, Runtime Packages, Designtime Packages, Instalação de Pacotes, Package Installation, Instalação de Componentes, Component Installation, Search Path, Library Path, Browsing Path, Output Directory, Unit Output Directory, .bpl, .dcp, .dcu','Existem na web centenas de artigos que ensinam a criar componentes no Delphi e apesar de eu poder fazer um artigo abordando este tema no futuro, no momento eu prefiro mesmo é falar a respeito de algo que é tão importante quanto o próprio componente em si. Muitas pessoas acham que sabem instalar corretamente um componente, mas o fato de ter o componente funcionando, não significa que a instalação foi correta. Você agora deve estar se perguntando \"Ora, mas se está funcionando, porque eu preciso me preocupar?\". Continue lendo e descubra.',1,38342,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(105,289,'Desmistificando as Interfaces no Delphi','entendendo-interfaces','<p style=\"text-align: justify;\">Meu primeiro contato com interfaces foi lendo o livro \"A Bíblia do Delphi 5\". Nele eu aprendi que interfaces, grosso modo, são classes puramente abstratas (todos os seus métodos são virtuais e abstratos) e que ela obriga as classes que as suportam a implementar todos os seus métodos. Na época não consegui enxergar a utilidade das interfaces no Delphi. Desde então, tudo que eu precisei fazer eu fiz com simples classes. Já passei por alguns empregos e alguns projetos e nunca precisei usar de fato interfaces. Este artigo aborda as interfaces no Delphi, por isso é possível que você não possa aplicar o que está escrito aqui em outras linguagens. Se quer entender um pouco a respeito, continue lendo.</p>\r\n','\r\n<p style=\"text-align: justify;\">Até pouco tempo atrás eu não entendia as interfaces. Eu lia a respeito e não conseguia ver onde eu poderia usá-las. De fato, vivi até hoje e ainda vivo sem precisar usá-las não mais que uma vez perdida, mas calma, isso não as torna inúteis.</p>\r\n<p style=\"text-align: justify;\">Assim como qualquer coisa nessa vida, você não é obrigado a usar nenhuma técnica específica apenas porque alguém te falou que era genial. Você também não precisa usar alguma coisa porque ela está na moda, aliás, esse é o pior caso. É por isso que eu quero deixar aqui um pensamento que você, caro leitor, pode usar não apenas no contexto computacional, mas também na sua vida de uma forma geral:</p>\r\n<blockquote>\r\n<p style=\"text-align: center;\">A necessidade é o motorista que o conduz na estrada da resolução de um problema</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Depois dessa tentativa frustrada de ser intelectual eu me sinto no dever de falar de forma clara que <strong>você só precisa usar interfaces se ela for a única forma de conseguir resolver um problema ou se você quiser manter seu código mais organizado em alguma situação MUITO ESPECÍFICA</strong>. Ignore o tom drástico da frase anterior, trabalhar com interfaces nem sempre é complicado, mas seu uso desnecessário pode tornar seu código complexo sem necessidade, algo que evito a todo custo. Sou defensor ferrenho do <a href=\"https://pt.wikipedia.org/wiki/Keep_It_Simple\" rel=\"alternate\">KISS Principle</a> ;) E por falar nisso, chega de conversa fiada vamos ao que interessa.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2><a title=\"Esta é uma interpretação pessoal, sem rodeios e que sintetiza o conceito de interface ao máximo, de forma que um leigo possa entender. Não cito, propositalmente, qualquer definição técnica ou justificativa embasada em &quot;programação teórica&quot;. Aqui tudo é real!\" href=\"#\" rel=\"bookmark\">Para que serve uma interface?</a></h2>\r\n<p style=\"text-align: justify;\">Uma interface tem basicamente duas utilidades, as quais devem ser observadas antes de se decidir por utilizá-las. São elas:</p>\r\n<ul>\r\n<li style=\"text-align: justify;\"><strong>Obrigar a implementação de métodos</strong>. Usando a terminologia mais difundida, \"obrigar a implementação\" significa estabelecer uma espécie de \"contrato\", no qual uma classe se compromete a implementar todos os métodos da interface. Não é à toa que o termo correto utilizado entre classes e interfaces é \"A classe implementa a(s) interface(s)\", isso é absolutamente correto, já que, todos os métodos da interface precisam ser implementados OBRIGATORIAMENTE na classe que a implementa.<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Introduzir comportamentos adicionais a uma classe</strong>. Normalmente ao trabalhar com orientação a objetos, tudo é muito simples; uma classe herda de outra, que herda de outra, que herda de outra e assim sucessivamente. Você vive feliz com isso até perceber que vai precisar implementar uma classe que vai ter comportamentos conjuntos de duas outras classes distintas, classes que não pertencem a uma mesma hierarquia. No Delphi não existe herança múltipla, logo, ao usar interfaces é possível separar os comportamentos que serão compartilhados e fazer com que apenas uma classe herde de uma classe pai e implemente n interfaces, as quais vão introduzir os comportamentos adicionais requeridos.</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">Se você planeja usar interfaces, mas os motivos não forem um dos citados acima, então eu lamento informar que você só está introduzindo complexidade a seu código.</p>\r\n<p style=\"text-align: justify;\">Mesmo que você tenha motivos para usar interfaces, você <strong>não precisa</strong> usar interfaces para cada uma das classes que você tiver. Isso é estupidez! Como qualquer coisa em qualquer contexto, você só deve usar se for necessário em algum ponto. Um programa feito em Delphi pode conter classes simples, interfaces, e classes implementando interfaces. Tudo convivendo de forma harmoniosa.</p>\r\n<p style=\"text-align: justify;\">Por fim, ainda que você tenha motivos para usar interfaces é opção sua usá-las. <strong>Praticamente tudo que se faz com interfaces pode ser feito com classes simples</strong>. Interprete este artigo apenas como um guia muito básico que tem por objetivo desmistificar as interfaces, dando a você conhecimento sobre mais uma <em>feature</em> que o Object Pascal disponibiliza para você sem obrigá-lo a usar.</p>\r\n<hr class=\"system-pagebreak\" title=\"Um exemplo simples (?)\" alt=\"Um exemplo simples (?)\" />\r\n<h2>Um exemplo simples (?)</h2>\r\n<p style=\"text-align: justify;\">Gostaria de propor um exemplo hipotético simples, o qual vai fazer um uso básico de interfaces. Eu pretendo, com este exemplo, atingir o maior número de pessoas, fazendo-as entender o conceito de interface, por isso entenda que o que vou escrever a seguir não tem qualquer utilidade, fora a didática. Quero aproveitar para dizer também que este exemplo não tem o intuito de convencer ninguém a usar ou deixar de usar interfaces, ele apenas servirá para mostrar de forma nua e crua a diferença entre trabalhar com interfaces e trabalhar com classes puras.</p>\r\n<p style=\"text-align: justify;\">Suponha que você precise criar uma estrutura de classes que representem pessoas, levando em conta que existem dois tipos de pessoa (pessoa física e pessoa jurídica) que considere os seguintes atributos:</p>\r\n<ul>\r\n<li style=\"text-align: justify;\">nome</li>\r\n<li style=\"text-align: justify;\">razão social</li>\r\n<li style=\"text-align: justify;\">idade</li>\r\n<li style=\"text-align: justify;\">sexo</li>\r\n<li style=\"text-align: justify;\">data de nascimento</li>\r\n<li style=\"text-align: justify;\">cpf</li>\r\n<li style=\"text-align: justify;\">cnpj</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">Existem duas abordagens clássicas que usam apenas classes. Uma delas utiliza todos os atributos em apenas uma classe com um campo para identificar o tipo de pessoa e a outra cria classes distintas. A primeira abordagem é muito ruim, porque mistura atributos de tipos distintos de pessoas, sendo assim, vou considerar a segunda abordagem clássica, a qual se pode ver a seguir: </p>\r\n<pre class=\"language-pascal\"><code>unit UClasses;\r\n\r\ninterface\r\n\r\ntype\r\n TSexo = (sMasculino, sFeminino);\r\n TCPF = String[11];\r\n TCNPJ = String[14];\r\n\r\n TPessoa = class\r\n private\r\n function GetNome: String;\r\n procedure SetNome(const Value: String);\r\n function GetDataNascimento: TDateTime;\r\n procedure SetDataNascimento(const Value: TDateTime);\r\n public\r\n property Nome: String read GetNome write SetNome;\r\n property DataNascimento: TDateTime read GetDataNascimento write SetDataNascimento;\r\n end;\r\n\r\n TPessoaFisica = class(TPessoa)\r\n private\r\n function getCPF: TCPF;\r\n function GetSexo: TSexo;\r\n procedure setCPF(const Value: TCPF);\r\n procedure SetSexo(const Value: TSexo);\r\n public\r\n property Sexo: TSexo read GetSexo write SetSexo;\r\n property CPF: TCPF read getCPF write setCPF;\r\n end;\r\n\r\n TPessoaJuridica = class(TPessoa)\r\n private\r\n function GetCNPJ: TCNPJ;\r\n function GetNomeFantasia: String;\r\n procedure SetCNPJ(const Value: TCNPJ);\r\n procedure SetNomeFantasia(const Value: String);\r\n public\r\n property CNPJ: TCNPJ read GetCNPJ write SetCNPJ;\r\n property NomeFantasia: String read GetNomeFantasia write SetNomeFantasia;\r\n end;\r\n\r\nimplementation\r\n\r\n{As implementações dos métodos foram omitidas }\r\n\r\nvar\r\n PF: TPessoaFisica;\r\n PJ: TPessoaJuridica;\r\n\r\ninitialization\r\n PF := TPessoaFisica.Create;\r\n PJ := TPessoaJuridica.Create;\r\n\r\n PF.Nome := \'José\';\r\n PJ.Nome := \'José Maria ME\';\r\n\r\n PF.CPF := \'012344321712\';\r\n PJ.CNPJ := \'91221133110010\';\r\n\r\n PF.Free;\r\n PJ.Free;\r\nend.\r\n\r\n</code></pre>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">Como se pode ver, a abordagem clássica é bem limpa e simples. A classe TPessoa contém atributos que eu julgo comuns tanto para pessoas físicas como pessoas jurídicas. <strong>Nome</strong> seria o nome de batismo da pessoa física, ou a razão social de uma pessoa jurídica. <strong>DataNascimento</strong> seria a data de nascimento de uma pessoa física, ou a data de fundação de uma pessoa jurídica. Simplório e bobo, eu sei, mas funciona que é uma beleza. A interpretação da estruturação de objetos usando apenas classes é a seguinte:</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">TPessoa é uma classe geral que possui atributos de uma pessoa qualquer, enquanto TPessoaFisica e TPessoaJuridica são classes que definem pessoas de tipos distintos, cada uma delas com atributos específicos, porém ambas herdando de TPessoa, pois ambas são Pessoas</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Vejamos agora como ficaria esta estrutura caso você quisesse usar interfaces de qualquer jeito, por ser um purista de OO ou um seguidor de modinhas:</p>\r\n<pre class=\"language-pascal\"><code>unit UInterfaces;\r\n\r\ninterface\r\n\r\ntype\r\n TSexo = (sMasculino, sFeminino);\r\n TCPF = String[11];\r\n TCNPJ = String[14];\r\n\r\n IPessoa = interface\r\n function GetNome: String;\r\n procedure SetNome(const Value: String);\r\n function GetDataNascimento: TDateTime;\r\n procedure SetDataNascimento(const Value: TDateTime);\r\n\r\n property Nome: String read GetNome write SetNome;\r\n property DataNascimento: TDateTime read GetDataNascimento write SetDataNascimento;\r\n end;\r\n\r\n IPessoaFisica = interface(IPessoa)\r\n function getCPF: TCPF;\r\n function GetSexo: TSexo;\r\n procedure setCPF(const Value: TCPF);\r\n procedure SetSexo(const Value: TSexo);\r\n\r\n property Sexo: TSexo read GetSexo write SetSexo;\r\n property CPF: TCPF read getCPF write setCPF;\r\n end;\r\n\r\n IPessoaJuridica = interface(IPessoa)\r\n function GetCNPJ: TCNPJ;\r\n function GetNomeFantasia: String;\r\n procedure SetCNPJ(const Value: TCNPJ);\r\n procedure SetNomeFantasia(const Value: String);\r\n\r\n property CNPJ: TCNPJ read GetCNPJ write SetCNPJ;\r\n property NomeFantasia: String read GetNomeFantasia write SetNomeFantasia;\r\n end;\r\n\r\n TPessoa = class(TInterfacedObject,IPessoa)\r\n private\r\n function GetNome: String;\r\n procedure SetNome(const Value: String);\r\n function GetDataNascimento: TDateTime;\r\n procedure SetDataNascimento(const Value: TDateTime);\r\n end;\r\n\r\n TPessoaFisica = class(TPessoa,IPessoaFisica)\r\n private\r\n function getCPF: TCPF;\r\n function GetSexo: TSexo;\r\n procedure setCPF(const Value: TCPF);\r\n procedure SetSexo(const Value: TSexo);\r\n end;\r\n\r\n TPessoaJuridica = class(TPessoa,IPessoaJuridica)\r\n private\r\n function GetCNPJ: TCNPJ;\r\n function GetNomeFantasia: String;\r\n procedure SetCNPJ(const Value: TCNPJ);\r\n procedure SetNomeFantasia(const Value: String);\r\n end;\r\n\r\nimplementation\r\n\r\n{ As implementações dos métodos foram omitidas }\r\n\r\nvar\r\n PF: IPessoaFisica;\r\n PJ: IPessoaJuridica;\r\n\r\ninitialization\r\n PF := TPessoaFisica.Create;\r\n PJ := TPessoaJuridica.Create;\r\n\r\n PF.Nome := \'José\';\r\n PJ.Nome := \'José Maria ME\';\r\n\r\n PF.CPF := \'012344321712\';\r\n PJ.CNPJ := \'91221133110010\';\r\n\r\n { Não é necessário liberar da memória porque interfaces possuem contagem de\r\n referência e são liberadas automaticamente pelo Delphi quando as variáveis\r\n saem do escopo }\r\nend.</code></pre>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">O código se tornou bem maior e por isso merece algumas explicações. Para começar, foram criadas 3 interfaces e 3 classes. Cada uma das 3 interfaces define métodos set/get e propriedades correspondentes que usam estes métodos, os quais não são implementados nas interfaces. Os métodos e propriedades em cada interface são coerentes com aquela interface, isto é, eles fazem sentido apenas para aquele interface, por exemplo, a interface <strong>IPessoaFisica</strong> contém a propriedade <strong>CPF</strong>, a qual só faz sentido para uma pessoa física, já a interface <strong>IPessoaJuridica</strong> contém a propriedade <strong>NomeFantasia</strong>, a qual, do mesmo modo, só faz sentido para uma pessoa jurídica. Cada uma dessas interfaces herda da interface <strong>IPessoa</strong>, a qual possui duas propriedades (e seus métodos set/get correspondentes) que eu julguei serem gerais, por poderem ser aplicados a qualquer pessoa, seja ela física ou jurídica. A herança de interfaces é semelhante a herança de classes, só que mais simples: interfaces que herdam de uma outra interface possuem todos os métodos e propriedades somados. IPessoaFisica possui seus métodos e propriedades, mais os métodos e propriedades de IPessoa, por exemplo.</p>\r\n<p style=\"text-align: justify;\">A classe TPessoa herda de <a title=\"Cada interface básica herda de &lt;b&gt;IInterface&lt;/b&gt; a qual possui alguns métodos que precisam ser implementadas obrigatoriamente. Quando uma classe implementa uma interface qualquer, ela precisa implementar todos os métodos desta interface, incluindo todos os métodos das interfaces das quais esta interface herda e isso inclui os métodos existentes em IInterface. TInterfacedObject é uma classe que já implementa os métodos de IInterface, de forma que você não precise se preocupar em implementá-los, já que eles não fazem parte da sua programação. IInterface está para as interfaces assim como &lt;b&gt;TObject&lt;/b&gt; está para as classes\" href=\"#\" rel=\"bookmark\">TInterfacedObject</a> ao mesmo tempo em que implementa a interface IPessoa, isso significa que nós somos obrigados a implementar em TPessoa todos os métodos existentes em IPessoa, de forma a satisfazer a interface. Isso exemplifica a primeira justificativa de uso de uma interface (Obrigar a implementação de métodos). TPessoaFisica e TPessoaJuridica ambos herdam de TPessoa e cada um deles implementa sua interface específica (IPessoaFisica e IPessoaJuridica), exemplificando a segunda justificativa de uso de uma interface (Introduzir comportamentos adicionais a uma classe). Aliás, vale salientar que não precisamos redeclarar as propriedades que foram definidas nas interfaces, precisamos apenas implementar os seus métodos set/get. A interpretação da estruturação de objetos usando interfaces é a seguinte:</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">TPessoa é uma classe que precisa implementar alguns métodos de forma obrigatória, ao passo que TPessoaFisica e TPessoaJuridica são pessoas, pois herdam de TPessoa, porém, cada uma delas tem características especiais que as diferem uma da outra, por isso, TPessoaFisica implementa IPessoaFisica e TPessoaJuridica implementa IPessoaJuridica. Ao final a classe TPessoaFisica define uma pessoa com características físicas, enquanto TPessoaJuridica define uma pessoa com caracterísiticas jurídicas</p>\r\n</blockquote>\r\n<hr class=\"system-pagebreak\" title=\"Um exemplo a não ser seguido...\" alt=\"Um exemplo a não ser seguido...\" />\r\n<h2>Um exemplo a não ser seguido...</h2>\r\n<p style=\"text-align: justify;\">O exemplo anterior demonstra como foi introduzida mais complexidade ao código por conta do uso de interfaces. Se essa introdução de complexidade vai ou não trazer benefícios, isso já é um assunto para outras discussões que, normalmente, não levam a lugar nenhum, pois, no mundo real o nosso objetivo é satisfazer o cliente e ele não quer saber como fazemos o código, ele só quer que esse código funcione bem, e tanto o uso de interfaces como o de classes puras gera o mesmo resultado final. Gostaria então de propor um segundo problema apenas para demonstrar que interfaces não são a solução para tudo.</p>\r\n<p style=\"text-align: justify;\">Suponha que você precise implementar uma estrutura de objetos para representar programadores e as linguagens com as quais ele trabalha, bem como o nível de conhecimento que um mesmo programador tem para cada uma delas. Suponha ainda que para cada linguagem o programador possua um conjunto bem específico de competências e características as quais só fazem sentido para aquela linguagem, por exemplo, programadores Delphi podem escolher trabalhar com VCL ou FMX, já programadores Java, normalmente usam um servidor web que pode ser TomCat, JBoss ou WebLogic (dentre outros). Isso deve ser observado para cada linguagem considerada, ou seja, existe uma variação muito grande de combinações de linguagens e características únicas de cada uma delas que cada programador pode utilizar ou não.</p>\r\n<p style=\"text-align: justify;\">A primeira coisa que pode vir a sua mente talvez seja usar uma lista ou uma coleção na qual você poderia adicionar as linguagens e incluir, via texto, as características únicas de cada uma delas, mas convenhamos, usar texto, mesmo que esse texto seja um JSON, para atribuir características únicas a um elemento de uma coleção, não parece nem um pouco prático e é sim uma bela gambiarra.</p>\r\n<p style=\"text-align: justify;\">A forma clássica de fazer isso usando simples classes seria a criação de várias classes que contenham como atributos as classes específicas de cada linguagem. Neste caso, uma classe <strong>TProgramadorDJ</strong> (Delphi e Java), por exemplo, conteria duas propriedades; uma seria <strong>LinguagemDelphi: TLinguagemDelphi</strong> e outra seria <strong>LinguagemJava: TLingagemJava</strong>. Com essa simples construção eu tenho uma classe que suporta Delphi e Java por meio de propriedades distintas. Tudo perfeito, limpo e sem gambiarras, mas como nossa intenção é ver esta implementação usando interfaces, vamos a ela:</p>\r\n<pre id=\"linguagens\" class=\"line-numbers language-pascal\"><code>unit UInterfaces;\r\n\r\ninterface\r\n\r\ntype\r\n TNivelDeAprendizado = (ndaBasico, ndaIntermediario, ndaAvancado, ndaExpert);\r\n TPlataformasSuportadas = (psDesktop, psWeb, psMobile, psDesktopWeb, psDesktopMobile, psWebMobile, psWebMobileDesktop);\r\n\r\n TProgramador = class(TInterfacedObject)\r\n private\r\n FNome: String;\r\n FIdade: Byte;\r\n public\r\n property Nome: String read FNome write FNome;\r\n property Idade: Byte read FIdade write FIdade;\r\n end;\r\n\r\n ILinguagem = interface\r\n function GetNivelDeAprendizado: TNivelDeAprendizado;\r\n procedure SetNivelDeAprendizado(PNivelDeAprendizado: TNivelDeAprendizado);\r\n\r\n property NivelDeAprendizado: TNivelDeAprendizado read GetNivelDeAprendizado write SetNivelDeAprendizado;\r\n end;\r\n\r\n IDelphi = interface(ILinguagem)\r\n function GetUsaVCL: Boolean;\r\n procedure SetUsaVCL(PValue: Boolean);\r\n function GetUsaFMX: Boolean;\r\n procedure SetUsaFMX(PValue: Boolean);\r\n function GetOdeiaJava: Boolean;\r\n procedure SetOdeiaJava(PValue: Boolean);\r\n\r\n property UsaVCL: Boolean read GetUsaVCL write SetUsaVCL;\r\n property UsaFMX: Boolean read GetUsaFMX write SetUsaFMX;\r\n property OdeiaJava: Boolean read GetOdeiaJava write SetOdeiaJava;\r\n end;\r\n\r\n IPHP = interface(ILinguagem)\r\n function GetUsaUmFramework: Boolean;\r\n procedure SetUsaUmFramework(PValue: Boolean);\r\n function GetUsaOIIS: Boolean;\r\n procedure SetUsaOIIS(PValue: Boolean);\r\n function GetUsaOApache: Boolean;\r\n procedure SetUsaOApache(PValue: Boolean);\r\n\r\n property UsaUmFramework: Boolean read GetUsaUmFramework write SetUsaUmFramework;\r\n property UsaOIIS: Boolean read GetUsaOIIS write SetUsaOIIS;\r\n property UsaOApache: Boolean read GetUsaOApache write SetUsaOApache;\r\n end;\r\n\r\n IJava = interface(ILinguagem)\r\n function GetUsaJBoss: Boolean;\r\n procedure SetUsaJBoss(PValue: Boolean);\r\n function GetUsaWebLogic: Boolean;\r\n procedure SetUsaWebLogic(PValue: Boolean);\r\n function GetUsaTomCat: Boolean;\r\n procedure SetUsaTomCat(PValue: Boolean);\r\n function GetAchaQueJavaEhASolucaoPraTudo: Boolean;\r\n procedure SetAchaQueJavaEhASolucaoPraTudo(PValue: Boolean);\r\n\r\n property UsaJBoss: Boolean read GetUsaJBoss write SetUsaJBoss;\r\n property UsaWebLogic: Boolean read GetUsaWebLogic write SetUsaWebLogic;\r\n property UsaTomCat: Boolean read GetUsaTomCat write SetUsaTomCat;\r\n property AchaQueJavaEhASolucaoPraTudo: Boolean read GetAchaQueJavaEhASolucaoPraTudo write SetAchaQueJavaEhASolucaoPraTudo;\r\n end;\r\n\r\n TProgramadorCompleto = class(TProgramador,IDelphi,IJava,IPHP)\r\n private\r\n // Campos\r\n // IDelphi\r\n FNivelDeDelphi: TNivelDeAprendizado;\r\n FUsaVCL: Boolean;\r\n FUsaFMX: Boolean;\r\n FOdeiaJava: Boolean;\r\n // IJava\r\n FNivelDeJava: TNivelDeAprendizado;\r\n FUsaJBoss: Boolean;\r\n FUsaWebLogic: Boolean;\r\n FUsaTomCat: Boolean;\r\n FAchaQueJavaEhASolucaoPraTudo: Boolean;\r\n // IPHP\r\n FNivelDePHP: TNivelDeAprendizado;\r\n FUsaUmFramework: Boolean;\r\n FUsaOIIS: Boolean;\r\n FUsaOApache: Boolean;\r\n\r\n // Mapeamentos para desambiguação de métodos em ILinguagem\r\n // IDelphi\r\n function IDelphi.GetNivelDeAprendizado = GetNivelDeDelphi;\r\n function GetNivelDeDelphi: TNivelDeAprendizado;\r\n procedure IDelphi.SetNivelDeAprendizado = SetNivelDeDelphi;\r\n procedure SetNivelDeDelphi(PValue: TNivelDeAprendizado);\r\n // IJava\r\n function IJava.GetNivelDeAprendizado = GetNivelDeJava;\r\n function GetNivelDeJava: TNivelDeAprendizado;\r\n procedure IJava.SetNivelDeAprendizado = SetNivelDeJava;\r\n procedure SetNivelDeJava(PValue: TNivelDeAprendizado);\r\n // IPHP\r\n function IPHP.GetNivelDeAprendizado = GetNivelDePHP;\r\n function GetNivelDePHP: TNivelDeAprendizado;\r\n procedure IPHP.SetNivelDeAprendizado = SetNivelDePHP;\r\n procedure SetNivelDePHP(PValue: TNivelDeAprendizado);\r\n\r\n // Implementação dos métodos exclusivos de cada interface\r\n // IDelphi\r\n function GetUsaVCL: Boolean;\r\n procedure SetUsaVCL(PValue: Boolean);\r\n function GetUsaFMX: Boolean;\r\n procedure SetUsaFMX(PValue: Boolean);\r\n function GetOdeiaJava: Boolean;\r\n procedure SetOdeiaJava(PValue: Boolean);\r\n // IJava\r\n function GetUsaJBoss: Boolean;\r\n procedure SetUsaJBoss(PValue: Boolean);\r\n function GetUsaWebLogic: Boolean;\r\n procedure SetUsaWebLogic(PValue: Boolean);\r\n function GetUsaTomCat: Boolean;\r\n procedure SetUsaTomCat(PValue: Boolean);\r\n function GetAchaQueJavaEhASolucaoPraTudo: Boolean;\r\n procedure SetAchaQueJavaEhASolucaoPraTudo(PValue: Boolean);\r\n // IPHP\r\n function GetUsaUmFramework: Boolean;\r\n procedure SetUsaUmFramework(PValue: Boolean);\r\n function GetUsaOIIS: Boolean;\r\n procedure SetUsaOIIS(PValue: Boolean);\r\n function GetUsaOApache: Boolean;\r\n procedure SetUsaOApache(PValue: Boolean);\r\n public\r\n // Propriedades Exclusivas do Delphi\r\n property NivelDeDelphi: TNivelDeAprendizado read GetNivelDeDelphi write SetNivelDeDelphi;\r\n // Propriedades Exclusivas do Java\r\n property NivelDeJava: TNivelDeAprendizado read GetNivelDeJava write SetNivelDeJava;\r\n // Propriedades Exclusivas do PHP\r\n property NivelDePHP: TNivelDeAprendizado read GetNivelDePHP write SetNivelDePHP;\r\n end;\r\n\r\n TProgramadorWeb = class(TProgramador,IPHP,IJava)\r\n private\r\n // Campos\r\n // IJava\r\n FNivelDeJava: TNivelDeAprendizado;\r\n FUsaJBoss: Boolean;\r\n FUsaWebLogic: Boolean;\r\n FUsaTomCat: Boolean;\r\n FAchaQueJavaEhASolucaoPraTudo: Boolean;\r\n // IPHP\r\n FNivelDePHP: TNivelDeAprendizado;\r\n FUsaUmFramework: Boolean;\r\n FUsaOIIS: Boolean;\r\n FUsaOApache: Boolean;\r\n\r\n // Mapeamentos para desambiguação de métodos em ILinguagem\r\n // IJava\r\n function IJava.GetNivelDeAprendizado = GetNivelDeJava;\r\n function GetNivelDeJava: TNivelDeAprendizado;\r\n procedure IJava.SetNivelDeAprendizado = SetNivelDeJava;\r\n procedure SetNivelDeJava(PValue: TNivelDeAprendizado);\r\n // IPHP\r\n function IPHP.GetNivelDeAprendizado = GetNivelDePHP;\r\n function GetNivelDePHP: TNivelDeAprendizado;\r\n procedure IPHP.SetNivelDeAprendizado = SetNivelDePHP;\r\n procedure SetNivelDePHP(PValue: TNivelDeAprendizado);\r\n\r\n // Implementação dos métodos exclusivos de cada interface\r\n // IJava\r\n function GetUsaJBoss: Boolean;\r\n procedure SetUsaJBoss(PValue: Boolean);\r\n function GetUsaWebLogic: Boolean;\r\n procedure SetUsaWebLogic(PValue: Boolean);\r\n function GetUsaTomCat: Boolean;\r\n procedure SetUsaTomCat(PValue: Boolean);\r\n function GetAchaQueJavaEhASolucaoPraTudo: Boolean;\r\n procedure SetAchaQueJavaEhASolucaoPraTudo(PValue: Boolean);\r\n // IPHP\r\n function GetUsaUmFramework: Boolean;\r\n procedure SetUsaUmFramework(PValue: Boolean);\r\n function GetUsaOIIS: Boolean;\r\n procedure SetUsaOIIS(PValue: Boolean);\r\n function GetUsaOApache: Boolean;\r\n procedure SetUsaOApache(PValue: Boolean);\r\n public\r\n // Propriedades Exclusivas do Java\r\n property NivelDeJava: TNivelDeAprendizado read GetNivelDeJava write SetNivelDeJava;\r\n // Propriedades Exclusivas do PHP\r\n property NivelDePHP: TNivelDeAprendizado read GetNivelDePHP write SetNivelDePHP;\r\n end;\r\n\r\nimplementation\r\n\r\n{ A implementação de todos os métodos foi omitida }\r\n\r\nvar\r\n Carlos: TProgramadorCompleto;\r\n WebSon: TProgramadorWeb;\r\n\r\ninitialization\r\n Carlos := TProgramadorCompleto.Create;\r\n // Propriedades de TProgramador\r\n Carlos.Nome := \'Carlos Barreto Feitoza Filho\';\r\n Carlos.Idade := 38;\r\n // Propriedades em ILinguagem em cada interface específica\r\n Carlos.NivelDeDelphi := ndaExpert;\r\n Carlos.NivelDePHP := ndaIntermediario;\r\n Carlos.NivelDeJava := ndaBasico;\r\n // Propriedades em IDelphi\r\n (Carlos as IDelphi).UsaVCL := True;\r\n (Carlos as IDelphi).UsaFMX := False;\r\n (Carlos as IDelphi).OdeiaJava := True;\r\n // Propriedades em IJava\r\n (Carlos as IJava).UsaJBoss := True;\r\n (Carlos as IJava).UsaWebLogic := True;\r\n (Carlos as IJava).UsaTomCat := False;\r\n (Carlos as IJava).AchaQueJavaEhASolucaoPraTudo := False; // definitivamente NÃO!\r\n // Propriedades em IPHP\r\n (Carlos as IPHP).UsaUmFramework := False;\r\n (Carlos as IPHP).UsaOApache := True;\r\n (Carlos as IPHP).UsaOIIS := False;\r\n\r\n WebSon := TProgramadorWeb.Create;\r\n // Propriedades de TProgramador\r\n WebSon.Nome := \'WebSon Heverton Taurus Tiobe Portaluppi\';\r\n WebSon.Idade := 38;\r\n // Propriedades em ILinguagem em cada interface específica\r\n WebSon.NivelDePHP := ndaIntermediario;\r\n WebSon.NivelDeJava := ndaBasico;\r\n // Propriedades em IJava\r\n (WebSon as IJava).UsaJBoss := True;\r\n (WebSon as IJava).UsaWebLogic := True;\r\n (WebSon as IJava).UsaTomCat := True;\r\n (WebSon as IJava).AchaQueJavaEhASolucaoPraTudo := False;\r\n // Propriedades em IPHP\r\n (WebSon as IPHP).UsaUmFramework := True;\r\n (WebSon as IPHP).UsaOApache := True;\r\n (WebSon as IPHP).UsaOIIS := True;\r\n\r\n { Como as classes TProgramador* herdam de TProgramador, que herda de\r\n TInterfacedObject, seus objetos são automaticamente liberado da memória quando\r\n suas variáveis saem do escopo }\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Não se assuste meu caro, trabalhar com interfaces pode gerar mais código mesmo quando você não sabe o que está fazendo. Vou tentar explicar isso tudo. As linhas <a href=\"#linguagens.9-16\" rel=\"alternate\">9 a 16</a> definem uma a classe básica de programador, pois todo programador tem um nome e uma idade. As linhas <a href=\"#linguagens.18-23\" rel=\"alternate\">18 a 23</a> definem a interface básica de uma linguagem, pois para cada linguagem um programador possui um nível distinto de aprendizado. As linhas <a href=\"#linguagens.25-65\" rel=\"alternate\">25 a 65</a> definem as interfaces específicas de cada linguagem. Nelas podem ser definidos métodos e propriedades que fazem sentido apenas em cada linguagem. As linhas <a href=\"#linguagens.69-135\" rel=\"alternate\">67 a 135</a> definem a classe de um programador completo, o qual trabalha com todas as linguagens disponíveis. A classe <strong>TProgramadorCompleto</strong> herda de <strong>TProgramador</strong> (que contém nome e idade) e implementa cada uma das interfaces de linguagens de programação disponíveis. As linhas <a href=\"#linguagens.137-186\" rel=\"alternate\">137 a 186</a> definem uma classe que representa um programador web, o qual trabalha apenas com Java e PHP. A classe <strong>TProgramadorWeb</strong> herda de TProgramador e implementa apenas as interfaces <strong>IJava</strong> e <strong>IPHP</strong>, que são as linguagens que um programador web (neste exemplo) pode usar.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">Quero ressaltar algo que muitos de vocês não conhecem, chama-se <strong>cláusula de resolução de método</strong>. Ao escrever este exemplo eu criei uma interface básica ILinguagem, a qual possui uma propriedade (<strong>NivelDeAprendizado</strong>) e seus métodos set/get. As interfaces IDelphi, IJava e IPHP herdam de ILinguagem e, como sabemos, a herança de interfaces soma todos os métodos existentes tanto na interface atual como na interface \"pai\". Como, por exemplo, TProgramadorWeb implementa duas interfaces que tem um pai comum, um problema foi introduzido: como implementar um método que existe em duas interfaces distintas? A solução para isso é usar a cláusula de resolução de método, a qual dá um \"apelido\" a métodos que vem de interfaces distintas. As linhas <a href=\"#linguagens.154\" rel=\"alternate\">154</a>, <a href=\"#linguagens.156\" rel=\"alternate\">156</a>, <a href=\"#linguagens.159\" rel=\"alternate\">159</a> e <a href=\"#linguagens.161\" rel=\"alternate\">161</a> de TProgramadorWeb, por exemplo, fazem uso de cláusulas de resolução de método para diferenciar os métodos de mesmo nome existentes em interfaces distintas. Abaixo estão estas declarações:</p>\r\n<blockquote>\r\n<p style=\"text-align: center;\">function IJava.GetNivelDeAprendizado = GetNivelDeJava;</p>\r\n<p style=\"text-align: center;\">procedure IJava.SetNivelDeAprendizado = SetNivelDeJava;</p>\r\n<p style=\"text-align: center;\">function IPHP.GetNivelDeAprendizado = GetNivelDePHP;</p>\r\n<p style=\"text-align: center;\">procedure IPHP.SetNivelDeAprendizado = SetNivelDePHP;</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">A interpretação destas declarações é muito simples, por exemplo, a primeira linha (function IJava.GetNivelDeAprendizado = GetNivelDeJava) diz que a função <strong>GetNivelDeAprendizado</strong>, da interface IJava, será conhecida localmente (em TProgramadorWeb), por GetNivelDeJava. Após definir o apelido para cada método ambíguo, basta implementá-los, tal como foi feito nas linhas <a href=\"#linguagens.155\" rel=\"alternate\">155</a>, <a href=\"#linguagens.157\" rel=\"alternate\">157</a>, <a href=\"#linguagens.160\" rel=\"alternate\">160</a> e <a href=\"#linguagens.162\" rel=\"alternate\">162</a>. Isso resolve o problema da ambiguidade!</p>\r\n<p style=\"text-align: justify;\">As linhas <a href=\"#linguagens.192-194\" rel=\"alternate\">192 a 194</a> declaram duas variáveis dos dois tipos que definimos. As linhas <a href=\"#linguagens.197-217\" rel=\"alternate\">197 a 217</a> são um exemplo de como usar a classe TProgramadorCompleto, note que a criação do objeto é trivial (linha <a href=\"#linguagens.197\" rel=\"alternate\">197</a>), bem como a atribuição das duas propriedades herdadas de TProgramador (Nome e Idade, nas linhas <a href=\"#linguagens.199-200\" rel=\"alternate\">199 e 200</a>). As linhas <a href=\"#linguagens.202-204\" rel=\"alternate\">202 a 204</a> são propriedades ligadas aos apelidos definidos por meio das cláusulas de resolução de método. Ao atribuirmos um valor a cada uma destas propriedades, na verdade estamos atribuindo estes valores à propriedade NivelDeAprendizado de cada uma das interfaces que nossa classe implementa.</p>\r\n<p style=\"text-align: justify;\">A coisa começa a ficar estranha a partir de agora. As linhas <a href=\"#linguagens.206-217\" rel=\"alternate\">206 a 217</a>, são atribuições às propriedades específicas de cada interface. Mas porque é preciso fazer toda essa manobra para acessar as propriedades de cada interface? Primeiramente, o operador <strong>as</strong> do Delphi, basicamente realiza um typecast. Ao dizer, por exemplo, <strong>Carlos as IDelphi</strong>, estamos fazendo um typecast que permite acessar Carlos como se ele fosse IDelphi e já que a classe de Carlos (TProgramadorCompleto) implementa IDelphi, o typecast será bem sucedido. Uma classe que implementa várias interfaces, pode assumir a personalidade de cada uma destas interfaces usando-se o operador <strong>as</strong> do Delphi. </p>\r\n<p style=\"text-align: justify;\">Como a variável <strong>Carlos</strong> é do tipo TProgramadorCompleto, podemos acessar diretamente apenas métodos e propriedades definidas em TProgramadorCompleto ou, no caso, TProgramador, que é sua classe pai. É por isso que conseguimos acessar as propriedades <strong>Nome</strong>, <strong>Idade</strong>, <strong>NivelDeDelphi</strong>, <strong>NivelDeJava</strong> e <strong>NivelDePHP</strong>. Nós não precisaríamos realizar o typecast, caso usássemos diretamente os métodos Set/Get, os quais nós implementamos em TProgramadorCompleto, mas eu gosto de manter os métodos Set/Get privados, logo, a única forma de manipular as propriedades de interfaces específicas é por meio do typecast usando o operador <strong>as</strong> do Delphi. De forma resumida, para acessar uma interface implementada por uma classe, precisamos acessar a instância do objeto desta classe como se ela fosse a interface usando o operador <strong>as</strong>. \"AS\" vem do inglês e significa \"COMO\", logo, dizer \"Classe as Interface\" pode ser traduzido como \"Classe como Interface\" ou, para ficar mais claro, \"acessar Classe como se ela fosse a Interface\".</p>\r\n<p style=\"text-align: justify;\">Como se pode ver o uso de interfaces aglutina numa mesma classe os métodos e propriedades de várias interfaces. Não precisam existir subclasses numa classe que implementa interfaces no entanto, existe um conceito de <a href=\"http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/implementinginterfaces_xml.html\" rel=\"alternate\">delegação</a> no qual parece que estamos usando subclasses, mas a implementação de interfaces por delegação é mais complexa do que usar uma simples subclasse, logo eu não a estou considerando como vantagem aqui.</p>\r\n<hr class=\"system-pagebreak\" title=\"Conclusão\" alt=\"Conclusão\" />\r\n<h2>Conclusão</h2>\r\n<p style=\"text-align: justify;\">O Delphi é uma linguagem evoluída e que possui inúmeros conceitos avançados, incluindo as interfaces e classes com métodos virtuais abstratos. Algumas outras linguagens não possuem o conceito de método virtual abstrato e talvez por isso, nestas linguagens o uso de interfaces para implementar certos tipos de programação específica seja imprescindível. No Delphi, por outro lado (com algumas exceções) o uso de interfaces pode ser dispensado para garantir um código mais limpo e fácil de se entender.</p>\r\n<p style=\"text-align: justify;\">Se você costuma usar interfaces no seu código e está feliz com isso, tudo bem, mas se você nunca usou interfaces e pretende usar, meu conselho é que você busque outras fontes além deste artigo a fim de decidir se elas vão trazer vantagens reais, se você está apenas tentando seguir algum tipo de modinha ou se está tentando usar apenas por causa de alguma promessa futura (muito futura!) de escalabiliade ou manutenibilidade as quais eu, particularmente, acredito que não justificam o uso de interfaces no Delphi.</p>\r\n<p style=\"text-align: justify;\">Com toda sinceridade, até hoje eu só vi utilidade nas interfaces utilizando o <strong>Delphi</strong> quando eu precisei obrigar a implementação de certos métodos em componentes de conexão (conectores) do <strong>TUserControl</strong>, os quais possuem métodos comuns que precisam ser implementados de forma diferente para cada engine de conexão considerada, mas precisam, ao mesmo tempo, ser ligados a um componente principal por meio de uma propriedade única. Neste mesmo uso que eu faço eu aplico também a segunda justificativa, a \"injeção\" de métodos em uma classe sem alterar sua hierarquia. Tirando isso, todas as vezes que penso em usar interfaces eu termino usando simples classes e tudo fica extremamente limpo e simples, do jeito que eu gosto, do jeito que TODOS deveríamos gostar.</p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-09-17 03:43:04',24,'','2020-07-10 23:46:27',24,0,'0000-00-00 00:00:00','2017-02-11 13:56:40','0000-00-00 00:00:00','{\"image_intro\":\"\",\"float_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000105\\/FullArticle.jpg\",\"float_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"article_layout\":\"\",\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_associations\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_page_title\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',100,90,'Delphi, Addicted 2 Delphi, Interface, IInterface, TInterfacedObject, Cláusula de Resolução de Método, Method Resolution Clause','Meu primeiro contato com interfaces foi lendo o livro \"A Bíblia do Delphi 5\". Nele eu aprendi que interfaces, grosso modo, são classes puramente abstratas (todos os seus métodos são virtuais e abstratos) e que ela obriga as classes que as suportam a implementar todos os seus métodos. Na época não consegui enxergar a utilidade das interfaces no Delphi. Desde então, tudo que eu precisei fazer eu fiz com simples classes. Já passei por alguns empregos e alguns projetos e nunca precisei usar de fato interfaces. Este artigo aborda as interfaces no Delphi, por isso é possível que você não possa aplicar o que está escrito aqui em outras linguagens. Se quer entender um pouco a respeito, continue lendo.',1,23640,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(106,290,'Como depurar um Serviço do Windows (Windows Service)','como-depurar-um-servico-do-windows-windows-service','<p style=\"text-align: justify;\">Se você algum dia já criou algum Serviço de Windows (Windows Services) no Delphi (TService) obviamente deve ter tentado depurá-lo e logo notou que a tarefa não seria fácil, devido ao fato de que os Windows Services, para serem executados, precisam ser instalados... Não! Espere! Não é bem assim. Continue lendo e descubra! ;)</p>\r\n','\r\n<p style=\"text-align: justify;\">Sem dúvida uma das características mais evidentes e úteis do Delphi é o seu depurador integrado à IDE. Se você é um bom programador fará bom uso dessa característica até o ponto em que você se torna dependente dela. Se isso é bom ou ruim, não cabe a ninguém julgar, pois características que nos ajudam a programar melhor e mais rápido são sempre bem vindas.</p>\r\n<p style=\"text-align: justify;\">Certa vez me pediram para criar um Serviço de Windows. Era a primeira vez que eu estava desenvolvendo este tipo de coisa, logo, pesquisei na ajuda do próprio Delphi e vi que era bem simples. Como bom programador Delphi, eu fui em <strong>File &gt; New &gt; Other &gt; Delphi Projects &gt; Service Application</strong> e desenvolvi toda a lógica do serviço, só que ao pressionar F9, descobri que não era possível executar um TService. Ao pesquisar, descobri então que o mesmo precisaria ser instalado para que seu evento OnExecute (onde a lógica reside) fosse executado de fato.</p>\r\n<p style=\"text-align: justify;\">Como sou muito fuçador, eu resolvi pesquisar muito na Internet, até que descobri um meio de realizar a depuração de um Serviço de Windows sem a necessidade de instalá-lo, então eu evoluí o exemplo que eu encontrei, até transformá-lo em um <strong>TForm simples com controles para, iniciar, parar e pausar qualquer serviço</strong>, e mais: o código para isso funcionar pode ser posto diretamente no código-fonte de um projeto já existente e detecta automaticamente quando estamos executando o serviço por meio da IDE do Delphi, altera a forma de inicialização do serviço e mostra o TForm com os controles. Ao executar o mesmo serviço por fora do Delphi, o mesmo se comporta como um serviço qualquer.</p>\r\n<h2 style=\"text-align: justify;\">Como utilizar a unit UFormServiceTester?</h2>\r\n<p style=\"text-align: justify;\">A fim de não tornar este artigo longo, vou direto ao ponto. Ao criar um TService no Delphi, o seu arquivo de projeto (dpr) vai conter o seguinte código:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>program MeuServico;\r\n\r\nuses\r\n SvcMgr,\r\n UServico in \'UServico.pas\' {Servico: TServico};\r\n\r\n{$R *.RES}\r\n\r\nbegin\r\n // Windows 2003 Server requires StartServiceCtrlDispatcher to be\r\n // called before CoRegisterClassObject, which can be called indirectly\r\n // by Application.Initialize. TServiceApplication.DelayInitialize allows\r\n // Application.Initialize to be called from TService.Main (after\r\n // StartServiceCtrlDispatcher has been called).\r\n //\r\n // Delayed initialization of the Application object may affect\r\n // events which then occur prior to initialization, such as\r\n // TService.OnCreate. It is only recommended if the ServiceApplication\r\n // registers a class object with OLE and is intended for use with\r\n // Windows 2003 Server.\r\n //\r\n // Application.DelayInitialize := True;\r\n //\r\n if not Application.DelayInitialize or Application.Installing then\r\n Application.Initialize;\r\n\r\n Application.CreateForm(TServico, Servico);\r\n Application.Run;\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Remova as linhas comentadas deste fonte (para melhor legibilidade) e adicione ao projeto a unit UFormServiceTester. O fonte acima vai ficar como segue:</p>\r\n<pre id=\"project3_2\" class=\"line-numbers language-pascal\"><code>program MeuServico;\r\n\r\nuses\r\n SvcMgr,\r\n UServico in \'UServico.pas\' {Servico: TServico},\r\n UFormServiceTester in \'UFormServiceTester.pas\' {FormServiceTester};\r\n\r\n{$R *.RES}\r\n\r\nbegin\r\n if not Application.DelayInitialize or Application.Installing then\r\n Application.Initialize;\r\n\r\n Application.CreateForm(TServico, Servico);\r\n Application.CreateForm(TFormServiceTester, FormServiceTester);\r\n Application.Run;\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Note que o Delphi, tentou adicionar o TFormServiceTester de forma que ele seja automaticamente criado (linha <a href=\"#project3_2.15\" rel=\"alternate\">15</a>). Isso não está correto, logo, simplesmente remova esta linha até que o fonte fique exatamente como abaixo:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>program MeuServico;\r\n\r\nuses\r\n SvcMgr,\r\n UServico in \'UServico.pas\' {Servico: TServico},\r\n UFormServiceTester in \'UFormServiceTester.pas\' {FormServiceTester};\r\n\r\n{$R *.RES}\r\n\r\nbegin\r\n if not Application.DelayInitialize or Application.Installing then\r\n Application.Initialize;\r\n\r\n Application.CreateForm(TServico, Servico);\r\n Application.Run;\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Neste ponto nós temos o mesmo serviço original, só que com uma unit adicional e que não faz nada. Vamos agora utilizar efetivamente a classe TFormServiceTester. Observe o fonte abaixo e deixe seu fonte tal como ele:</p>\r\n<pre id=\"servico\" class=\"line-numbers language-pascal\"><code>program MeuServico;\r\n\r\nuses\r\n SvcMgr,\r\n Forms,\r\n UServico in \'UServico.pas\' {Servico: TServico},\r\n UFormServiceTester in \'UFormServiceTester.pas\' {FormServiceTester};\r\n\r\n{$R *.RES}\r\n\r\nbegin\r\n if DebugHook &lt;&gt; 0 then\r\n begin\r\n Forms.Application.Initialize;\r\n FormServiceTester := TFormServiceTester.Create(TServico);\r\n Forms.Application.Run;\r\n end\r\n else\r\n begin\r\n if not SvcMgr.Application.DelayInitialize or SvcMgr.Application.Installing then\r\n SvcMgr.Application.Initialize;\r\n\r\n SvcMgr.Application.CreateForm(TServico, Servico);\r\n SvcMgr.Application.Run;\r\n end;\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Vou explicar rapidamente alguns elementos do fonte acima. Primeiramente note que na linha <a href=\"#servico.5\" rel=\"alternate\">5</a> foi incluída a unit <strong>Forms</strong>. Esta unit é necessária para que o form <strong>TFormServiceTester</strong> possa ser criado, pois ela contém o objeto global <strong>Application</strong> que define um programa simples de Delphi. Isso é necessário para que o serviço se comporte como um programa qualquer, mas introduz um problema. Na linha <a href=\"#servico.4\" rel=\"alternate\">4</a> existe a unit <strong>SvcMgr</strong>, a qual é responsável por definir o serviço do windows. Esta unit <strong>também contém um objeto global de nome Application</strong>, ou seja, <strong>temos dois objetos globais de nome Application; um deles define um programa simples e o outro define um serviço do Windows</strong>. Para resolver este problema e diferenciar quem é quem, precisamos qualificar o objeto Application, incluindo como prefixo o nome da unit a qual ele pertence. É por isso que nas linhas <a href=\"#servico.14-16\" rel=\"alternate\">14 a 16</a> nós vemos <strong>Forms.Application</strong> e nas linhas <a href=\"#servico.20-24\" rel=\"alternate\">20 a 24</a> nós vemos <strong>SvcMgr.Application</strong>. Assim nós sabemos exatamente quem é quem. Ao falar Forms.Application nos referimos a um programa tradicional e ao falar em SvcMgr.Application, nos referimos a um Windows Service.</p>\r\n<p style=\"text-align: justify;\">Ao executarmos este programa dentro da IDE do delphi, ele tem que se comportar como um programa simples e exibir TFormServiceTester como form principal, e ao executar ele fora do Delphi ele deve se comportar como um serviço do Windows. Para que isso seja possível é utilizada a variável global <strong>DebugHook</strong> (linha <a href=\"#servico.12\" rel=\"alternate\">12</a>). Caso o valor dessa variável em tempo de execução seja diferente de zero, significa que estamos executando o projeto dentro da IDE do Delphi, em outras palavras, o estamos debugando. Caso esta variável tenha um valor zero, estamos executando o programa fora do Delphi. Com a utilização da variável DebugHook fica fácil dividir o código em duas partes. Caso estejamos executando o programa a partir da IDE do Delphi, as linhas <a href=\"#servico.14-16\" rel=\"alternate\">14 a 16</a> serão executadas. Note que olhando estas linhas de forma isolada nos vemos algo muito parecido com aquilo que veríamos ao criar um projeto novo no Delphi. Caso estejamos executando o programa fora do Delphi, as linhas <a href=\"#servico.20-24\" rel=\"alternate\">20 a 24</a> serão executadas. Elas representam exatamente aquilo que havia neste fonte (dpr) antes da adaptação para utilização de TFormServiceTester.</p>\r\n<p style=\"text-align: justify;\">É isso! Aproveite o código anexado a este artigo e comece ainda hoje a debugar seus serviços de Windows. Não serão mais toleradas desculpas depois da leitura deste artigo :)</p>',1,80,'2016-09-20 03:02:24',24,'','2016-11-18 14:13:29',24,0,'0000-00-00 00:00:00','2016-09-21 13:19:35','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000106\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{}',12,91,'Delphi, Addicted 2 Delphi, TService, Windows Services, Depuração, Debug, Threads, TThread, DebugHook','Se você algum dia já criou algum Serviço de Windows (Windows Services) no Delphi (TService) obviamente deve ter tentado depurá-lo e logo notou que a tarefa não seria fácil, devido ao fato de que os Windows Services, para serem executados, precisam ser instalados... Não! Espere! Não é bem assim. Continue lendo e descubra! ;)',1,6407,'{}',1,'*','',''),(107,294,'Como validar um XML com um XSD usando o Delphi?','como-validar-um-xml-com-um-xsd','<p style=\"text-align: justify;\">Recentemente eu estive envolvido em um projeto onde eu precisava gerar v&#225;rios arquivos XML. O solicitante forneceu arquivos XSD (Esquema XML) para cada um dos XML que deveriam ser gerados. Ap&#243;s a gera&#231;&#227;o de cada XML eu, utilizando um plug-in do <a href=\"https://notepad-plus-plus.org/\" rel=\"alternate\">Notepad++</a>, validava individualmente cada XML com seu respectivo XSD, foi a&#237; que meu chefe perguntou se o pr&#243;prio Delphi n&#227;o poderia fazer essa etapa. Meus olhos brilharam e eu comecei a pesquisar. O resultado voc&#234; vai ver no artigo.</p>\r\n','\r\n<p style=\"text-align: justify;\">Por uma feliz coincid&#234;ncia, quando eu comecei a buscar uma resposta para esta pergunta, eu estava terminando de desenvolver o exemplo que foi usado no artigo \"<a href=\"index.php?option=com_content&amp;view=article&amp;id=102&amp;catid=80&amp;Itemid=493\">Como usar corretamente uma barra de progresso (TProgressBar)?</a>\", ent&#227;o, a valida&#231;&#227;o de um XML com um XSD foi a primeira solu&#231;&#227;o real na qual a classe <strong>TProgressThread</strong> foi utilizada. Se voc&#234; ainda n&#227;o leu o artigo em quest&#227;o, recomendo que o fa&#231;a agora, clicando <a href=\"index.php?option=com_content&amp;view=article&amp;id=102&amp;catid=80&amp;Itemid=493\" rel=\"alternate\">aqui</a>.</p>\r\n<p style=\"text-align: justify;\">A primeira vers&#227;o desta solu&#231;&#227;o utilizou uma forma de valida&#231;&#227;o muito b&#225;sica que retornava apenas o resultado da primeira checagem. Como eu usava o plugin do Notepad++ eu queria ver um resultado tal como ele retorna,&#160;mostrando o resultado da valida&#231;&#227;o para cada elemento (n&#243; xml)&#160;validado. Isso d&#225; ao usu&#225;rio uma informa&#231;&#227;o precisa acerca do que est&#225; errado.</p>\r\n<p style=\"text-align: justify;\">Ap&#243;s algumas pesquisas eu descobri o que faltava e ajustei a vers&#227;o inicial. O resultado veio do jeito que eu queria e eu j&#225; ia entregar a solu&#231;&#227;o, quando resolvi comparar a sa&#237;da de meu validador, com a sa&#237;da do validador do Notepad++. Foi a&#237; que eu descobri que ele n&#227;o estava validando corretamente as restri&#231;&#245;es &#250;nicas (unique constraints) contidas no XSD. Houve um XML com 3 erros de chaves duplicadas mas apenas a primeira era reportada pelo meu validador. Voltei a pesquisar.</p>\r\n<p style=\"text-align: justify;\">Depois de algum tempo eu descobri um exemplo que usava uma forma totalmente diferente de valida&#231;&#227;o, usando a interface <a href=\"https://msdn.microsoft.com/pt-br/library/ms767671(v=vs.85).aspx\" rel=\"alternate\">ISAXXMLReader</a>. Esta interface possui propriedades espec&#237;ficas para manipula&#231;&#227;o de conte&#250;do e manipula&#231;&#227;o de erros de valida&#231;&#227;o (<strong>ContentHandler</strong> e <strong>ErrorHandler</strong> respectivamente). Cada uma destas propriedades deve receber uma inst&#226;ncia de uma classe espec&#237;fica. Uma classe que implementa&#160;<strong>IVBSAXErrorHandler</strong> e outra que implementa&#160;<strong>IVBSAXContentHandler</strong>. Isso d&#225; ao programador um controle absoluto do que fazer em cada situa&#231;&#227;o. Eu n&#227;o usei todas as capacidades desta implementa&#231;&#227;o, me concentrei apenas na valida&#231;&#227;o, mas certamente &#233; poss&#237;vel realizar muito mais coisas com ela.</p>\r\n<p style=\"text-align: justify;\">Abaixo est&#225; a unit <strong>UValidateXMLXSD</strong>. &#201; nela onde est&#225; a classe <strong>TValidateXMLXSD</strong>, derivada de TProgressThread, e que &#233; a respons&#225;vel por executar a valida&#231;&#227;o:</p>\r\n<pre id=\"uvalidatexmlxsd\" class=\"line-numbers language-pascal\"><code>unit UValidateXMLXSD;\r\n\r\ninterface\r\n\r\nuses\r\n UProgressThread, Classes;\r\n\r\ntype\r\n TValidateXMLXSD = class (TProgressThread)\r\n private\r\n FXSDFile: String;\r\n FXMLFile: String;\r\n FResult: TStringList;\r\n FIgnoreDuplicates: Boolean;\r\n function ValidateXMLXSD(PXMLFile, PXSDFile: string; PIgnoreDuplicates: Boolean): TStringList;\r\n public\r\n constructor Create; override;\r\n destructor Destroy; override;\r\n procedure Execute; override;\r\n property XMLFile: String write FXMLFile;\r\n property XSDFile: String write FXSDFile;\r\n property IgnoreDuplicates: Boolean write FIgnoreDuplicates;\r\n property Result: TStringList read FResult;\r\n end;\r\n\r\nimplementation\r\n\r\nuses\r\n SysUtils, ComObj, ActiveX, MSXML2_TLB;\r\n\r\ntype\r\n TSaxErrorHandler = class (TInterfacedObject, IVBSAXErrorHandler)\r\n private\r\n FListaDeErros: TStringList;\r\n FIgnoreDuplicates: Boolean;\r\n public\r\n constructor Create(PListaDeErros: TStringList; PIgnoreDuplicates: Boolean);\r\n // TInterfacedObject\r\n function GetTypeInfoCount(out Count: Integer): HResult; stdcall;\r\n function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;\r\n function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;\r\n function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;\r\n // IVBSAXErrorHandler\r\n procedure Error(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer); safecall;\r\n procedure FatalError(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer); safecall;\r\n procedure IgnorableWarning(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer); safecall;\r\n end;\r\n\r\n TSaxContentHandler = class (TInterfacedObject, IVBSAXContentHandler)\r\n protected\r\n FPath: TStringList;\r\n public\r\n constructor Create; virtual;\r\n destructor Destroy; override;\r\n // TInterfacedObject\r\n function GetTypeInfoCount(out Count: Integer): HResult; stdcall;\r\n function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;\r\n function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;\r\n function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;\r\n // IVBSAXContentHandler\r\n procedure _Set_documentLocator(const Param1: IVBSAXLocator); virtual; safecall;\r\n procedure startDocument; virtual; safecall;\r\n procedure endDocument; virtual; safecall;\r\n procedure startPrefixMapping(var strPrefix: WideString; var strURI: WideString); virtual; safecall;\r\n procedure endPrefixMapping(var strPrefix: WideString); virtual; safecall;\r\n procedure startElement(var strNamespaceURI: WideString; var strLocalName: WideString; var strQName: WideString; const oAttributes: IVBSAXAttributes); virtual; safecall;\r\n procedure endElement(var strNamespaceURI: WideString; var strLocalName: WideString; var strQName: WideString); virtual; safecall;\r\n procedure characters(var strChars: WideString); virtual; safecall;\r\n procedure ignorableWhitespace(var strChars: WideString); virtual; safecall;\r\n procedure processingInstruction(var strTarget: WideString; var strData: WideString); virtual; safecall;\r\n procedure skippedEntity(var strName: WideString); virtual; safecall;\r\n end;\r\n\r\n TTagReaded = class (TSaxContentHandler)\r\n private\r\n FValidateXMLXSD: TValidateXMLXSD;\r\n public\r\n constructor Create(PValidateXMLXSD: TValidateXMLXSD); reintroduce;\r\n procedure startElement(var strNamespaceURI: WideString; var strLocalName: WideString; var strQName: WideString; const oAttributes: IVBSAXAttributes); override; safecall;\r\n end;\r\n\r\n{ TValidateXMLXSD }\r\n\r\nconstructor TValidateXMLXSD.Create;\r\nbegin\r\n inherited;\r\n\r\nend;\r\n\r\ndestructor TValidateXMLXSD.Destroy;\r\nbegin\r\n\r\n inherited;\r\nend;\r\n\r\nprocedure TValidateXMLXSD.Execute;\r\nvar\r\n XMLDocument: Variant;\r\nbegin\r\n inherited;\r\n Max := 0;\r\n\r\n CoInitialize(nil);\r\n try\r\n try\r\n XMLDocument := CreateOLEObject(\'MSXML2.DOMDocument.6.0\');\r\n XMLDocument.load(FXMLFile);\r\n Max := XMLDocument.documentElement.selectNodes(\'//*\').Length;\r\n DoMax;\r\n finally\r\n XMLDocument := varNull;\r\n end;\r\n\r\n FResult := ValidateXMLXSD(FXMLFile,FXSDFile,FIgnoreDuplicates);\r\n finally\r\n CoUnInitialize;\r\n end;\r\nend;\r\n\r\nfunction TValidateXMLXSD.ValidateXMLXSD(PXMLFile, PXSDFile: string; PIgnoreDuplicates: Boolean): TStringList;\r\nvar\r\n SAXXMLReader: IVBSAXXMLReader;\r\n XMLSchemaCache: Variant;\r\nbegin\r\n // Criando uma cole&#231;&#227;o de esquemas (XSD)\r\n XMLSchemaCache := CreateOleObject(\'MSXML2.XMLSchemaCache.6.0\');\r\n try\r\n // Criando um leitor SAX (XML)\r\n SAXXMLReader := CreateOleObject(\'MSXML2.SAXXMLReader.6.0\') as IVBSAXXMLReader;\r\n\r\n // Adicionando o arquivo de esquema na cole&#231;&#227;o\r\n XMLSchemaCache.Add(\'\',PXSDFile);\r\n\r\n // Configurando o leitor SAX para validar o documento XML que nele for carregado\r\n SAXXMLReader.putFeature(\'schema-validation\', True);\r\n SAXXMLReader.putFeature(\'exhaustive-errors\', True);\r\n SAXXMLReader.putProperty(\'schemas\', XMLSchemaCache);\r\n\r\n Result := TStringList.Create;\r\n // Atribuindo manipuladores necess&#225;rios. TSaxErrorHandler manipula apenas os erros\r\n // e TTagReaded manipula cada n&#243; lido\r\n SAXXMLReader.errorHandler := TSaxErrorHandler.Create(Result,PIgnoreDuplicates);\r\n SAXXMLReader.contentHandler := TTagReaded.Create(Self);\r\n\r\n // Executa a valida&#231;&#227;o\r\n try\r\n SAXXMLReader.ParseURL(PXMLFile);\r\n except\r\n { Evita que as exce&#231;&#245;es decorrentes de erros de valida&#231;&#227;o parem um processamento em lote }\r\n end;\r\n finally\r\n XMLSchemaCache := varNull;\r\n end;\r\nend;\r\n\r\n{ TSaxErrorHandler }\r\n\r\nconstructor TSaxErrorHandler.Create(PListaDeErros: TStringList; PIgnoreDuplicates: Boolean);\r\nbegin\r\n FListaDeErros := PListaDeErros;\r\n FIgnoreDuplicates := PIgnoreDuplicates;\r\nend;\r\n\r\nprocedure TSaxErrorHandler.Error(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer);\r\nvar\r\n Erro: String;\r\nbegin\r\n Erro := \'[ERRO]: \' + Trim(StringReplace(strErrorMessage,#13#10,\' \',[rfReplaceAll]));\r\n if (not FIgnoreDuplicates) or (FListaDeErros.IndexOf(Erro) = -1) then\r\n FListaDeErros.Add(Erro);\r\nend;\r\n\r\nprocedure TSaxErrorHandler.FatalError(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer);\r\nbegin\r\n FListaDeErros.Add(\'[ERRO FATAL]: \' + Trim(StringReplace(strErrorMessage,#13#10,\' \',[rfReplaceAll])));\r\nend;\r\n\r\nfunction TSaxErrorHandler.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { N&#227;o usado }\r\nend;\r\n\r\nfunction TSaxErrorHandler.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { N&#227;o usado }\r\nend;\r\n\r\nfunction TSaxErrorHandler.GetTypeInfoCount(out Count: Integer): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { N&#227;o usado }\r\nend;\r\n\r\nprocedure TSaxErrorHandler.IgnorableWarning(const oLocator: IVBSAXLocator; var strErrorMessage: WideString; nErrorCode: Integer);\r\nvar\r\n Erro: String;\r\nbegin\r\n Erro := \'[AVISO]: \' + Trim(StringReplace(strErrorMessage,#13#10,\' \',[rfReplaceAll]));\r\n if (not FIgnoreDuplicates) or (FListaDeErros.IndexOf(Erro) = -1) then\r\n FListaDeErros.Add(Erro);\r\nend;\r\n\r\nfunction TSaxErrorHandler.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { N&#227;o usado }\r\nend;\r\n\r\n{ TMySaxHandler }\r\n\r\nprocedure TSaxContentHandler.characters(var strChars: WideString);\r\nbegin\r\n { Este m&#233;todo &#233; executado para exibir o conte&#250;do de um tag. Normalmente o que\r\n acontece &#233; &lt;tag&gt;strChars&lt;/tag&gt;, logo este procedure pode ser usado para obter\r\n texto plano que est&#225; contido dentro de um tag }\r\nend;\r\n\r\nconstructor TSaxContentHandler.Create;\r\nbegin\r\n FPath := TStringList.Create;\r\nend;\r\n\r\ndestructor TSaxContentHandler.Destroy;\r\nbegin\r\n FPath.Free;\r\n inherited;\r\nend;\r\n\r\nprocedure TSaxContentHandler.endDocument;\r\nbegin\r\n { Este m&#233;todo &#233; executado ap&#243;s a leitura do documento chegar ao final }\r\nend;\r\n\r\nprocedure TSaxContentHandler.endElement(var strNamespaceURI, strLocalName, strQName: WideString);\r\nbegin\r\n { Este m&#233;todo &#233; executado quando um tag de fechamento &#233; encontrado }\r\n FPath.Delete(Pred(FPath.Count));\r\nend;\r\n\r\nprocedure TSaxContentHandler.endPrefixMapping(var strPrefix: WideString);\r\nbegin\r\n { N&#227;o usado }\r\nend;\r\n\r\nfunction TSaxContentHandler.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { N&#227;o usado }\r\nend;\r\n\r\nfunction TSaxContentHandler.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { N&#227;o usado }\r\nend;\r\n\r\nfunction TSaxContentHandler.GetTypeInfoCount(out Count: Integer): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { N&#227;o usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler.ignorableWhitespace(var strChars: WideString);\r\nbegin\r\n { N&#227;o usado }\r\nend;\r\n\r\nfunction TSaxContentHandler.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { N&#227;o usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler.processingInstruction(var strTarget, strData: WideString);\r\nbegin\r\n { N&#227;o usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler.skippedEntity(var strName: WideString);\r\nbegin\r\n { N&#227;o usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler.startDocument;\r\nbegin\r\n { Este m&#233;todo &#233; executado antes da leitura do documento come&#231;ar }\r\nend;\r\n\r\nprocedure TSaxContentHandler.startElement(var strNamespaceURI, strLocalName, strQName: WideString; const oAttributes: IVBSAXAttributes);\r\nbegin\r\n { Este m&#233;todo &#233; executado quando um tag de abertura &#233; encontrado }\r\n FPath.Add(strLocalName);\r\nend;\r\n\r\nprocedure TSaxContentHandler.startPrefixMapping(var strPrefix, strURI: WideString);\r\nbegin\r\n { N&#227;o usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler._Set_documentLocator(const Param1: IVBSAXLocator);\r\nbegin\r\n { N&#227;o usado }\r\nend;\r\n\r\n{ TTagReaded }\r\n\r\nconstructor TTagReaded.Create(PValidateXMLXSD: TValidateXMLXSD);\r\nbegin\r\n inherited Create;\r\n FValidateXMLXSD := PValidateXMLXSD;\r\nend;\r\n\r\nprocedure TTagReaded.startElement(var strNamespaceURI, strLocalName, strQName: WideString; const oAttributes: IVBSAXAttributes);\r\nbegin\r\n inherited;\r\n FValidateXMLXSD.Text := strLocalName;\r\n FValidateXMLXSD.Number := 0;\r\n FValidateXMLXSD.DoProgress;\r\nend;\r\n\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">N&#227;o tem como eu explicar todas as linhas dessa unit, por pregui&#231;a e por falta de conhecimentos aprofundados (<strong>n&#227;o vou falar do que n&#227;o tenho certeza</strong>), mas vou explicar o que &#233; mais relevante. Para coisas mais simples eu mantive os coment&#225;rios na pr&#243;pria classe.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<p style=\"text-align: justify;\">A classe TValidateXMLXSD (linha <a href=\"#uvalidatexmlxsd.9\" rel=\"alternate\">9</a>) &#233; nossa thread de trabalho (derivada de TProgressThread).&#160;O m&#233;todo <strong>ValidateXMLXSD</strong> (linha <a href=\"#uvalidatexmlxsd.15\" rel=\"alternate\">15</a>) dessa classe, &#233; o respons&#225;vel pela valida&#231;&#227;o do XML. Note que a classe possui 3 propriedades de entrada (write-only) e uma propriedade de sa&#237;da (read-only) nas linhas <a href=\"#uvalidatexmlxsd.20-23\" rel=\"alternate\">20 a 23</a>. As propriedades de entrada s&#227;o&#160;<strong>XMLFile</strong>,&#160;<strong>XSDFile</strong> e&#160;<strong>IgnoreDuplicates</strong>. Elas mapeiam para par&#226;metros do m&#233;todo ValidadeXMLXSD. J&#225; a propriedade Result vai conter o resultado da valida&#231;&#227;o, retornado pela fun&#231;&#227;o ValidadeXMLXSD. Este resultado &#233; uma lista de erros (<strong>TStringList</strong>) a qual, obviamente, estar&#225; vazia, caso nenhum erro tenha sido encontrado.</p>\r\n<p style=\"text-align: justify;\">A linha <a href=\"#IgnoreDuplicates.29\" rel=\"alternate\">29</a> cont&#233;m uma unit estranha de nome <strong>MSXML2_TLB</strong>. Esta unit n&#227;o vem com o Delphi, mas voc&#234; precisa adicion&#225;-la tamb&#233;m ao seu projeto. Eu tamb&#233;m n&#227;o a estou distribuindo no zip anexado a estre artigo por um simples motivo: voc&#234; pode gerar esta unit usando o pr&#243;prio Delphi! Se voc&#234; nunca importou uma&#160;biblioteca de tipos (Type Library), <a href=\"#cotld\" rel=\"alternate\">no final deste artigo eu ensino como gerar&#160;a MSXML2_TLB.pas</a>, mas por ora, vou considerar que voc&#234; j&#225; a possui, pois vou continuar a explicar a unit UValidateXMLXSD.</p>\r\n<p style=\"text-align: justify;\">As linhas <a href=\"#uvalidatexmlxsd.32\" rel=\"alternate\">32</a>&#160;(<strong>TSaxErrorHandler</strong>), <a href=\"#uvalidatexmlxsd.49\" rel=\"alternate\">49</a>&#160;(<strong>TSaxContentHandler</strong>) e <a href=\"#uvalidatexmlxsd.74\" rel=\"alternate\">74</a>&#160;(<strong>TTagReaded</strong>) definem 3 classes que ser&#227;o usadas como classes manipuladoras pela interface ISAXXMLReader. As duas primeiras s&#227;o implementa&#231;&#245;es diretas das interfaces IVBSAXErrorHandler e IVBSAXContentHandler respectivamente. A terceira (TTagReaded) &#233; uma especializa&#231;&#227;o (classe filha) de TSaxContentHandler.&#160;Eu poderia usar diretamente a classe TSaxContentHandler, mas resolvi&#160;implementar uma classe filha como exemplo, pois podemos implementar v&#225;rias&#160;classes filhas com funcionalidades distintas, e que cont&#233;m uma funcionalidade&#160;comum (classe pai). Isso poderia ter sido feito tamb&#233;m com TSaxErrorHandler, mas esta eu usei diretamente.</p>\r\n<p style=\"text-align: justify;\">Na linha <a href=\"#uvalidatexmlxsd.120\" rel=\"alternate\">120</a> encontramos o m&#233;todo principal da thread, respons&#225;vel por realizar a valida&#231;&#227;o do aquivo XML, dentro dele, nas linhas <a href=\"#uvalidatexmlxsd.142\" rel=\"alternate\">142</a> e <a href=\"#uvalidatexmlxsd.143\" rel=\"alternate\">143</a> est&#225; a atribui&#231;&#227;o das classes manipuladoras &#224;s propriedades <strong>SAXXMLReader.ErrorHandler</strong> e <strong>SAXXMLReader.ContentHandler</strong>. Note que a atribui&#231;&#227;o &#233; feita simplesmente criando uma inst&#226;ncia de cada uma das classes diretamente na sua propriedade correspondente. Como as propriedades s&#227;o interfaces, n&#227;o &#233; necess&#225;rio liberar as classes criadas (Free).</p>\r\n<p style=\"text-align: justify;\">A linha <a href=\"#uvalidatexmlxsd.149\" rel=\"alternate\">149</a> n&#227;o foi erro de digita&#231;&#227;o e nem &#233; uma gambiarra.&#160;Eu chamo essa t&#233;cnica de \"mudinho\" ou \"bico calado\". Bom, n&#227;o &#233; uma t&#233;cnica de fato, mas foi uma necessidade. O m&#233;todo <strong>ParseURL</strong> (linha <a href=\"#uvalidatexmlxsd.147\" rel=\"alternate\">147</a>) inicia o processo de valida&#231;&#227;o n&#243; a n&#243; do XML. Quando um erro &#233; encontrado em um n&#243;, um flag interno de erro &#233; ativado, mas o processamento continua at&#233; que n&#227;o sobre mais nenhum n&#243;. Ao terminar o processamento, o m&#233;todo ParseURL verifica o flag interno e se ele estiver ativado, uma exce&#231;&#227;o &#233; levantada. Como nesta implementa&#231;&#227;o eu estou salvando cada erro numa lista (TStringList), n&#227;o &#233; necess&#225;rio manipular a exce&#231;&#227;o, pois caso a lista contenha itens eu saberei que houve erros, logo, eu estou emudecendo qualquer exce&#231;&#227;o levantada por ParseURL. Voc&#234; poderia manipular a exce&#231;&#227;o e exibir nela uma mensagem mais amig&#225;vel ao usu&#225;rio. Eu n&#227;o fiz isso porque este m&#233;todo seria usado por mim numa rotina em lote (v&#225;rios arquivos sendo validados), logo, eu n&#227;o poderia mostrar uma mensagem toda vez que houvesse erros. Outra coisa que poderia ser feita na manipula&#231;&#227;o desta exce&#231;&#227;o seria adicionar um item a mais na lista de erros. Tudo isso fica a crit&#233;rio do desenvolvedor. A solu&#231;&#227;o, como est&#225;, atende &#224;s minhas necessidades.</p>\r\n<p style=\"text-align: justify;\">Nas&#160;linhas <a href=\"#uvalidatexmlxsd.164\" rel=\"alternate\">164</a>, <a href=\"#uvalidatexmlxsd.173\" rel=\"alternate\">173</a> e <a href=\"#uvalidatexmlxsd.193\" rel=\"alternate\">193</a>&#160;est&#227;o declarados os m&#233;todos que s&#227;o executados quando o parser encontra problemas.&#160;<strong>TSaxErrorHandler.Error</strong> &#233; executado quando h&#225; um erro de valida&#231;&#227;o em um n&#243; mas o parser continua.&#160;<strong>TSaxErrorHandler.FatalError</strong> &#233; executado quando um erro grave &#233; encontrado e neste caso o parser para imediatamente.&#160;<strong>TSaxErrorHandler.IgnorableWarning</strong> &#233; executado quando h&#225; algo estranho em algum n&#243;, mas que n&#227;o caracteriza um erro e o parser continua.</p>\r\n<p style=\"text-align: justify;\">Na linha <a href=\"#uvalidatexmlxsd.307\" rel=\"alternate\">307</a> est&#225; o m&#233;todo que &#233; executado para cada n&#243; lido. Especificamente, o m&#233;todo <strong>TTagReaded.StartElement</strong> (<strong>TSaxContentHandler.StartElement</strong>) &#233; executado cada vez que um tag de abertura &#233; encontrado. Nas linguagens de marca&#231;&#227;o, os elementos&#160;podem ser compostos por um tag de abertura (&lt;a&gt;, por exemplo) e um tag de fechamento (&lt;/a&gt;, por exemplo) ou apenas um tag de abertura (&lt;br /&gt;, por exemplo), logo, este m&#233;todo &#233; executado para todo e qualquer tag (n&#243; do XML) encontrado e &#233; o local ideal para se executar o m&#233;todo <strong>DoProgress</strong>, o qual gera o evento <strong>OnProgress</strong>, no qual uma barra de progresso pode ser incrementada.</p>\r\n<p>Para utilizar a classe TValidateXMLXSD, crie um campo privado em um form (<strong>FValidateXMLXSD</strong> no exemplo) e proceda da seguinte forma:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>MEMO.Lines.Add(\'Validando o arquivo \"arquivo.xml\"\'#13#10);\r\n\r\nFValidateXMLXSD := TValidateXMLXSD.Create;\r\n\r\nwith FValidateXMLXSD do\r\nbegin\r\n XMLFile := \'arquivo.xml\'; { informe o arquivo xml }\r\n XSDFile := \'arquivo.xsd\'; { informe o esquema xsd para validar o arquivo }\r\n IgnoreDuplicates := False; { usar true, gera menos sa&#237;da, caso haja erros }\r\n OnMax := DoMax; { evento para configurar uma barra de progresso }\r\n OnProgress := DoProgress; { evento para incrementar uma barra de progresso}\r\n OnTerminate := DoTerminate; { evento ativado quando a thread termina }\r\n Resume;\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">N&#227;o vou explicar o que cada uma destas linhas faz porque isso j&#225; foi coberto no artigo \"<a href=\"index.php?option=com_content&amp;view=article&amp;id=102&amp;catid=80&amp;Itemid=493\">Como usar corretamente uma barra de progresso (TProgressBar)?</a>\". Se voc&#234; ainda n&#227;o o leu, recomendo fazer isso. O artigo est&#225; muito bem escrito por um grande amigo meu, o qual conhe&#231;o muito bem :)&#160;Ao terminar a valida&#231;&#227;o do arquivo, o evento <strong>OnTerminate</strong> vai ser ativado. &#201; nele onde devemos verificar a resposta (lista de erros) e gerar uma sa&#237;da adequada para o usu&#225;rio. Neste exemplo eu usei um <strong>TMemo</strong> de nome <strong>MEMO</strong> para mostrar o resultado da valida&#231;&#227;o. Veja como foi feito:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>procedure TFormValidarXML.DoTerminate(PSender: TObject);\r\nbegin\r\n try\r\n { caso haja linhas no resultado, significa que houve erros, logo, devemos mostr&#225;-los }\r\n if FValidateXMLXSD.Result.Count &gt; 0 then\r\n begin\r\n MEMO.Lines.Add(\'Os seguintes erros de valida&#231;&#227;o foram encontrados:\'#13#10);\r\n MEMO.Lines.Add(FValidateXMLXSD.Result.Text);\r\n end\r\n else\r\n MEMO.Lines.Add(\'Este arquivo n&#227;o cont&#233;m erros!\'#13#10);\r\n finally\r\n FValidateXMLXSD.Result.Free;\r\n end;\r\n MEMO.Lines.Add(\'-------------\'); { isso &#233; apenas um separador. Pode ser omitido }\r\nend;</code></pre>\r\n<p>Basicamente isso &#233; tudo que voc&#234; precisa fazer para validar um XML com seu XSD no Delphi usando uma thread :)</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 id=\"cotld\">Como importar uma Type Library no Delphi</h2>\r\n<p style=\"text-align: justify;\">Segundo a Microsoft uma Type Library (biblioteca de tipos) &#233;:</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Um arquivo bin&#225;rio que armazena informa&#231;&#245;es sobre propriedades e m&#233;todos de objetos COM ou DCOM, numa forma que &#233; acess&#237;vel a outras aplica&#231;&#245;es em tempo de execu&#231;&#227;o. Usando uma biblioteca de tipos uma aplica&#231;&#227;o pode determinar quais interfaces um objeto suporta e invocar os m&#233;todos da interface deste objeto. Isso pode ser feito&#160;mesmo se o objeto e a aplica&#231;&#227;o cliente tiverem sido escritas em linguagens diferentes.</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Em outras palavras uma biblioteca de tipos cont&#233;m informa&#231;&#245;es sobre tipos (objetos) e podem ser usadas para instanciar estes objetos em qualquer aplica&#231;&#227;o, escrita em qualquer linguagem!</p>\r\n<p style=\"text-align: justify;\">O validador de XML descrito neste artigo, utiliza objetos, constantes e tipos dispon&#237;veis na biblioteca <strong>MSXML</strong> que &#233; uma biblioteca de tipos. Precisamos ent&#227;o importar esta dll com o Delphi a fim de gerar a unit MSXML_TLB.pas, a qual &#233; utilizada (<strong>uses</strong>) na unit UValidateXMLXSD. Siga os passos abaixo para importar uma biblioteca de tipos:</p>\r\n<ol>\r\n<li style=\"text-align: justify;\">Clique no item de menu <strong>Component &gt; Import Component...</strong> O wizard <strong>Import Component</strong> vai aparecer<br /><br /><img src=\"images/add2del/artigos/ID000107/TL1.png\" width=\"527\" height=\"418\" /><br /><br /></li>\r\n<li style=\"text-align: justify;\">Selecione a op&#231;&#227;o <strong>Import a Type Library</strong> e pressione o bot&#227;o <strong>Next &gt;&gt;</strong>. A pr&#243;xima etapa vai aparecer<br /><br /><img src=\"images/add2del/artigos/ID000107/TL2.png\" width=\"700\" height=\"418\" /><br /><br /></li>\r\n<li style=\"text-align: justify;\">Na lista de bibliotecas selecione <strong>Microsoft XML</strong>. Note que no meu caso, existem 3 vers&#245;es de biblioteca dispon&#237;veis, v3.0, v4.0 e v6.0. Escolha a vers&#227;o mais recente, no meu caso &#233; a vers&#227;o 6.0. Existe uma observa&#231;&#227;o muito importante a respeito destas vers&#245;es. N&#227;o esque&#231;a de ler a <a href=\"#versoes\" rel=\"alternate\">&#250;ltima se&#231;&#227;o deste artigo</a>. Clique o bot&#227;o <strong>Next &gt;&gt;</strong>. A pr&#243;xima etapa vai aparecer<br /><br /><img src=\"images/add2del/artigos/ID000107/TL3.png\" width=\"700\" height=\"418\" /><br /><br /></li>\r\n<li style=\"text-align: justify;\">Nesta tela nada precisa ser feito, deixe tudo como est&#225; e pressione <strong>Next &gt;&gt;</strong>. A pr&#243;xima etapa vai aparecer<br /><br /><img src=\"images/add2del/artigos/ID000107/TL4.png\" width=\"700\" height=\"418\" /><br /><br /></li>\r\n<li style=\"text-align: justify;\">Agora, selecione a op&#231;&#227;o <strong>Add unit to &lt;NomeDoProjeto&gt; project</strong> e pressione o bot&#227;o <strong>Finish</strong>.</li>\r\n</ol>\r\n<p style=\"text-align: justify;\">Ao executar o passo 5 acima, a unit MSXML2_TLB vai ser adicionada ao projeto &lt;NomeDoProjeto&gt; e poder&#225; ser salva como qualquer outra unit juntamente com os fontes do seu sistema.</p>\r\n<h2 style=\"text-align: justify;\"><a id=\"versoes\"></a>IMPORTANTE!: Sobre as vers&#245;es de MSXML e os arquivos anexados a este exemplo</h2>\r\n<p style=\"text-align: justify;\">Quando eu desenvolvi&#160;esta solu&#231;&#227;o de valida&#231;&#227;o eu importei a vers&#227;o 6.0 de MSXML, sendo assim, dentro do arquivo UValidateXMLXSD, existem refer&#234;ncias a esta vers&#227;o. Observe as linhas <a href=\"#uvalidatexmlxsd.106\" rel=\"alternate\">106</a>, <a href=\"#uvalidatexmlxsd.126\" rel=\"alternate\">126</a> e <a href=\"#uvalidatexmlxsd.129\" rel=\"alternate\">129</a>. Em todas elas, ao informar o identificador da classe eu incluo o valor apropriado &#160;da vers&#227;o, no caso 6.0.</p>\r\n<p style=\"text-align: justify;\"><strong>Portanto, ao importar a biblioteca de tipo MSXML, caso a vers&#227;o mais recente dispon&#237;vel n&#227;o seja&#160;a 6.0, voc&#234; precisar&#225; alterar as 3 linhas citadas no par&#225;grafo anterior de forma a conter a vers&#227;o correta, do contr&#225;rio podem acontecer coisas estranhas ou a classe TValidateXMLXSD pode n&#227;o funcionar por completo. Voc&#234; precisar&#225;, pois, usar 3.0 ou 4.0 nos identificadores, dependendo da vers&#227;o que foi importada.</strong></p>\r\n<hr class=\"adsensesnippet\" />',1,80,'2016-09-21 20:07:45',24,'','2020-12-02 17:44:29',24,0,'0000-00-00 00:00:00','2016-09-23 14:51:31','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000107\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{\"show_title\":\"\",\"link_titles\":\"\",\"show_tags\":\"\",\"show_intro\":\"\",\"info_block_position\":\"\",\"info_block_show_title\":\"\",\"show_category\":\"\",\"link_category\":\"\",\"show_parent_category\":\"\",\"link_parent_category\":\"\",\"show_author\":\"\",\"link_author\":\"\",\"show_create_date\":\"\",\"show_modify_date\":\"\",\"show_publish_date\":\"\",\"show_item_navigation\":\"\",\"show_icons\":\"\",\"show_print_icon\":\"\",\"show_email_icon\":\"\",\"show_vote\":\"\",\"show_hits\":\"\",\"show_noauth\":\"\",\"urls_position\":\"\",\"alternative_readmore\":\"\",\"article_layout\":\"\",\"show_publishing_options\":\"\",\"show_article_options\":\"\",\"show_urls_images_backend\":\"\",\"show_urls_images_frontend\":\"\"}',29,92,'Delphi, Addicted 2 Delphi, XML, XSD, Validação, Type Library, Biblioteca de Tipos, MSXML','Recentemente eu estive envolvido em um projeto onde eu precisava gerar vários arquivos XML. O solicitante forneceu arquivos XSD (Esquema XML) para cada um dos XML que deveriam ser gerados. Após a geração de cada XML eu, utilizando um plug-in do Notepad++, validava individualmente cada XML com seu respectivo XSD, foi aí que meu chefe perguntou se o próprio Delphi não poderia fazer essa etapa. Meus olhos brilharam e eu comecei a pesquisar. O resultado você vai ver no artigo.',1,11237,'{\"robots\":\"\",\"author\":\"\",\"rights\":\"\",\"xreference\":\"\"}',1,'*','',''),(108,300,'Sobrecarga de operadores','sobrecarga-de-operadores','<p style=\"text-align: justify;\">Sempre fui uma pessoa prática e como tal eu prezo muito pela celeridade do desenvolvimento de minhas soluções. Por este motivo, tudo aquilo que pode gerar menos digitação a curto ou médio prazo me atrai, e um exemplo muito bom disso é a <strong>sobrecarga de operadores</strong>. Usando esta técnica é possível economizar bastante tempo (e seus dedos), pois ela permite que estruturas complexas possam ser operadas com simples operadores matemáticos. O que você acha de somar dois records, por exemplo, ou mesmo multiplicar dois records, ou dividí-los, ou subtraí-los, etc? Ficou curioso? Então leia mais.</p>\r\n','\r\n<p style=\"text-align: justify;\">Uma característica poderosa que o Delphi possui é a sobrecarga de operdadores (<strong>Operator Overloading</strong>), a qual basicamente permite que objetos complexos e records possam ser usados como operandos simples em expressões como \"A + B\" ou \"A * B\", onde A e B são objetos complexos (ou records).</p>\r\n<p style=\"text-align: justify;\">No Delphi 2006, onde o exemplo deste artigo foi construído, é possível fazer a sobrecarga de operadores em records na personalidade Win32 e em records ou classes na personalidade .NET. Como não tenho interessem em programação .NET vou apenas explicar a sobrecarga em records que ao meu ver já ajuda bastante. Se você quiser saber como fazer a sobrecarga de operadores em classes no .NET ou está usando uma versão mais recente do Delphi que permita a sobrecarga de operadores em classes na personalidade Win32, consulte a ajuda do Delphi, aliás, aconselho você a olhar a ajuda do Delphi de qualquer maneira, pois este artigo é apenas uma abordagem básica para expor esta funcionalidade pouco explorada por nós, desenvolvedores</p>\r\n<h2 style=\"text-align: justify;\">Sobrecarregando...</h2>\r\n<p style=\"text-align: justify;\">A título de exemplo vamos criar um record simples, com alguns membros de tipos simples. Esse record poderia ser a representação de um registro de um arquivo qualquer que armazena valores a respeito de determinado assunto. Quaisquer tipos podem ser usados, incluindo outros tipos complexos, records e objetos, pois as regras do que deve ser feito quando da utilização de qualquer operador são definidas nos métodos conhecidos como <strong>class operators</strong>. Para uma lista completa de todos os class operators consulte a ajuda do Delphi buscando por \"Operator Overloading\". Chega de bla bla bla, vamos ao código. A implementação completa da unit encontra-se a seguir:</p>\r\n<pre id=\"sdo\" class=\"line-numbers language-pascal\"><code>program SDO;\r\n\r\n{$APPTYPE CONSOLE}\r\n\r\nuses\r\n SysUtils, Windows, DateUtils, Classes;\r\n\r\ntype\r\n TRegistro = record\r\n ValorString: String;\r\n ValorInteiro: Integer;\r\n ValorDouble: Double;\r\n ValorCurrency: Currency;\r\n class operator Add(POperando1, POperando2: TRegistro): TRegistro;\r\n class operator Subtract(POperando1, POperando2: TRegistro): TRegistro;\r\n class operator Multiply(POperando1, POperando2: TRegistro): TRegistro;\r\n class operator Divide(POperando1, POperando2: TRegistro): TRegistro;\r\n end;\r\n\r\n{ TExemplo }\r\n\r\nfunction ColocaParenteses(POperando: String): String;\r\nbegin\r\n Result := POperando;\r\n\r\n if (Pos(\'+\',Result) &gt; 0)\r\n or (Pos(\'-\',Result) &gt; 0)\r\n or (Pos(\'*\',Result) &gt; 0)\r\n or (Pos(\'/\',Result) &gt; 0) then\r\n begin\r\n Result := \'(\' + Result + \')\';\r\n end;\r\nend;\r\n\r\nclass operator TRegistro.Add(POperando1, POperando2: TRegistro): TRegistro;\r\nbegin\r\n ZeroMemory (@Result,SizeOf(TRegistro));\r\n\r\n with Result do\r\n begin\r\n ValorString := ColocaParenteses(POperando1.ValorString) + \' + \' + ColocaParenteses(POperando2.ValorString);\r\n ValorInteiro := POperando1.ValorInteiro + POperando2.ValorInteiro;\r\n ValorDouble := POperando1.ValorDouble + POperando2.ValorDouble;\r\n ValorCurrency := POperando1.ValorCurrency + POperando2.ValorCurrency;\r\n end;\r\nend;\r\n\r\nclass operator TRegistro.Divide(POperando1, POperando2: TRegistro): TRegistro;\r\nbegin\r\n ZeroMemory (@Result,SizeOf(TRegistro));\r\n\r\n with Result do\r\n begin\r\n ValorString := ColocaParenteses(POperando1.ValorString) + \' / \' + ColocaParenteses(POperando2.ValorString);\r\n ValorInteiro := POperando1.ValorInteiro div POperando2.ValorInteiro;\r\n ValorDouble := POperando1.ValorDouble / POperando2.ValorDouble;\r\n ValorCurrency := POperando1.ValorCurrency / POperando2.ValorCurrency;\r\n end;\r\nend;\r\n\r\nclass operator TRegistro.Multiply(POperando1, POperando2: TRegistro): TRegistro;\r\nbegin\r\n ZeroMemory (@Result,SizeOf(TRegistro));\r\n\r\n with Result do\r\n begin\r\n ValorString := ColocaParenteses(POperando1.ValorString) + \' * \' + ColocaParenteses(POperando2.ValorString);\r\n ValorInteiro := POperando1.ValorInteiro * POperando2.ValorInteiro;\r\n ValorDouble := POperando1.ValorDouble * POperando2.ValorDouble;\r\n ValorCurrency := POperando1.ValorCurrency * POperando2.ValorCurrency;\r\n end;\r\nend;\r\n\r\nclass operator TRegistro.Subtract(POperando1, POperando2: TRegistro): TRegistro;\r\nbegin\r\n ZeroMemory (@Result,SizeOf(TRegistro));\r\n\r\n with Result do\r\n begin\r\n ValorString := ColocaParenteses(POperando1.ValorString) + \' - \' + ColocaParenteses(POperando2.ValorString);\r\n ValorInteiro := POperando1.ValorInteiro - POperando2.ValorInteiro;\r\n ValorDouble := POperando1.ValorDouble - POperando2.ValorDouble;\r\n ValorCurrency := POperando1.ValorCurrency - POperando2.ValorCurrency;\r\n end;\r\nend;\r\n\r\nvar\r\n Op1: TRegistro;\r\n Op2: TRegistro;\r\n OpR: TRegistro;\r\nbegin\r\n ZeroMemory(@Op1,SizeOf(TRegistro));\r\n ZeroMemory(@Op2,SizeOf(TRegistro));\r\n\r\n Op1.ValorString := \'Carlos\';\r\n Op1.ValorInteiro := 25;\r\n Op1.ValorDouble := 23.8;\r\n Op1.ValorCurrency := 123.6789;\r\n\r\n WriteLn(\'Op1:\');\r\n WriteLn(\'&gt; ValorString = \', Op1.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', Op1.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',Op1.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',Op1.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n Op2.ValorString := \'Lara\';\r\n Op2.ValorInteiro := 30;\r\n Op2.ValorDouble := 478.333;\r\n Op2.ValorCurrency := 17899.4788;\r\n\r\n WriteLn(\'Op2:\');\r\n WriteLn(\'&gt; ValorString = \', Op2.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', Op2.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',Op2.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',Op2.ValorCurrency));\r\n\r\n WriteLn;\r\n WriteLn(\'------------------------------------------------------------------\');\r\n WriteLn;\r\n\r\n WriteLn(\'Op1 + Op2:\');\r\n OpR := Op1 + Op2;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op1 * Op2:\');\r\n OpR := Op1 * Op2;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op1 + Op2 - Op1:\');\r\n OpR := Op1 + Op2 - Op1;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op2 + Op1 - Op2:\');\r\n OpR := Op2 + Op1 - Op2;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op2 + Op1 * Op2:\');\r\n OpR := Op2 + Op1 * Op2;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'(Op2 + Op1) * Op2:\');\r\n OpR := (Op2 + Op1) * Op2;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op2 / (Op1 * Op2):\');\r\n OpR := Op2 / (Op1 * Op2);\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op2 / Op1 * (Op2 / Op2) + Op1 * (Op1 + Op2):\');\r\n OpR := Op2 / Op1 * (Op2 / Op2) + Op1 * (Op1 + Op2);\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op1 - Op1:\');\r\n OpR := Op1 - Op1;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op2 - Op2:\');\r\n OpR := Op2 - Op2;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op1 / Op1:\');\r\n OpR := Op1 / Op1;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n\r\n WriteLn;\r\n\r\n WriteLn(\'Op2 / Op2:\');\r\n OpR := Op2 / Op2;\r\n WriteLn(\'&gt; ValorString = \', OpR.ValorString);\r\n WriteLn(\'&gt; ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'&gt; ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'&gt; ValorCurrency = \', FormatFloat(\'0.##########\',OpR.ValorCurrency));\r\n \r\n ReadLn;\r\nend.</code></pre>\r\n<p style=\"text-align: justify;\">Na linha <a href=\"#sdo.9\" rel=\"alternate\">9</a> declaramos o record TRegistro. Usei quatro membros de tipos simples de dados, mas poderiam ser quaisquer tipos, inclusive tipos complexos, como outros records ou mesmo classes, não importa. Abaixo da declaração destes membros está a declaração da sobrecarga dos operadores <strong>+</strong> (<strong>Add</strong>), <strong>-</strong> (<strong>Subtract</strong>), <strong>*</strong> (<strong>Multiply</strong>) e <strong>/</strong> (<strong>Divide</strong>) o que significa que poderemos usar os operadores \"+\", \"-\", \"*\" e \"/\" para operar em instâncias de TRegistro. Add, Subtract, Multiply e Divide são class operators.</p>\r\n<p style=\"text-align: justify;\">Na linha <a href=\"#sdo.22\" rel=\"alternate\">22</a> declaramos uma função utilitária que coloca parênteses em volta de um operando, caso este operando seja composto de dois outros operandos. Isso é apenas para que o resultado da concatenação do membro ValorString fique coeso com a operação realizada e nada interfere no resultado final do exemplo. Esta função, fará com que a soma <strong>Op1 + Op2 - Op1</strong>, por exemplo, seja traduzida como <strong>(Op1 + Op2) - Op1</strong>, provando que, neste caso, a soma foi realizada primeiro, ao passo que, ao fazer <strong>Op1 + (Op2 - Op1)</strong>, o resultado da função vai ser exatamente este Op1 + (Op2 - Op1), porque forçamos a precedência usando parênteses. Aliás, esta é uma excelente forma de verificar a precedência de operadores. Fica isso como dica de aprendizado.</p>\r\n<p style=\"text-align: justify;\">Na linha <a href=\"#sdo.35\" rel=\"alternate\">35</a> temos a implementação da sobrecarga do operador \"+\". Toda vez que usarmos o operador \"+\" com operandos do tipo TRegistro, este método será executado. Os parâmetros deste método são os operandos sendo \"somados\". Aqui a regra para somar os dois records é simplesmente somar os membros numéricos destes records e concatenar o membro que é uma string com o separador \"+\".</p>\r\n<p style=\"text-align: justify;\">Na linha <a href=\"#sdo.48\" rel=\"alternate\">48</a> temos a implementação da sobrecarga do operador \"/\". Toda vez que usarmos o operador \"/\" com operandos do tipo TRegistro, este método será executado. Os parâmetros deste método são os operandos sendo \"divididos\". Aqui a regra para dividir os dois records é simplesmente dividir os membros numéricos destes records. Já o membro string será concatenado utilizando o caractere \"/\" como separador.</p>\r\n<p style=\"text-align: justify;\">Na linha <a href=\"#sdo.61\" rel=\"alternate\">61</a> temos a implementação da sobrecarga do operador \"*\". Toda vez que usarmos o operador \"*\" com operandos do tipo TRegistro, este método será executado. Os parâmetros deste método são os operandos sendo \"multiplicados\". Aqui a regra para multiplicar os dois records é simplesmente multiplicar os membros numéricos destes records e concatenar o membro que é uma string com o separador \"*\".</p>\r\n<p style=\"text-align: justify;\">Na linha <a href=\"#sdo.74\" rel=\"alternate\">74</a> temos a implementação da sobrecarga do operador \"-\". Toda vez que usarmos o operador \"-\" com operandos do tipo TRegistro, este método será executado. Os parâmetros deste método são os operandos sendo \"subtraidos\". Aqui a regra para subtrair os dois records é simplesmente subtrair os membros numéricos destes records e concatenar o membro que é uma string com o separador \"-\".</p>\r\n<p style=\"text-align: justify;\">As linhas <a href=\"#sdo.92-117\" rel=\"alternate\">92 a 117</a> inicializam e exibem duas instâncias de TRegistro (<strong>Op1</strong> e <strong>Op2</strong>). Estas duas instâncias serão operadas neste exemplo</p>\r\n<p style=\"text-align: justify;\">As linhas <a href=\"#sdo.123-137\" rel=\"alternate\">123 a 137</a> operam em Op1 e Op2, somando-os e multiplicando-os. Os valores obtidos são bem óbvios. As operações ocorrem sempre atuando em dois operandos de cada vez, observando-se a precedência. Usar parênteses afeta a precedência e o valor final também muda. Alguns operadores, como \"*\" e \"/\" também afetam a precedência.</p>\r\n<p style=\"text-align: justify;\">As linhas <a href=\"#sdo.141-155\" rel=\"alternate\">141 a 155</a> operam 3 operandos, somando-os e subtraindo-os. Não há parênteses nas duas expressões apresentadas, logo as operações serão realizadas na ordem em que aparecem na expressão. O resultado final vai exibir parênteses a fim de explicitar como as operações matemáticas são realizadas. Já nas linhas <a href=\"#sdo.159-173\" rel=\"alternate\">159 a 173</a> existem duas operações com 3 operandos. Na primeira (linha <a href=\"#sdo.159\" rel=\"alternate\">159</a>) existe uma multiplicação entre o segundo e o terceiro operandos e nenhum parêntese e na segunda (linha <a href=\"#sdo.168\" rel=\"alternate\">168</a>) existe a mesma expressão, mas desta vez há um parêntese em volta da operação entre o primeiro e o segundo operandos. Na primeira expressão a matemática ensina que as multiplicações acontecem antes das somas e subtrações, mesmo sem que hajam parênteses, logo, o resultado final da operação vai apresentar parênteses em volta da operação entre o segundo e o terceiro parâmetros. Na segunda expressão, por outro lado, nós forçamos a precedência de forma que a soma acontecesse primeiro e é exatamente isso que acontece, observando o resultado final da operação.</p>\r\n<p style=\"text-align: justify;\">Tal como a soma e a subtração, as multiplicações e divisões são feitas na ordem em que aparecem. Nas linhas <a href=\"#sdo.177-182\" rel=\"alternate\">177 a 182</a> mostramos uma expressão onde usamos parênteses para forçar a multiplicação a ser efetuada primeiro. Comprove novamente usando breakpoints nos métodos \"Multiply\" e \"Divide\".</p>\r\n<p style=\"text-align: justify;\">As linhas <a href=\"#sdo.186-191\" rel=\"alternate\">186 a 191</a>, mostram uma operação mais complexa. O resultado final vai conter vários parênteses aninhados devido à forma como as operações são efetuadas. Use breakpoints em todos os métodos e entenda melhor</p>\r\n<p style=\"text-align: justify;\">As linhas <a href=\"#sdo.195-227\" rel=\"alternate\">195 a 227</a> são simples validadores que usam expressões de resultados óbvios ao se usarem operandos iguais, servindo apenas para comprovar o funcionamento da técnica de sobrecarga de operadores.</p>\r\n<h2 style=\"text-align: justify;\">E daí?</h2>\r\n<p style=\"text-align: justify;\">Se você leu tudo até aqui, você é um guerreiro mesmo, mas talvez isso prove que meu artigo foi interessante, obrigado :) Você pode estar perguntando onde isso vai ser útil e a resposta a esta pergunta depende do quanto você compreendeu o conceito da sobrecarga de operadores. Eu por exemplo, jamais usei a técnica em absolutamente nada (na verdade eu a descobri há pouco tempo) e mesmo sem nunca tê-la usado eu enxergo sua utilidade, não para mim no momento, mas para pessoas que fazem uso extensivo de objetos e precisam eventualmente realizar operações entre seus membros.</p>',1,80,'2016-09-28 00:13:22',24,'','2016-11-14 23:26:24',24,0,'0000-00-00 00:00:00','2016-10-15 19:18:47','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"images\\/add2del\\/artigos\\/ID000108\\/FullArticle.jpg\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{}',12,93,'Delphi, Addicted 2 Delphi, Operadores, Sobrecarga, Operator Overloading, Operators, Overloading','Sempre fui uma pessoa prática e como tal eu prezo muito pela celeridade do desenvolvimento de minhas soluções. Por este motivo, tudo aquilo que pode gerar menos digitação a curto ou médio prazo me atrai, e um exemplo muito bom disso é a sobrecarga de operadores. Usando esta técnica é possível economizar bastante tempo (e seus dedos), pois ela permite que estruturas complexas possam ser operadas com simples operadores matemáticos. O que você acha de somar dois records, por exemplo, ou mesmo multiplicar dois records, ou dividí-los, ou subtraílos, etc? Ficou curioso? Então leia mais.',1,4221,'{}',1,'*','',''),(109,304,'Windows Messages: A base da programação orientada a eventos','windows-messages-a-base-da-programacao-orientada-a-eventos','<p>Você já parou pra pensar como um evento é disparado no Windows? Tenho certeza que não! Certas coisas na programação que parecem mágica, normalmente são bem mais simples do que parecem. Os eventos, tão simples no uso, são uma prova disso. Eles não estão ali como por magia. Existe um responsável por fazer os eventos funcionarem e o nome deste responsável se chama <strong>Windows Message</strong>. Neste artigo eu vou explicar de forma bem simples como funciona o sistema de mensagens no Windows e vou apresentar também um uso interessante das mensagens; o compartilhamento de informações e comunicação entre aplicações totalmente distintas. Continue lendo e descubra!</p>\r\n','\r\n<p>Primeiramente quero deixar claro que nem todo evento é disparado por mensagens, mas certamente todos os eventos mais básicos, como OnClick, OnMouseMove, OnKeyPress, bem como todos aqueles que, no fim das contas, representam interação entre hardware e software ou entre softwares distintos, são.</p>\r\n<p>Por exemplo, o evento OnClick representa a interação de um hardware (mouse) com um software, logo, em algum ponto do código fonte houve a manipulação de uma mensagem que foi enviada ao seu programa quando você pressionou e soltou o <a title=\"Esse botão seria o direito, caso nas configurações do Windows os botões direito e esquerdo do mouse estiverem trocados\" href=\"#\" rel=\"bookmark\">botão esquerdo</a> do mouse em cima de um controle onde o evento OnClick está sendo manipulado.</p>\r\n<p>Apesar das mensagens de Widows serem um tema pouco abordado diretamente, saiba que, mesmo sem numca ter ouvido falar disso você as usa</p>\r\n<p>Use o programa do point blank</p>\r\n<p>Tente transferir objetos entre aplicações</p>\r\n<p>Falar do loop de mensagens</p>\r\n<p>User messages</p>',0,80,'2016-10-18 00:20:38',24,'','2016-12-09 14:51:11',24,0,'0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00','{\"image_intro\":\"\",\"image_intro_alt\":\"\",\"image_intro_caption\":\"\",\"float_intro\":\"\",\"image_fulltext\":\"\",\"image_fulltext_alt\":\"\",\"image_fulltext_caption\":\"\",\"float_fulltext\":\"\"}','{\"urla\":false,\"urlatext\":\"\",\"targeta\":\"\",\"urlb\":false,\"urlbtext\":\"\",\"targetb\":\"\",\"urlc\":false,\"urlctext\":\"\",\"targetc\":\"\"}','{}',6,94,'Delphi, Addicted 2 Delphi!, Events, Eventos, Windows Messages, WM_','',1,49,'{}',1,'*','',''),(110,306,'Uso pleno do TClientDataSet e o Modelo de Maleta','modelo-de-maleta','<p style=\"text-align: justify;\">Em um <a title=\"Quando o TClientDataSet e o TDataSetProvider s&#227;o necess&#225;rios?\" href=\"index.php/a2d-mei/articles-a2d-mei/98-devo-usar-tclientdataset-tdatasetprovider\" rel=\"alternate\">artigo anterior</a> eu expliquei quando o par TClientDataSet e TDataSetProvider s&#227;o necess&#225;rios. Neste artigo, por outro lado, ser&#227;o abordados 3 usos t&#237;picos do TClientDataSet que certamente far&#227;o voc&#234;&#160;tirar o m&#225;ximo de proveito deste que &#233; sem d&#250;vida alguma um dos componentes mais vers&#225;teis dispon&#237;veis no Delphi. O terceiro uso (Modelo de Maleta), no meu entendimento, &#233; o uso mais avan&#231;ado e &#233; a raz&#227;o da exist&#234;ncia do TClientDataSet. Se voc&#234; n&#227;o conhece o modelo de maleta, leia este artigo! Esta &#233; mais uma <strong>vers&#227;o brasileira by myself</strong> de um excelente artigo publicado por&#160;<strong>Robert E. Swart</strong>. Prepare-se para o peso desse conte&#250;do!</p>\r\n','\r\n<p style=\"text-align: justify;\">Este artigo est&#225; sendo organizado de forma diferente dos demais artigos do meu site. Ele foi dividido em p&#225;ginas, j&#225; que ser&#227;o abordados tr&#234;s assuntos. A inten&#231;&#227;o com isso &#233; facilitar a busca de informa&#231;&#245;es, fornecendo links diretos para os tr&#234;s assuntos dentro do mesmo artigo. Vou utilizar este modo de organiza&#231;&#227;o em outras publica&#231;&#245;es, pois achei bem interessante. Chega de conversa fiada e vamos ao que interessa. Primeiramente gostaria de falar a respeito do autor original do artigo.</p>\r\n<p style=\"text-align: justify;\">Robert E. Swart &#233; MVP da Embarcadero e vencedor do pr&#234;mio Spirit Of Delphi de 1999 (junto com Marco Cant&#249;). Ele &#233; autor, instrutor, revendedor, desenvolvedor,&#160;solucionador de problemas, consultor e webmaster. O artigo original encontra-se em&#160;<a href=\"http://www.drbob42.com/examines/examin64.htm\">http://www.drbob42.com/examines/examin64.htm</a>.</p>\r\n<p style=\"text-align: justify;\">O artigo foi escrito em 2004, por isso algumas tecnologias citadas s&#227;o hoje obsoletas, entretanto, o assunto de uma forma geral &#233; indispens&#225;vel&#160;para o entendimento pleno do TClientDataSet, partindo do uso mais b&#225;sico at&#233; culminar com o seu uso de forma plena no DataSnap, atrav&#233;s da habilita&#231;&#227;o do briefcase model (modelo de maleta).</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Gostaria de salientar que, a fim de explicar algumas coisas, eu utilizei notas de rodap&#233;. Recomendo fortemente a leitura dessas notas, as quais s&#227;o identificadas por n&#250;meros entre colchetes, assim: <em>[1]</em>. As explica&#231;&#245;es incluem aprofundamento das informa&#231;&#245;es e ajuda a respeito de poss&#237;veis mudan&#231;as de tecnologia e localiza&#231;&#227;o de menus no Delphi.</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Abaixo segue a vers&#227;o brasileira.</p>\r\n<hr />\r\n<p style=\"text-align: justify;\">Neste artigo, cobriremos o uso do componente TClientDataSet em tr&#234;s situa&#231;&#245;es: usando-o com o formato stand-alone MyBase, usando-o com o dbExpress, e, finalmente, usando-o como \"maleta\" no lado do cliente em aplica&#231;&#245;es DataSnap. Vou usar Delphi 7, bem como Delphi 8 (para .NET) e Delphi 2005 para ilustrar o uso do TClientDataSet em aplica&#231;&#245;es&#160;VCL (para .NET). No entanto, t&#233;cnicas semelhantes podem ser aplicadas no&#160;Kylix (no Linux) e C ++ <a title=\"Apesar de aparentemente as tecnologias usadas parecerem defasadas, todo o conte&#250;do deste artigo pode ser usado em quaisquer vers&#245;es de Delphi mais recentes. As t&#233;cnicas descritas aqui s&#227;o atemporais e extremamente &#250;teis. Eu refiz todos os exemplos descritos em Delphi XE5 com um m&#237;nimo de altera&#231;&#245;es\" href=\"#\" rel=\"bookmark\">Builder</a>.</p>\r\n<hr class=\"system-pagebreak\" title=\"O TClientDataSet stand-alone (MyBase)\" alt=\"\" />\r\n<h2>O TClientDataSet stand-alone (MyBase)</h2>\r\n<p style=\"text-align: justify;\">O TClientDataSet&#160;stand-alone &#233; a solu&#231;&#227;o perfeita para situa&#231;&#245;es em que s&#227;o necess&#225;rios execut&#225;veis aut&#244;nomos, sem a necessidade de instalar mecanismos de banco de dados adicionais ou drivers. Como o TClientDataSet carrega todos os dados na mem&#243;ria ele &#233; muito r&#225;pido, mas o tamanho das tabelas &#233; limitado &#224; quantidade de mem&#243;ria dispon&#237;vel (e o tempo de carga/persist&#234;ncia de dados&#160;tamb&#233;m aumenta correspondentemente). Al&#233;m disso, n&#227;o podemos executar consultas SQL, mas filtros e outras opera&#231;&#245;es podem ser realizadas.</p>\r\n<h3>Por que um TClientDataSet local?</h3>\r\n<p style=\"text-align: justify;\">Os benef&#237;cios da utiliza&#231;&#227;o do&#160;TClientdataSet como um DataSet local sobre, por exemplo, o BDE s&#227;o numerosos. Em primeiro lugar, o TClientdataSet est&#225; contido dentro de uma &#250;nica DLL chamada <strong>MIDAS.DLL</strong>. Apenas solt&#225;-la dentro do diret&#243;rio <a title=\"Atualmente o mais indicado &#233; usar os diret&#243;rios System32 ou SysWOW64\" href=\"#\" rel=\"bookmark\">Windows\\System</a>&#160;(ou deix&#225;-la no mesmo diret&#243;rio que o execut&#225;vel Delphi) e voc&#234; est&#225; apto a usar o componente. A partir do&#160;Delphi 6, voc&#234; pode at&#233; mesmo adicionar a unit <strong>MidasLib</strong> &#224; cl&#225;usula <strong>uses</strong> do arquivo <strong>.dpr</strong> do seu projeto. Ao fazer isso, toda a biblioteca MIDAS.DLL ser&#225; incorporada dentro do seu execut&#225;vel (que ir&#225; crescer cerca de 200K), resultando em um verdadeiro execut&#225;vel aut&#244;nomo de configura&#231;&#227;o-zero! Compare isto com a instala&#231;&#227;o do BDE no seu computador cliente. E mesmo que voc&#234; decida usar <a title=\"Ou qualquer outro tipo de componente de conex&#227;o que n&#227;o exija instala&#231;&#227;o na m&#225;quina-cliente, como o FireDAC, o UniDAC, o ZeosLib, etc.\" href=\"#\" rel=\"bookmark\">ado</a>, por exemplo, que provavelmente j&#225; estar&#225; dispon&#237;vel&#160;na m&#225;quina do seu cliente, voc&#234; precisar&#225; garantir que o banco de dados a acessar (Access, SQL Server, etc.) tamb&#233;m esteja presente, funcional e com as bibliotecas de acesso (client libraries) dispon&#237;veis. Em suma, o MIDAS.DLL &#233; provavelmente um dos \"bancos de dados\" mais f&#225;ceis de instalar que voc&#234; j&#225; viu at&#233; ent&#227;o!</p>\r\n<p style=\"text-align: justify;\">O TClientDataSet &#233; tamb&#233;m uma das implementa&#231;&#245;es de DataSet mais r&#225;pidas que existem. Classifica&#231;&#227;o e filtragem s&#227;o feitas com uma velocidade impressionante. Com toda essa velocidade ent&#227;o ele &#233; um componente perfeito, n&#227;o &#233;? N&#227;o &#233; bem assim! <strong>Essa velocidade toda tem um custo alto</strong> e &#233; tamb&#233;m&#160;uma das desvantagens potenciais do TClientDataSet.&#160;<strong>Tudo &#233; gerenciado na mem&#243;ria</strong>, e&#160;cada opera&#231;&#227;o, como classifica&#231;&#227;o, filtragem ou busca tamb&#233;m &#233; feito na mem&#243;ria. Isso explica a velocidade, mas tamb&#233;m significa que <strong>um TClientDataSet com uma grande quantidade de dados exige&#160;uma grande quantidade de mem&#243;ria em sua m&#225;quina</strong>.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>Alimentando um TClientDataSet local...</h3>\r\n<p style=\"text-align: justify;\">A maneira mais f&#225;cil de carregar um TClientDataSet em tempo de design, &#233; clicar com o bot&#227;o direito no componente TClientDataSet e selecionar a op&#231;&#227;o do menu pop-up \"<strong>Assign local data</strong>\". Isto ir&#225; mostrar uma caixa de di&#225;logo que lista todos os conjuntos de dados (tabelas, consultas, etc.)&#160;dispon&#237;veis no TForm&#160;atual ou em um TDataModule associado. Ao escolher um item nessa lista e pressionar OK, todos os dados do conjunto de dados escolhido&#160;ser&#227;o atribu&#237;dos ao TClientDataSet e voc&#234; poder&#225; ent&#227;o remover o conjunto de dados&#160;fonte do TForm ou TDataModule, ficando apenas com um TClientDataSet stand-alone que cont&#233;m todos os dados do conjunto de dados de origem. Note que voc&#234; ainda precisar&#225; remover a unit&#160;DBTables caso voc&#234; esteja usando o BDE e quiser fazer sua aplica&#231;&#227;o totalmente independente do BDE.</p>\r\n<p style=\"text-align: justify;\">Al&#233;m de usar a op&#231;&#227;o \"Assign Local Data\", um TClientDataSet tamb&#233;m pode carregar e armazenar suas informa&#231;&#245;es no disco. Isto &#233; vis&#237;vel em tempo de design pelas op&#231;&#245;es \"<strong>Load From File</strong>\" e \"<strong>Save To File</strong>\" do menu <a title=\"Os nomes dos itens deste menu variam de acordo com o Delphi sendo utilizado. No Delphi 2006, por exemplo, existem os itens &lt;b&gt;Load from MyBase table...&lt;/b&gt;, &lt;b&gt;Save to MyBase XML table...&lt;/b&gt;, &lt;b&gt;Save to MyBase XML UTF8 Table...&lt;/b&gt; e &lt;b&gt;Save to Binary MyBase file...&lt;/b&gt;\" href=\"#\" rel=\"bookmark\">pop-up</a>. O componente TClientDataSet em si cont&#233;m tamb&#233;m estes m&#233;todos acess&#237;veis em tempo de execu&#231;&#227;o, bem como os m&#233;todos <strong>LoadFromStream</strong> e <strong>SaveToStream</strong>, que voc&#234; pode redirecionar a diferentes tipos de fluxos (como um TMemoryStream, imediatamente&#160;antes de enviar esse fluxo atrav&#233;s de uma conex&#227;o socket, por exemplo).</p>\r\n<p style=\"text-align: justify;\">O TClientDataSet pode carregar e armazenar dois tipos de formato de dados. O primeiro &#233; normalmente chamado formato \"<strong>cds</strong>\", e &#233; o formato bin&#225;rio interno (e n&#227;o documentado). Pequeno, nativo e quase imposs&#237;vel de&#160;compartilhar (exceto com outros componentes TClientDataSet de Delphi 5 e superior, C ++ Builder 5 e superiores, e Kylix). O segundo formato de dados que o TClientDataSet suporta&#160;&#233; o <strong>XML,</strong>&#160;e todos n&#243;s sabemos que arquivos&#160;XML s&#227;o abertos, independentes de plataforma&#160;e port&#225;teis, no entanto, o formato XML que &#233; usado pelo TClientDataSet ainda &#233; um formato propriet&#225;rio&#160;definido originalmente pela Borland, por isso n&#227;o &#233; f&#225;cil us&#225;-lo com um conjunto de dados ADO, por exemplo.</p>\r\n<h3>Usando um TClientDataSet local...</h3>\r\n<p style=\"text-align: justify;\">Uma vez que um TClientDataSet local &#233; preenchido com dados, n&#243;s podemos navegar por ele como qualquer outro conjunto de dados, no entanto h&#225; uma diferen&#231;a que pode ser um benef&#237;cio ou&#160;uma maldi&#231;&#227;o (se voc&#234; n&#227;o estiver ciente disso). A principal diferen&#231;a entre o TClientDataSet e outros conjuntos de dados tradicionais (TTable ou similares) &#233; o fato de o TClientDataSet n&#227;o salvar&#160;automaticamente o seu conte&#250;do em disco. E mesmo se isso acontecer, ele s&#243; salva as altera&#231;&#245;es que tiverem sido realizadas e n&#227;o o conjunto de dados completos (dados antigos mais dados alterados).</p>\r\n<p style=\"text-align: justify;\">O que isso significa exatamente, e como podemos fazer melhor uso desta funcionalidade? Primeiro de tudo, vamos dar uma olhada nas capacidades de salvamento do&#160;TClientDataSet. <strong>Se os dados dentro do TClientDataSet s&#227;o&#160;carregados por quaisquer outros meios que n&#227;o a propriedade FileName do TClientDataSet, ent&#227;o ele, obviamente, n&#227;o sabe onde guardar os dados que tenham sido modificados</strong> (os dados originais devem ter sido&#160;carregados&#160;a partir do arquivo .dfm). <strong>Se a propriedade FileName for&#160;usada, ent&#227;o o TClientDataSet vai salvar o seu conte&#250;do de volta para o arquivo quando ele for&#160;explicitamente desativado (close) (ou destru&#237;do)</strong>. No entanto, em algumas situa&#231;&#245;es, pode ser uma boa ideia chamar explicitamente o m&#233;todo SaveToFile (assim como voc&#234; teria de usar LoadFromFile para carregar os novos dados de volta).</p>\r\n<p style=\"text-align: justify;\">Agora, se voc&#234; usar LoadFromFile no in&#237;cio da sua aplica&#231;&#227;o, e SaveToFile no final da mesma, ent&#227;o voc&#234; vai&#160;perceber que o arquivo externo (com o conte&#250;do do TClientDataset) cresce a uma taxa mais elevada do que seria esperado&#160;de poucas mudan&#231;as e poss&#237;veis adi&#231;&#245;es&#160;que voc&#234; fa&#231;a no TClientDataSet. Algo est&#225; acontecendo dentro desse arquivo e se voc&#234; o salvar&#160;no formato XML, ent&#227;o voc&#234; descobrir&#225; rapidamente que todas as altera&#231;&#245;es individuais s&#227;o salvas, tanto com o valor \"original\" como com o valor \"alterado\" de cada registro. Isto significa que mesmo poucas mudan&#231;as v&#227;o encher o banco de dados rapidamente com v&#225;rias vers&#245;es de registros com altera&#231;&#245;es, que ocupam mais espa&#231;o do que as pr&#243;prias altera&#231;&#245;es em si (ou as altera&#231;&#245;es aplicadas ao conjunto de dados).</p>\r\n<p style=\"text-align: justify;\">Por que isso &#233; feito? Basicamente, para permitir que o TClientDataSet trabalhe em um ambiente multi-tier (e multi-utilizador). Em um ambiente multi-camadas, o TClientDataSet precisa chamar o m&#233;todo ApplyUpdates, o qual envia as atualiza&#231;&#245;es pendentes para a camada de conjunto de dados remoto (middleware) e isto tem de incluir tanto a vers&#227;o original dos registros, como as altera&#231;&#245;es que ser&#227;o aplicadas. Se a vers&#227;o original de um registro n&#227;o coincide com a vers&#227;o atual do registro (persistido em banco de dados), a atualiza&#231;&#227;o pode n&#227;o ser aplicada. Este &#233; um problema t&#237;pico&#160;de ambientes multi-usu&#225;rios que precisa&#160;ser resolvido para evitar problemas de integridade de dados, mas ao usar o&#160;TClientDataSet como uma solu&#231;&#227;o stand-alone&#160;este comportamento &#233; muito indesejado.</p>\r\n<p style=\"text-align: justify;\">E fica ainda pior quando voc&#234; percebe que o TClientDataSet cont&#233;m o valor original de todos os registros, bem como todas as altera&#231;&#245;es, o que significa que quando voc&#234; carregar o arquivo externo, ter&#225; que voltar a aplicar todas as altera&#231;&#245;es e isto levar&#225; tempo, tornando a carga mais e mais lenta com o tempo, &#224; medida que mais e mais mudan&#231;as s&#227;o aplicadas.</p>\r\n<h3>MegeChangeLog</h3>\r\n<p style=\"text-align: justify;\">Felizmente existe um m&#233;todo especial chamado <strong>MergeChangeLog</strong>, o qual faz, tal como o nome sugere, a mesclagem de todas as altera&#231;&#245;es contidas no TClientDataSet, o que resulta em um arquivo bem pequeno novamente (com apenas os registros e todas as mudan&#231;as efetivas aplicadas neles). Obviamente este m&#233;todo nunca deve ser executado em um ambiente multi-camadas, pois ele vai quebrar todas as poss&#237;veis chamadas ao m&#233;todo ApplyUpdates que voc&#234; queira fazer. Contudo, em um ambiente&#160;de camada &#250;nica local, este m&#233;todo &#233; estritamente necess&#225;rio, pois ele diminui o tamanho (e acelera o carregamento) da representa&#231;&#227;o local do TClientDataSet.</p>\r\n<p style=\"text-align: justify;\">Antes de executar o m&#233;todo MergeChangeLog, &#233; recomend&#225;vel verificar o valor de <strong>ChangeCount</strong>, que representa o n&#250;mero de altera&#231;&#245;es que est&#227;o atualmente dispon&#237;veis no TClientDataSet. Apenas se ChangeCount for maior que zero &#233; que voc&#234; deve executar o m&#233;todo MergeChangeLog, do contr&#225;rio isso seria apenas perda de tempo.</p>\r\n<p style=\"text-align: justify;\">Como uma &#250;ltima dica, voc&#234; pode querer considerar o fato de que um log de modifica&#231;&#245;es inclui a habilidade para \"desfazer\" altera&#231;&#245;es registro a registro usando os m&#233;todos <strong>UndoLastChange</strong>, <strong>CancelUpdates</strong> ou <strong>RevertRecord</strong>. Leia a ajuda do Delphi para maiores detalhes. Estes m&#233;todos podem tamb&#233;m ser usados em um ambiente multi-camadas, claro, antes de se executar um <strong>ApplyUpdates</strong>.</p>\r\n<hr class=\"system-pagebreak\" title=\"O TClientDataSet e o dbExpress\" alt=\"\" />\r\n<h2>O TClientDataSet e o dbExpress</h2>\r\n<p style=\"text-align: justify;\">Quando combinado com o <strong>dbExpress (DBX)</strong>, o TClientDataSet pode ser visto como um&#160;\"cache\" (ou maleta), no qual os cursores unidirecionais podem armazenar seus dados, assim os usu&#225;rios podem visualiz&#225;-los como&#160;um conjunto de registros bidirecionais. Isto habilita capacidades SQL, pois &#233; o DBX quem est&#225; fornecendo os dados ao TClientDataSet. N&#243;s n&#227;o precisamos mais nos preocupar com armazenamento local, apenas precisamos executar ApplyUpdates para enviar as altera&#231;&#245;es ao banco de dados no qual o DBX est&#225; conectado.</p>\r\n<h3>O que &#233; o dbExpress?</h3>\r\n<p style=\"text-align: justify;\">O DBX&#160;<strong>&#233; uma arquitetura de acesso a dados multi-plataformas, leve, r&#225;pida e aberta</strong>. Um driver DBX precisa implementar algumas interfaces para obter metadados, executar consultas SQL ou stored procedures, e retornar um cursor unidirecional. Voltaremos a falar disso em breve.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>Personalizando o dbExpress</h3>\r\n<p style=\"text-align: justify;\">Como dito anteriormente o DBX foi criado como uma arquitetura de acesso a dados aberta, significando que qualquer um pode escrever um driver compat&#237;vel (ou seria compilante?) com o DBX&#160;para uso com o Kylix, Delphi ou C++ Builder. Um artigo sobre o funcionamento intr&#237;nseco do DBX, escrito por Ramesh Theivendran, o arquiteto do DBX, foi publicado no site Borland Community. Apesar de este artigo ser apenas um esbo&#231;o preliminar, ele deixou claro que qualquer um pode escrever um driver para o dbExpress.</p>\r\n<p style=\"text-align: justify;\">Como um exemplo pr&#225;tico, a EasySoft desenvolveu o&#160;\"dbExpress Gateway for ODBC\", que pode ser usado para conectar ao ODBC do UNIX e, via \"ponte ODBC-ODBC\", ele tamb&#233;m &#233; capaz de conectar-se a um SQL Server remoto, a um banco de dados Access ou mesmo outros drivers ODBC do Windows.</p>\r\n<h3>Componentes</h3>\r\n<p style=\"text-align: justify;\">A paleta dbExpress cont&#233;m sete componentes: TSQLConnection, TSQLDataSet, TSQLQuery, TSQLStoredProc, TSQLTable, TSQLMonitor e TSQLClientDataSet</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000110/CLDS1.png\" width=\"412\" height=\"311\" /></p>\r\n<h3>TSQLConnection</h3>\r\n<p style=\"text-align: justify;\">O componente TSQLConnection &#233; literalmente a conex&#227;o entre os drivers do DBX e os outros componentes da paleta DBX. Ele &#233; o meio de liga&#231;&#227;o entre a aplica&#231;&#227;o e drivers que possibilitam o acesso aos bancos de dados. Se voc&#234; soltar este componente em um TForm ou TDataModule, voc&#234; ver&#225; apenas 12 propriedades e aquela que &#233; provavelmente a mais usada &#233; a propriedade <strong>ConnectionName</strong>, que pode ser atribu&#237;da a um dos valores da lista apresentada. Se voc&#234; selecionar <strong>IBConnection</strong> ent&#227;o a propriedade <strong>DriverName</strong> ter&#225; o valor \"<strong>INTERBASE</strong>\", a propriedade <strong>GetDriverFunc</strong> ter&#225; o valor \"<strong>getSQLDriverINTERBASE</strong>\" a propriedade <strong>LibraryName</strong> ter&#225; o valor \"<strong>dbxint30.dll</strong>\" e a propriedade <strong>VendorLib</strong> ter&#225; o valor \"<strong>gds32.dll</strong>\". Tudo foi automaticamente selecionado baseado no valor IBConnection, selecionado em ConnectionName.</p>\r\n<p style=\"text-align: justify;\">Voc&#234; pode abrir a propriedade Params e editar os valores dos par&#226;metros. Estes s&#227;o tamb&#233;m automaticamente preenchidos, ali&#225;s, quando voc&#234; seleciona um valor para a propriedade ConnectionName. Se voc&#234; n&#227;o quiser que isso aconte&#231;a, por exemplo, quando estiver escrevendo c&#243;digo n&#227;o visual de acesso a banco de dados e voc&#234; quiser informar seus pr&#243;prios valores para os par&#226;metros, voc&#234; pode configurar a propriedade LoadParamsOnConnection como false.</p>\r\n<p style=\"text-align: justify;\">Se voc&#234;&#160;der duplo clique no componente TSQLConnection, voc&#234; ver&#225; as configura&#231;&#245;es de conex&#227;o para cada valor poss&#237;vel da propriedade&#160;ConnectionName.</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"images/add2del/artigos/ID000110/CLDS2.png\" width=\"712\" height=\"412\" /></p>\r\n<p style=\"text-align: justify;\">Nesta tela tamb&#233;m &#233; poss&#237;vel alterar as configura&#231;&#245;es atuais e criar novas configura&#231;&#245;es, de forma que seja poss&#237;vel selecion&#225;-la na propriedade ConnectionName.</p>\r\n<p style=\"text-align: justify;\">Uma vez que tudo tenha sido configurado de forma adequada, voc&#234; pode configurar a pripriedade <strong>Connected</strong> como true e, ou obter uma mensagem de erro de conex&#227;o, ou ver a propriedade simplesmente mudando para true, o que indicaria sucesso na conex&#227;o.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>TSQLDataSet</h3>\r\n<p style=\"text-align: justify;\">Uma vez que o componente TSQLConnection esteja conectado, voc&#234; poder&#225; usar qualquer um dos&#160;outros componentes DBX dispon&#237;veis, como o TSQLDataSet, que &#233; o mais \"geral\" destes componentes. Sempre comece por configurar a propriedade SQLConnection deste componente para um TSQLConnection dispon&#237;vel. Os componentes TSQLQuery, TSQLStoredProc e TSQLTable podem ser vistos como inst&#226;ncias especiais de TSQLDataSet. De fato, isso me lembra muito o <strong>ADOExpress </strong>(<strong>dbGo</strong>), no qual o componente <strong>TADODataSet</strong> &#233; o \"pai\" de <strong>TADOQuery</strong>, <strong>TADOStoredProc</strong> e <strong>TADOTable</strong>. Na verdade o n&#250;cleo dos componentes TxxxDataSet de ambos, dbGo&#160;e DBX, compartilham as propriedades <strong>CommandType</strong> e <strong>CommandText</strong>, com as quais voc&#234; pode determinar o subtipo do componente. Se voc&#234; configurar o valor de CommandType para <strong>ctQuery</strong>, ent&#227;o a propriedade CommandText &#233; interpretada como uma consulta SQL. Se voc&#234; configurar o valor de CommandType para <strong>ctStoredProc</strong>, ent&#227;o a propriedade CommandText especifica o nome de um stored procedure e, finalmente, se CommandType for configurado como <strong>ctTable</strong>, ent&#227;o CommandText deve conter o nome de uma tabela.</p>\r\n<p style=\"text-align: justify;\">Em nosso caso, usando o componente geral TSQLDataSet, n&#243;s podemos configurar CommandType como ctTable e CommandText como \"customer\" e com isso selecionar a tabela \"customer\". Se voc&#234; configurar a propriedade Active como true, voc&#234; obter&#225; os dados atuais em tempo de projeto e, se voc&#234; configurar a propriedade LoginPrompt do componente TSQLConnection como false, voc&#234; nem mesmo ver&#225; a tela de login. Esse &#233; um comportamento padr&#227;o de todos os componentes de acesso a dados do Delphi. Nada de especial, nada diferente. Ainda...</p>\r\n<h3>Unidirecional!?</h3>\r\n<p style=\"text-align: justify;\">Agora n&#243;s podemos direcionar o foco para a paleta <strong>Data Controls</strong> e come&#231;ar a usar um de seus componentes para exibir os dados que recebemos do TSQLDataSet ativo. Note que n&#243;s n&#227;o podemos usar todos estes componentes agora sem algumas considera&#231;&#245;es especiais. Este &#233; o ponto onde a maior diferen&#231;a entre as arquiteturas do BDE e do DBX est&#225; presente. O componente TSQLDataSet e seus componentes derivados retornam um <strong>cursor unidirecional</strong>. Isso significa que voc&#234; pode mover-se apenas para frente e nunca para tr&#225;s e isso n&#227;o &#233; nada &#250;til quando pretendemos usar, por exemplo, um <strong>TDBGrid</strong> (n&#243;s vemos apenas um registro de cada vez!) e muito menos quando usamos um <strong>TDBNavigator</strong>, porque clicando nos bot&#245;es <strong>Back</strong> ou <strong>First</strong> ir&#225; invariavelmente levantar uma exce&#231;&#227;o!</p>\r\n<p style=\"text-align: justify;\">Por que um cursor unidirecional? Bem, a resposta &#243;bvia &#233; <strong>velocidade</strong>. O BDE nunca foi nosso melhor amigo (podemos consider&#225;-lo um bom amigo, ou um conhecido amig&#225;vel), mas ele nos ajudou com necessidades simples e pequenas dos bancos de dados. Infelizmente o ovehead e os requisitos gerais de mem&#243;ria do BDE n&#227;o s&#227;o pequenos e as tabelas do BDE nunca foram conhecidos por suas \"velocidades incr&#237;veis\". Esta era uma &#225;rea onde a Borland procurou mostrar algumas melhorias reais. A nova arquitetura chamada de&#160;dbExpress foi desenhada com isso em mente e, consequentemente, os cursores unidirecionais como <em>Result Sets</em>, sem o ovehead causado pelo gerenciamento de metadados ou armazenamento em cache dos dados.</p>\r\n<p style=\"text-align: justify;\">Um cursor unidirecional &#233; especialmente &#250;til quando voc&#234; apenas precisa ver os resultados uma vez, ou precisa percorrer os resultados do in&#237;cio ao fim (novamente, uma s&#243; vez),&#160;por exemplo, em um loop <strong>while not Eof</strong>, ao processar os resultados de uma query ou stored procedure. Situa&#231;&#245;es do mundo real onde isso &#233; &#250;til incluem a gera&#231;&#227;o de relat&#243;rios e aplica&#231;&#245;es para servidores web que produzem dinamicamente p&#225;ginas html como resultado.</p>\r\n<p style=\"text-align: justify;\">Mas especialmente quando combinado com componentes visuais de acesso a dados, voc&#234; rapidamente percebe que em ambientes gr&#225;ficos os usu&#225;rios certamente v&#227;o querer navegar em qualquer dire&#231;&#227;o dentro de Result Sets. Ent&#227;o, voc&#234; precisa, de alguma forma, colocar os registros em cache a fim de permitir sua exibi&#231;&#227;o em um TDBGrid e tamb&#233;m a navega&#231;&#227;o em qualquer dire&#231;&#227;o. &#201; a&#237; onde o TClientDataSet entra em a&#231;&#227;o. &#201; totalmente poss&#237;vel e pertinente usar um <strong>TDataSetProvider</strong> ligado ao TSQLDataSet e ent&#227;o usar um TClientDataSet para obter os dados do TDataSetProvider. O resultado &#233; um TClientDataSet que obt&#233;m registros (uma vez) de uma fonte unidirecional, o TSQLDataSet. O TDataSetProvider &#233; usado apenas como um meio local de transporte de dados.</p>\r\n<h3>Migra&#231;&#227;o de tecnologia</h3>\r\n<p style=\"text-align: justify;\">O fato de o TClientDataSet estar dispon&#237;vel tanto no Delphi quanto no Kylix significa que existe uma forma simples e r&#225;pida de migrar tabelas de bancos de dados locais, como tabelas em Paradox ou dBASE. Este &#233; o primeiro passo&#160;a ser feito para migrar do BDE para o DBX: migra&#231;&#227;o de dados. O segundo&#160;passo &#233; migrar a aplica&#231;&#227;o em si.</p>\r\n<p style=\"text-align: justify;\">Para realizar o primeiro passo devemos migrar os dados do BDE para o formato nativo do TClientDataSet. Considere o c&#243;digo abaixo de uma aplica&#231;&#227;o chamada <strong>dbAlias</strong> que eu escrevi. Esta aplica&#231;&#227;o vai converter todas as tabelas acessadas por um alias (passado via par&#226;metro) para arquivos .cds:</p>\r\n<pre class=\"language-pascal\"><code>{$APPTYPE CONSOLE}\r\nprogram dbAlias;\r\nuses\r\n Classes, SysUtils, DB, DBTables, Provider, DBClient;\r\nvar\r\n i: Integer;\r\n TableNames: TStringList;\r\n Table: TTable;\r\n DataSetProvider: TDataSetProvider;\r\n ClientDataSet: TClientDataSet;\r\nbegin\r\n TableNames := TStringList.Create;\r\n with TSession.Create(nil) do\r\n try\r\n AutoSessionName := True;\r\n GetTableNames(ParamStr(1), \'\', True, False, TableNames);\r\n finally\r\n Free\r\n end {TSession};\r\n Table := TTable.Create(nil);\r\n DataSetProvider := TDataSetProvider.Create(nil);\r\n ClientDataSet := TClientDataSet.Create(nil);\r\n try\r\n Table.DatabaseName := ParamStr(1);\r\n for i : =0 to Pred(TableNames.Count) do\r\n begin\r\n writeln(Table.TableName);\r\n Table.TableName := TableNames[i];\r\n Table.Open;\r\n DataSetProvider.DataSet := Table;\r\n ClientDataSet.SetProvider(DataSetProvider);\r\n ClientDataSet.Open;\r\n ClientDataSet.SaveToFile(ChangeFileExt(Table.TableName,\'.cds\'));\r\n ClientDataSet.Close;\r\n Table.Close\r\n end\r\n finally\r\n Table.Free\r\n end\r\nend.</code></pre>\r\n<p>Esta &#233; uma forma r&#225;pida e grosseira de <a title=\"Esta aplica&#231;&#227;o n&#227;o foi testada por mim, contudo mesmo que ela n&#227;o se aplique mais, a ideia que dela se pode tirar &#233; &#250;til, at&#233; mesmo como forma de backup local, por exemplo\" href=\"#\" rel=\"bookmark\">converter</a> suas tabelas Paradox e dBASE referenciadas por&#160;um alias do BDE para arquivos cds, os quais podem ser carregados novamente por componentes TClientDataSet em uma outra aplica&#231;&#227;o. O uso que voc&#234; far&#225; desses dados depende obviamente do seu sistema.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>Cursores unidirecionais e conjuntos de dados somente leitura?</h3>\r\n<p style=\"text-align: justify;\">Tanto o Delphi 6 como o Delphi 7 e o Kylix usam a nova camada de acesso a dados independente de plataforma chamada de dbExpress, mas como tirar o m&#225;ximo proveito dessa nova tecnologia?</p>\r\n<p style=\"text-align: justify;\">Vou mostrar agora que, ao usar o dbExpress, n&#243;s precisamos adotar uma nova forma de olhar os dados (especialmente na hora de salv&#225;-los), porque o DBX disp&#245;e apenas de datasets somente leitura com cursores unidirecionais, por isso, n&#227;o h&#225; como confirmar nossas altera&#231;&#245;es automaticamente mediante um simples post em seus componentes.</p>\r\n<p style=\"text-align: justify;\">Para ilustrar este ponto, e mostrar como proceder de forma correta, vamos&#160;construir uma aplica&#231;&#227;o usando o DBX. Primeiro inicialize um novo projeto Win32 de forma usual. Solte um componente TSQLConnection no TForm dispon&#237;vel e configure sua propriedade ConnectionName para IBCONNECTION (ou a de sua prefer&#234;ncia). Voc&#234; pode&#160;dar duplo clique no TSQLConnection para iniciar o editor de conex&#245;es e garantir que todas as propriedades do ConnectionName escolhido estejam corretas. Em seguida solte um componente TSQLTable e configure sua propriedade SQLConnection como SQLConnection1, que deve ser o nome do TSQLConnection. Selecione uma das tabelas dispon&#237;veis na propriedade <strong>TableName</strong>. Neste ponto n&#243;s temos um dataset somente leitura e unidirecional (o qual suporta movimenta&#231;&#227;o de registros apenas um passo de cada vez para frente ou de volta ao in&#237;cio, mas nenhuma outra opera&#231;&#227;o). Esse comportamento &#233; excelente caso se pretenda usar um componente TDataSetTableProducer, onde precisamos andar para frente no resultset apenas uma vez, mas n&#227;o &#233; nada &#250;til em outras situa&#231;&#245;es.</p>\r\n<h3>TClientDataSet</h3>\r\n<p style=\"text-align: justify;\">Para permitir a exibi&#231;&#227;o das informa&#231;&#245;es contidas no TSQLTable (ou qualquer dataset DBX) precisamos armazen&#225;-los dentro de um TClientDataSet usando um TDataSetProvider como \"conector\". Ent&#227;o, solte no TForm um componente TDataSetProvider e um TClientDataSet, os quais se encontram na paleta Data Access. Atribua o componente TSQLtable &#224; propridade DataSet do TDataSetProvider e ent&#227;o atribua o nome do TDataSetProvider &#224; propriedade ProviderName do TClientDataSet. No momento em que voc&#234; abrir o TClientDataSet (por exemplo, configurando sua propriedade Active como True) o conte&#250;do de TSQLTable ser&#225; percorrido (apenas uma vez) e os registros ser&#227;o providos ao TClientDataSet, o qual os armazenar&#225; daquele momento em diante. N&#243;s podemos agora usar um TDataSource e (por exemplo) um TDBGrid para exibir o conte&#250;do dispon&#237;vel no TClientDataSet.</p>\r\n<h3>Manipula&#231;&#227;o de dados</h3>\r\n<p style=\"text-align: justify;\">O problema da manipula&#231;&#227;o de dados ocorre quando executamos a aplica&#231;&#227;o, fazemos algumas altera&#231;&#245;es em alguns campos e registros e sa&#237;mos da mesma. Quando abrimos a aplica&#231;&#227;o novamente n&#243;s vemos apenas os valores antigos! O&#160;TClientDataSet &#233; perfeito para colocar dados em cache, mas n&#227;o &#233; capaz de <a title=\"A palavra &quot;atualizar&quot; neste contexto, significa qualquer opera&#231;&#227;o de banco de dados, e n&#227;o apenas as atualiza&#231;&#245;es propriamente ditas\" href=\"#\" rel=\"bookmark\">atualizar</a>&#160;o banco de dados para n&#243;s de forma autom&#225;tica.</p>\r\n<p style=\"text-align: justify;\">Como o componente TSQLTable &#233; somente leitura e n&#227;o &#233; capaz de manipular de forma tradicional um banco de dados (usando m&#233;todos&#160;<strong>insert</strong>, <strong>edit</strong>, <strong>delete</strong> e <strong>post</strong>), precisamos usar o TClientDataSet, o qual pode usar o TDataSetProvider para realizar estas opera&#231;&#245;es diretamente. O TDataSetProvider, &#233; capaz de gerar os comandos SQL de inser&#231;&#227;o, atualiza&#231;&#227;o e exclus&#227;o e envi&#225;-los diretamente ao banco de dados, sem usar o m&#233;todos de alto n&#237;vel, indispon&#237;veis nos componentes do DBX.</p>\r\n<p style=\"text-align: justify;\">Ent&#227;o, para que as opera&#231;&#245;es de banco de dados sejam executadas, precisamos executar o m&#233;todo ApplyUpdates do TClientDataSet. Podemos colocar no TForm um&#160;TButton, configurar sua propriedade Caption para \"Aplicar Altera&#231;&#245;es\" e, dentro do manipulador do evento OnClick desse bot&#227;o devemos escrever apenas uma linha de c&#243;digo:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>procedure TForm1.Button1Click(Sender: TObject);\r\nbegin\r\n ClientDataSet1.ApplyUpdates(0)\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Este simples comando vai enviar todas as atualiza&#231;&#245;es pendentes no TClientDataSet para o banco de dados acessado pelo DBX. Caso um erro aconte&#231;a no momento da execu&#231;&#227;o dos comandos -- por exemplo, quando um registro alterado por n&#243;s j&#225; tiver sido alterado por outro usu&#225;rio --, um erro conhecido como <strong>Reconcile</strong> (<strong>EReconcileError</strong>) ser&#225; gerado e poder&#225; ser manipulado no evento <strong>OnReconcileError</strong> do TClientDataSet. Neste evento o usu&#225;rio poder&#225; decidir como o erro ser&#225; corrigido.&#160;Maiores detalhes de como manipular erros EReconcileError ser&#227;o dados mais adiante na se&#231;&#227;o sobre DataSnap deste mesmo artigo.</p>\r\n<h3>Automatizando o ApplyUpdates</h3>\r\n<p style=\"text-align: justify;\">Apesar da solu&#231;&#227;o apresentada funcionar, voc&#234; n&#227;o pode realisticamente esperar que seus clientes e usu&#225;rios finais se lembrem de clicar no bot&#227;o \"Aplicar Altera&#231;&#245;es\" quando eles quiserem simplesmente salvar seus trabalhos. N&#243;s podemos facilitar a vida dos nossos usu&#225;rios executando o m&#233;todo ApplyUpdates de forma autom&#225;tica no evento <strong>AfterPost</strong> do TClientDataSet:</p>\r\n<pre class=\"line-numbers language-pascal\"><code>procedure TForm1.ClientDataSet1AfterPost(DataSet: TDataSet);\r\nbegin\r\n (DataSet as TClientDataSet).ApplyUpdates(0)\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Apesar de voc&#234; estar se sentindo bem com essa solu&#231;&#227;o, ainda existem situa&#231;&#245;es que precisamos considerar, por exemplo, ao excluir um registro n&#227;o existe m&#233;todo Post, logo, o evento AfterPost n&#227;o vai funcionar neste caso e por isso precisamos colocar tamb&#233;m o mesmo c&#243;digo no evento <strong>AfterDelete</strong>.</p>\r\n<p style=\"text-align: justify;\">Finalmente, quando voc&#234; fecha sua aplica&#231;&#227;o imediatamente ap&#243;s sua &#250;ltima altera&#231;&#227;o, mas antes de executar um Post, por exemplo, se voc&#234; alterou o valor em um <strong>TDBEdit</strong>, mas n&#227;o confirmou sua a&#231;&#227;o (Post), ent&#227;o voc&#234; pode querer que esta altera&#231;&#227;o seja confirmada tamb&#233;m. Isso significa que voc&#234; precisa executar novamente a mesma linha de c&#243;digo descrita anteriormente, nos eventos <strong>OnDestroy</strong> ou <strong>OnClose</strong> de seu TForm, TDataModule ou TFrame (ou qualquer outros cont&#234;iner que voc&#234; esteja usando para seu TClientDataSet)</p>\r\n<hr class=\"system-pagebreak\" title=\"O papel do TClientDataSet no DataSnap\" alt=\"\" />\r\n<h2>O papel do TClientDataSet no DataSnap</h2>\r\n<p style=\"text-align: justify;\">A terceira solu&#231;&#227;o, na qual usaremos a maior parte de nosso tempo aqui, posiciona o TClientDataSet como uma \"maleta\" do lado do cliente em uma aplica&#231;&#227;o DataSnap multicamadas. Isso significa que n&#243;s podemos desconectar a aplica&#231;&#227;o cliente e armazenar os dados localmente em arquivo, no formato bin&#225;rio MyBase. N&#243;s podemos, sempre que quisermos, recarregar esses dados e continuar o trabalho localmente at&#233; quando uma conex&#227;o com o servidor DataSnap for restabelecida, permitindo que n&#243;s possamos enviar nossas altera&#231;&#245;es de volta ao servidor, mediante o uso do m&#233;todo ApplyUpdates.</p>\r\n<p style=\"text-align: justify;\">Vale salientar que n&#243;s <strong>n&#227;o focaremos</strong> nos detalhes do lado do servidor da arquitetura DataSnap, nem nos protocolos de comunica&#231;&#227;o utilizados nesta arquitetura. O foco ser&#225; o TClientDataSet e suas habilidades de conex&#227;o com provedores locais ou remotos e a aplica&#231;&#227;o de atualiza&#231;&#245;es ao middleware.</p>\r\n<p style=\"text-align: justify;\">Por fim, a aplica&#231;&#227;o de atualiza&#231;&#245;es (ApplyUpdates) pode resultar em erros de reconcilia&#231;&#227;o (<strong>Reconcile Errors</strong>), os quais normalmente acontecem quando um ou mais usu&#225;rios realizaram altera&#231;&#245;es conflitantes em um mesmo campo de um mesmo registro. Veremos ent&#227;o como podemos detectar e responder a estes erros usando a caixa de di&#225;logo padr&#227;o para erros de reconcilia&#231;&#227;o fornecida junto com o Delphi.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>Arquitetura de banco de dados multicamadas</h3>\r\n<p style=\"text-align: justify;\">Usando uma arquitetura de banco de dados multicamadas voc&#234; pode particionar uma aplica&#231;&#227;o de forma que ela possa acessar um banco de dados sem precisar de bibliotecas ou programas adicionais nas m&#225;quinas locais. Esta arquitetura tamb&#233;m permite que voc&#234;&#160;centralize as regras de neg&#243;cio e processos, al&#233;m de distribuir o processamento atrav&#233;s da rede. O DataSnap suporta uma tecnologia de 3 camadas, a qual em sua forma cl&#225;ssica consiste de:</p>\r\n<ul>\r\n<li style=\"text-align: justify;\">Um <strong>servidor de banco de dados</strong> em uma m&#225;quina (servidor)</li>\r\n<li style=\"text-align: justify;\">Um <strong>servidor de aplica&#231;&#227;o</strong> em uma segunda m&#225;quina (camada do meio)</li>\r\n<li style=\"text-align: justify;\">Um <strong>cliente magro</strong> (thin client) em uma terceira m&#225;quina (cliente)</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">O servidor de banco de dados pode ser qualquer um de sua prefer&#234;ncia, InterBase, Postgres, Oracle, MySQL, SQL Server, dentre outros. O servidor de aplica&#231;&#227;o e o cliente magro podem ser desenvolvidos em Delphi, Kylix ou C++ Builder. &#201; no servidor de aplica&#231;&#227;o onde se concentram as regras de neg&#243;cio e todas as ferramentas necess&#225;rias para acessar e manipular os dados que est&#227;o no servidor de banco de dados. Os programas que ser&#227;o utilizados&#160;pelos usu&#225;rios n&#227;o fazem nada al&#233;m de mostrar-lhes os dados de forma que eles possam visualiz&#225;-los e edit&#225;-los.</p>\r\n<h3>O que &#233; o DataSnap?</h3>\r\n<p style=\"text-align: justify;\">O DataSnap &#233; baseado na tecnologia que permite que os conjuntos de dados sejam empacotados e enviados atrav&#233;s da rede como par&#226;metros nas chamadas a m&#233;todos remotos (<strong>Remote Procedure Call - RPC</strong>). Ele inclui tecnologias que permitem converter um TDataSet&#160;em dados Variant ou XML do lado do servidor. No lado do cliente esses dados s&#227;o transformados novamente em <a title=\"Costuma-se chamar estas convers&#245;es de objetos em bytes port&#225;veis e vice-versa de &lt;i&gt;serializa&#231;&#227;o de objetos&lt;/i&gt;\" href=\"#\" rel=\"bookmark\">TDataSet</a> e exibidos ao usu&#225;rio em um TDBGrid (por exemplo), com a ajuda dos componentes TClientDataSet ou <strong>TInetXPageProducer</strong>.</p>\r\n<p style=\"text-align: justify;\">Olhando de um &#226;ngulo um pouco diferente, <span style=\"text-decoration: underline;\">o DataSnap &#233; uma tecnologia que permite mover um conjunto de dados&#160;(TTable, TQuery ou similares) de um servidor, para um TClientDataSet no cliente</span>. O TClientDataSet, por sua vez, aparenta, age e se comporta exatamente como um TTable ou TQuery, exceto que ele n&#227;o precisa estar ligado ao BDE ou outros componentes de conex&#227;o, como UniDAC ou FireDAC. O &#250;nico requisito no cliente &#233; a presen&#231;a da biblioteca do DataSnap, a qual, ali&#225;s, se chama MIDAS.DLL. Mesmo assim, voc&#234; pode dispensar a presen&#231;a desta DLL, incluindo numa das cl&#225;usulas uses de sua aplica&#231;&#227;o cliente a unit MidasLib. Fazendo isso, nem mesmo a dll do DataSnap precisa estar presente! Ainda olhando para o DataSnap de uma forma diferenciada, basicamente o TClientDataSet obt&#233;m um conjunto de dados a partir de dados Variant que ele recebe do servidor.</p>\r\n<p style=\"text-align: justify;\">O DataSnap permite que voc&#234; utilize todos os componentes padr&#227;o do Delphi,&#160;incluindo <span style=\"text-decoration: underline;\">componentes de acesso a dados <strong>do lado do servidor</strong></span>, mas o lado do cliente &#233; um verdadeiro cliente magro, isto &#233;, ele n&#227;o deve incluir ou ligar-se diretamente a nenhum banco de dados. Como foi dito antes, a &#250;nica depend&#234;ncia do cliente magro &#233; a biblioteca midas.dll, a qual pode ser dispensada, mediante o uso da unit MidasLib.</p>\r\n<h3>Clientes DataSnap e Servidores DataSnap</h3>\r\n<p style=\"text-align: justify;\">At&#233; agora, muita teoria, mas a melhor maneira de entender o que &#233; o DataSnap e como ele funciona &#233; construindo uma aplica&#231;&#227;o DataSnap, que consiste de um <strong>Cliente DataSnap</strong> e um <strong>Servidor DataSnap</strong>. Costumo come&#231;ar com o servidor, onde encapsulo e exporto os conjuntos de dados. Com um servidor funcional, o pr&#243;ximo passo &#233; construir o cliente que se conectar&#225; com este servidor e exibir&#225; os dados de alguma forma.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>O Sevidor DataSnap</h3>\r\n<p style=\"text-align: justify;\">Para construir o seu primeiro Servidor DataSnap (doravante chamado de <strong>SDS</strong>) voc&#234; come&#231;a com uma aplica&#231;&#227;o nova vazia (<a title=\"Esse caminho muda em alguns Delphis mais recentes. Em Delphi 2006, por exemplo, o caminho &#233; &lt;b&gt;File &gt; New &gt; VCL Forms Application&lt;/b&gt;\" href=\"#\" rel=\"bookmark\">File &gt; New &gt; Application</a>).&#160;Enquanto o formul&#225;rio principal da aplica&#231;&#227;o estiver sendo exibido significa que o SDS estar&#225; ativo, em outras palavras, o loop de mensagens da aplica&#231;&#227;o vai manter o SDS vivo. Configure um t&#237;tulo para o formul&#225;rio principal de forma a identificar o SDS. Al&#233;m disso eu costumo incluir neste formul&#225;rio um TLabel com uma fonte grande e leg&#237;vel e configuro sua propriedade <strong>Caption</strong> com um valor sugestivo para o nome do SDS, por exemplo, \"Meu primeiro servidor DataSnap\".</p>\r\n<p style=\"text-align: justify;\">Para transformar uma aplica&#231;&#227;o regular em um servidor de aplica&#231;&#227;o (middleware database server), voc&#234; precisa adicionar um Remote DataModule (<strong>RDM</strong>)&#160;nela. Este DataModule especial pode ser encontrado na <a title=\"Em vers&#245;es mais modernas do Delphi, o Object Repository &#233; composto de uma visualiza&#231;&#227;o estilo &quot;Windows Explorer&quot; com itens sendo exibidos em pastas, logo, nestas vers&#245;es existe uma &lt;b&gt;pasta&lt;/b&gt; chamada Multitier\" href=\"#\" rel=\"bookmark\">aba</a> \"Multitier\" do Object Repository do Delphi, logo, acesse File &gt; New &gt; Other e v&#225; direto &#224; aba Multitier, a qual mostra v&#225;rios <a title=\"Este item pode n&#227;o existir na sua vers&#227;o de Delphi\" href=\"#\" rel=\"bookmark\">Wizards CORBA</a>, um Remote DataModule e um <a title=\"Este item pode n&#227;o existir na sua vers&#227;o de Delphi\" href=\"#\" rel=\"bookmark\">Transitional DataModule</a>. O &#250;ltimo pode ser usado com o MTS (Microsoft Transaction Server) antes do Windows 2000 ou COM+ no Windows 2000 ou posterior e n&#227;o ser&#225; coberto aqui. &#201; o RDM \"normal\" que voc&#234; precisa selecionar para criar seu primeiro SDS simples. Ao selecionar o &#237;cone do RDM e clicar no bot&#227;o OK, o assistente <a title=\"Este nome pode variar\" href=\"#\" rel=\"bookmark\">\"New Remote DataModule Object\"</a> ser&#225; iniciado.</p>\r\n<p style=\"text-align: justify;\">Existem algumas op&#231;&#245;es que voc&#234; precisa especificar (ou garantir que estejam selecionadas corretamente). Primeiramente, \"CoClass Name\", que &#233; o nome da classe interna. Este precisa ser um nome que voc&#234; possa lembrar facilmente depois, ent&#227;o, eu recomendo que voc&#234; use <strong>SimpleRemoteDataModule</strong> desta vez. Mantenha a op&#231;&#227;o \"Instancing\" configurada como <strong>Multiple Instance</strong>, desta forma seu SDS poder&#225; conter m&#250;ltiplas inst&#226;ncias do RDM. Mantenha outras op&#231;&#245;es como est&#227;o e pressione OK para, finalmente, gerar o RDM.</p>\r\n<h3><a title=\"Esta se&#231;&#227;o est&#225; focada no BDE, que era a tecnologia nativa de conex&#227;o dispon&#237;vel no Delphi na &#233;poca em que este artigo foi escrito. Obviamente voc&#234; n&#227;o precisa (e nem deve) usar mais o BDE. Resumidamente, ignore as refer&#234;ncias ao BDE e inclua no RDM componentes correlatos dispon&#237;veis no su&#237;te de componentes de conex&#227;o de sua prefer&#234;ncia (FireDAC, UniDAC, ZeosLib, DBX, etc.)\" href=\"#\" rel=\"bookmark\">O Remote DataModule</a></h3>\r\n<p style=\"text-align: justify;\">O resultado da execu&#231;&#227;o do assistente &#233; um RDM que parece muito com um TDataModule regular. Visualmente n&#227;o existem diferen&#231;as, e se voc&#234; planeja usar o BDE, ent&#227;o voc&#234; pode come&#231;ar considerando-o como um TDataModule qualquer, soltando nele um componente <strong>TSession</strong> sem esquecer de configurar a propriedade <strong>AutoSessionName</strong> como <strong>true</strong>. Lembre-se de que fazer isso &#233; imprescind&#237;vel caso voc&#234; esteja usando o modelo de thread <a title=\"Ao criar o RDM, o modelo de thread padr&#227;o &#233; o Apartment, logo, voc&#234; precisar&#225; configurar a propriedade AutoSessionName como true neste exemplo, caso esteja usando o BDE\" href=\"#\" rel=\"bookmark\">Apartment</a>.</p>\r\n<p style=\"text-align: justify;\">Uma vez que voc&#234; tenha inclu&#237;do um componente TSession voc&#234; poder&#225; adicionar os outros componentes. Por exemplo, voc&#234; pode incluir um <strong>TTable</strong> e nome&#225;-lo como <strong>TableCustomer</strong>. Configure sua propriedade DatabaseName como \"DBDEMOS\" e selecione na propriedade TableName a tabela customer.db.</p>\r\n<p style=\"text-align: justify;\">At&#233; agora tudo que foi feito poderia s&#234;-lo em um DataModule regular, mas agora &#233; hora de olhar os aspectos remotos deste DataModule especial (remoto). Na paleta de componentes, v&#225; at&#233; a aba <strong>Data Access</strong> onde voc&#234; encontrar&#225; o componente <strong>TDataSetProvider</strong>. Este componente &#233; a chave para exportar conjuntos de dados de um RDM para o mundo exterior, mais especificamente para clientes&#160;DataSnap. Solte um componente TDataSetProvider no RDM e configure sua propriedade <strong>DataSet</strong> para TableCustomer. Isto significa que o TDataSetProvider ir&#225; prover ou exportar TableCustomer para um cliente DataSnap que se conectar nele (esse cliente ser&#225; constru&#237;do posteriormente neste artigo).</p>\r\n<p style=\"text-align: justify;\">Uma propriedade muito importante do TDataSetProvider &#233; a propriedade <strong>Exported</strong>, que &#233; configurada como <strong>true</strong> para indicar que TableCustomer &#233; exportada. Voc&#234; pode configurar esta propriedade como false para \"esconder\" o fato de que TableCustomer &#233; exportada a partir do RDM, assim, nenhum cliente poder&#225; se conectar a ela. Isso pode ser &#250;til, por exemplo em um servidor rodando constantemente (24x7), onde voc&#234; precisa fazer o backup de algumas tabelas e precisa garantir que ningu&#233;m est&#225; trabalhando&#160;com elas durante o backup. Com a propriedade Exported do TDataSetProvider configurada como false ningu&#233;m pode fazer conex&#245;es a ele at&#233; que voc&#234; configure novamente a propriedade como true.</p>\r\n<h3>Compilando o Servidor DataSnap</h3>\r\n<p style=\"text-align: justify;\">Basicamente isso &#233; tudo que precisa ser feito para criar o seu primeiro SDS. A &#250;nica coisa que resta ser feita &#233; salvar o projeto. Salvei&#160;o formul&#225;rio principal no arquivo \"ServerMainForm.pas\", o RDM foi salvo&#160;no arquivo <a title=\"Um arquivo adicional de extens&#227;o .tlb pode ter sido criado pelo assistente &quot;New Remote DataModule Object&quot;. Caso haja necessidade de salvar este arquivo, nomeie-o com o mesmo nome do RDM, ou seja, &lt;b&gt;RDataMod.tlb&lt;/b&gt;.\" href=\"#\" rel=\"bookmark\">\"RDataMod.pas\"</a>, e salvei o projeto do SDS no arquivo \"SimpleDataSnapServer.dpr\". Ap&#243;s ter salvo o projeto, precisamos compil&#225;-lo e execut&#225;-lo. Ao executar o SDS -- o qual mostrar&#225; apenas o formul&#225;rio principal, claro -- ele ser&#225; <a title=\"A partir do Delphi 2007 o registro autom&#225;tico foi descontinuado devido a presen&#231;a maci&#231;a do UAC nos Windows, por isso, se voc&#234; tentar rodar este programa ele n&#227;o ser&#225; registrado automaticamente. Se este for seu caso, basta execut&#225;-lo com a linha de comando &lt;b&gt;/RegServer&lt;/b&gt;. Para desregistr&#225;-lo, use a linha de comando &lt;b&gt;/UnRegServer&lt;/b&gt;. O registro e o desregistro precisam ser feitos executando o SDS como administrador\" href=\"#\" rel=\"bookmark\">registrado</a> (no registro do Windows), assim qualquer cliente DataSnap poder&#225; encontr&#225;-lo e ent&#227;o&#160;conectar-se a ele. Mesmo que voc&#234; mova o SDS para outro diret&#243;rio (na mesma m&#225;quina), voc&#234; precisar&#225; apenas execut&#225;-lo novamente para que ele seja re-registrado na nova localiza&#231;&#227;o. Esta &#233; uma forma muito conveniente de gerenciar servidores DataSnap.</p>\r\n<p style=\"text-align: justify;\">At&#233; aqui, voc&#234; n&#227;o escreveu uma linha de c&#243;digo sequer para implementar este servidor DataSnap simples. Vamos ver ent&#227;o o que teremos de escrever ao implementar o cliente DataSnap que se conectar&#225; a ele.</p>\r\n<h3>O Cliente DataSnap</h3>\r\n<p style=\"text-align: justify;\">Existem v&#225;rios tipos de clientes DataSnap que voc&#234; pode desenvolver. Aplica&#231;&#245;es comuns de Windows (GUI), Active Forms e at&#233; mesmo servidores Web (usando Web Broker ou Internet Express). De fato, praticamente qualquer coisa pode atuar como um cliente DataSnap, como voc&#234; ver&#225; em breve. Por enquanto voc&#234; deve criar apenas uma aplica&#231;&#227;o regular de Windows a qual ir&#225; atuar como o seu primeiro cliente DataSnap simples que vai se conectar no seu SDS, o qual foi desenvolvido nas se&#231;&#245;es anteriores. Neste ponto voc&#234; n&#227;o deve tentar executar o cliente e o servidor em m&#225;quinas separadas. Ao inv&#233;s disso, execute tudo em uma m&#225;quina e ent&#227;o, depois, voc&#234; pode distribuir a aplica&#231;&#227;o na rede.</p>\r\n<p style=\"text-align: justify;\">Acesse&#160;File &gt; New &gt; Application&#160;para inicializar a constru&#231;&#227;o de uma aplica&#231;&#227;o simples. Neste ponto voc&#234; decide se quer adicionar ou n&#227;o um DataModule. A fim de evitar screenshots desnecess&#225;rios neste artigo, eu n&#227;o pretendo usar um DataModule. Ao inv&#233;s disso, vou usar o formul&#225;rio principal como cont&#234;iner&#160;para meus componentes n&#227;o visuais (DataSnap), bem como para meus componentes visuais normais.</p>\r\n<p style=\"text-align: justify;\">Antes de mais nada, seu cliente DataSnap precisa fazer uma conex&#227;o com o&#160;SDS. Esta conex&#227;o pode ser feita usando v&#225;rios protocolos distintos, como <strong>(D)COM</strong>, <strong>TCP/IP (sockets)</strong> e <strong>HTTP</strong> e os componentes que implementam estes protocolos de conex&#227;o s&#227;o, respectivamente, TDCOMConnection, TSocketConnection, TWebConnection e <a title=\"Este componente pode n&#227;o existir na sua vers&#227;o de Delphi\" href=\"#\" rel=\"bookmark\">TCorbaConnection</a> na aba <strong>DataSnap</strong> e o TSoapConnection na aba <strong>Web Services</strong>.&#160;Para este primeiro cliente DataSnap simples usaremos o componente <strong>TDCOMConnection</strong>, logo, inclua este componente no seu formul&#225;rio principal.</p>\r\n<p style=\"text-align: justify;\">O componente TDCOMConnection tem uma propriedade chamada ServerName a qual cont&#233;m o nome do SDS no qual voc&#234; pretende conectar. Na verdade, se voc&#234; abrir a lista dispon&#237;vel na propriedade ServerName no Object Inspector, voc&#234; ver&#225; uma lista de todos os servidores DataSnap registrados na m&#225;quina local. No seu caso, esta lista deve conter apenas um item de nome&#160;<strong>SimpleDataSnapServer.SimpleRemoteDataModule</strong>, mas eventualmente todos os servidores MIDAS 3 e DataSnap que estiverem registrados ser&#227;o vistos nesta lista. Os nomes nesta lista consistem de duas partes; a parte antes do ponto, denota o nome da aplica&#231;&#227;o e a parte ap&#243;s o ponto denota o nome do RDM, ent&#227;o, no caso atual, voc&#234; vai selecionar o SimpleRemoteDataModule contido na aplica&#231;&#227;o SimpleDataSnapServer.</p>\r\n<p style=\"text-align: justify;\">Quando voc&#234; selecionar um servidor, a propriedade ServerGUID ser&#225; automaticamente preenchida com o valor correto que est&#225; salvo no registro do Windows. Desenvolvedores com uma mem&#243;ria prodigiosa podem digitar um valor na propriedade ServerGUID e comprovar que a propriedade ServerName ser&#225; preenchida como o nome do SDS correspondente. A divers&#227;o come&#231;a mesmo quando voc&#234; configura a propriedade <strong>Connected</strong> do componente TDCOMConnection como true. Para que a conex&#227;o seja feita, o SDS precisa estar em execu&#231;&#227;o, logo, o ato de configurar a propriedade Connected como true faz com que o SDS seja executado automaticamente, fato que pode ser comprovado pela exibi&#231;&#227;o do&#160;formul&#225;rio principal do SDS criado nas se&#231;&#245;es anteriores.</p>\r\n<h3>Client DataSets</h3>\r\n<p style=\"text-align: justify;\">Configure a propriedade Connected do componente TDCOMConnection como false, para fechar o SDS. Agora que voc&#234; comprovou que &#233; capaz de conectar-se a ele &#233; hora de importar alguns DataSets que foram exportados pelo componente TDataSetProvider no RDM. Inclua um TClientDataSet, localizado na aba Data Access, no formul&#225;rio principal e conecte a sua propriedade <strong>RemoteServer</strong> ao componente TDCOMConnection. Para que o TClientDataSet obtenha dados do SDS voc&#234; precisa especificar qual TDataSetProvider usar. Em outras palavras, a partir de qual TDataSetProvider voc&#234; deseja importar os dados que preencher&#227;o o TClientDataSet. Utilize a propriedade <strong>ProviderName</strong> para especificar o TDataSetProvider de sua escolha.&#160;Abra a lista do combobox associado a propriedade e voc&#234; ver&#225; todos os TDataSetProvider dispon&#237;veis no RDM, todos aqueles que tem a propriedade <strong>Exported</strong> configurada como true. Em nosso caso existe apenas um TDataSetProvider no RDM do SDS, logo, selecione-o na lista.</p>\r\n<p style=\"text-align: justify;\">Antes de escolher um valor para a propriedade ProviderName, lembre-se que voc&#234; fechou a conex&#227;o com o SDS, entretanto, quando voc&#234; abriu a lista da propriedade a fim de listar todos os componentes TDataSetProvider dispon&#237;veis no RDM que possuem sua propriedade Exported configurada como true, existe apenas uma forma (para o Delphi e o Object Inspector) de saber exatamente quais destes TDataSetProvider est&#227;o dispon&#237;veis: perguntar ao SDS!&#160;Mais especificamente, varrer o RDM em busca de componentes TDataSetProvider que possuem a propriedade Exported = true. Por conta desta necessidade, como o SDS estava desligado, ele precisa ser iniciado novamente de forma que a lista da propriedade ProviderName seja preenchida e exibida no Object Inspector. Como resultado disso, no momento em que voc&#234; clica no combobox da propriedade ProviderName para exibir sua lista, o SDS ser&#225; automaticamente iniciado novamente.</p>\r\n<p style=\"text-align: justify;\">Quando voc&#234; selecionar o RemoteServer e o ProviderName voc&#234; poder&#225; abrir (ou ativar) o TClientDataSet. Voc&#234; pode fazer isso configurando a propriedade <strong>Active</strong> do componente como true. Neste momento o SDS estar&#225;&#160;alimentando dados a partir do TTable de nome TableCustomer, via componente TDataSetProvider atrav&#233;s de uma conex&#227;o COM para o componente TDCOMConnection que roteia estes dados para o componente TClientDataSet no seu Cliente DataSnap, que estar&#225; pronto para ser usado.</p>\r\n<p style=\"text-align: justify;\">Voc&#234; pode agora soltar um componente <strong>TDataSource</strong> e acessar a aba <strong>Data Controls</strong> da paleta de componentes e incluir um ou mais controles conscientes de dados (data-aware). A fim de manter o exemplo simples, inclua apenas um componente <strong>TDBGrid</strong>. Conecte a propriedade <strong>DataSet</strong> do componente TDataSource ao TClientDataSet e conecte a propriedade <strong>DataSource</strong> do TDBGrid ao componente TDataSource. Como o componente TClientDataSet j&#225; estava ativado, voc&#234; ver&#225; dados \"ao vivo\" em tempo de projeto, dados estes providos pelo nosso SDS.</p>\r\n<p style=\"text-align: justify;\">Est&#225; tudo quase pronto agora. Para finalizar o Cliente DataSnap, primeiramente altere a propriedade Caption do formul&#225;rio principal para algum nome &#250;til, como por exemplo \"Simple DataSnap Client\" e salve seu trabalho. Nomeie o formul&#225;rio principal como \"ClientForm\", salve-o como \"ClientMainForm.pas\", e nomeie o projeto como \"SimpleDataSnapClient\". Ent&#227;o, voc&#234; estar&#225; pronto para compilar e executar o&#160;Simple DataSnap Client! Novamente voc&#234; n&#227;o escreveu nenhuma simples linha de c&#243;digo, mas esteja avisado que isso vai mudar em breve nas pr&#243;ximas se&#231;&#245;es.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>BriefCase Model (Modelo de Maleta)</h3>\r\n<p style=\"text-align: justify;\">Ao executar o Cliente DataSnap voc&#234; v&#234; os dados de&#160;TableCustomer dentro do TDBGrid. Voc&#234; pode navegar atrav&#233;s destes dados, alterar o valor dos campos e mesmo incluir novos registros ou excluir registros existentes. Entretanto, uma vez que voc&#234; fechar a aplica&#231;&#227;o, todas as mudan&#231;as ser&#227;o perdidas. N&#227;o importa quantas vezes voc&#234; tentar; as mudan&#231;as feitas nos dados do TDBGrid em tempo de execu&#231;&#227;o do Cliente DataSnap afetam apenas o TClientDataSet local e n&#227;o o TableCustomer no servidor.</p>\r\n<p style=\"text-align: justify;\">O que voc&#234; acaba de experimentar aqui &#233; aquilo que &#233; chamado de <strong>Modelo de Maleta</strong> (<strong>Briefcase Model</strong>). Usando&#160;o modelo de maleta (<strong>MDM</strong>) voc&#234; &#233; capaz de desconectar o cliente da rede e ainda conseguir acessar os dados. O MDM funciona da seguinte maneira:</p>\r\n<ul>\r\n<li style=\"text-align: justify;\">Salve um conjunto de dados remoto no disco local, desligue a sua m&#225;quina e desconecte-a da rede. Voc&#234; pode ent&#227;o religar a sua m&#225;quina e editar o seu conjunto de dados sem estar conectado &#224; rede<br /><br /></li>\r\n<li style=\"text-align: justify;\">Quando a rede estiver dispon&#237;vel novamente, voc&#234; pode reconectar e atualizar o banco de dados. Um mecanismo especial est&#225; dispon&#237;vel para notific&#225;-lo acerca de erros de banco de dados. Isso permite que o usu&#225;rio possa resolver quaisquer conflitos que ocorram. Por exemplo, se duas pessoas editarem o mesmo registro, ent&#227;o uma delas ser&#225; notificada do fato e ser&#227;o apresentadas op&#231;&#245;es para a resolu&#231;&#227;o do conflito</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">O ponto chave do MDM &#233; que <span style=\"text-decoration: underline;\">voc&#234; n&#227;o precisa ter o servidor dispon&#237;vel todo o tempo</span> para poder trabalhar nos seus dados. Esta capacidade &#233; perfeita para usu&#225;rios com notebooks ou sites, onde voc&#234; quer diminuir ao m&#225;ximo o tr&#225;fego no banco de dados.</p>\r\n<p style=\"text-align: justify;\">Voc&#234; agora deve entender que seu Cliente DataSnap atua apenas nos dados locais que est&#227;o dentro do TClientDataSet e que &#233; poss&#237;vel salvar estes dados em um arquivo local e carreg&#225;-lo novamente em momento oportuno, logo, indo ao que interessa, para salvar o conte&#250;do atual do TClientDataSet, inclua um TButton no formul&#225;rio, nomei-o como \"ButtonSave\", configure seu Caption como \"Salvar\" e escreva o seguinte c&#243;digo no manipulador do seu evento OnClick:</p>\r\n<pre class=\"language-pascal\"><code>procedure TClientForm.ButtonSaveClick(Sender: TObject);\r\nbegin\r\n if ClientDataSet1.ChangeCount &gt; 0 then\r\n ClientDataSet1.SaveToFile(\'customer.cds\')\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Este c&#243;digo salva todos os registros contidos no TClientDataSet em um arquivo de nome \"customer.cds\" no diret&#243;rio atual da aplica&#231;&#227;o, mas apenas se houve altera&#231;&#245;es no TClientDataSet (<strong>ClientDataSet1.ChangeCount &gt; 0</strong>). Ali&#225;s, \"CDS\" significa \"Client DataSet\", mas voc&#234; pode usar qualquer nome de arquivo e qualquer extens&#227;o, claro. Observe que o segundo par&#226;metro do m&#233;todo SaveToFile &#233; implicitamente <strong>dfBinary</strong>. Este valor indica que queremos salvar os dados no formato bin&#225;rio (propriet&#225;rio da Borland). Alternativamente poder&#237;amos utilizar no segundo par&#226;metro o valor <strong>dfXML</strong> para salvar os dados em formato XML. Um arquivo XML &#233; muito maior (14K contra apenas 7K para todos os dados de TableCustomer), mas tem a vantagem de poder, em tese, ser usado por outras aplica&#231;&#245;es. Eu prefiro o formato bin&#225;rio, que gera um arquivo menor e mais eficiente.</p>\r\n<p style=\"text-align: justify;\">De forma similar, para implementar a funcionalidade que permite carregar o arquivo customer.cds no TClientDataSet, inclua outro TButton no formul&#225;rio principal, nomeie-o como \"ButtonLoad\", configure seu Caption como \"Carregar\" e escreva o seguinte c&#243;digo no manipulador do evento OnClick:</p>\r\n<pre class=\"language-pascal\"><code>procedure TClientForm.ButtonLoadClick(Sender: TObject);\r\nbegin\r\n ClientDataSet1.LoadFromFile(\'customer.cds\')\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Perceba que o m&#233;todo LoadFromFile n&#227;o precisa de um segundo argumento; ele &#233; suficientemente esperto para determinar se est&#225; lendo um arquivo bin&#225;rio ou um <a title=\"O arquivo XML que pode ser carregado pelo TClientDataSet tem uma estrutura espec&#237;fca, no entanto, qualquer XML pode ser carregado por um TClientDataSet ap&#243;s sofrer uma transforma&#231;&#227;o XML, ou seja, uma convers&#227;o que transforma uma estrutura XML qualquer numa estrutura XML que o TClientDataSet &#233; capaz de entender (DATAPACKET). Para realizar a convers&#227;o, utilize o XML Mapper, dispon&#237;vel no diret&#243;rio bin do Delphi. O XML Mapper vai gerar um arquivo de transforma&#231;&#227;o XML (XSLT) com o qual, em tempo de execu&#231;&#227;o, um arquivo XML &quot;padr&#227;o&quot; pode ser convertido em um XML DataPacket\" href=\"#\" rel=\"bookmark\">XML</a>.&#160;</p>\r\n<p style=\"text-align: justify;\">Armado com estes dois bot&#245;es voc&#234; poder&#225; agora, localmente, salvar as altera&#231;&#245;es feitas em seus dados e recarreg&#225;-los posteriormente.&#160;A fim de controlar o fato de quando ou n&#227;o o TClientDataSet estar&#225; conectado \"ao vivo\" ao SDS, voc&#234; pode incluir mais um TButton no formul&#225;rio principal que alterna a propriedade Active do TClientDataSet. Nomeie esse novo TButtom como \"ButtonConnect\", configure sua propriedade Caption como \"Conectar\", em seguida escreva o trecho de c&#243;digo a seguir no seu manipulador do evento OnClick:</p>\r\n<pre class=\"language-pascal\"><code>procedure TClientForm.ButtonConnectClick(Sender: TObject);\r\nbegin\r\n if ClientDataSet1.Active then // Fecha e desconecta\r\n begin\r\n ClientDataSet1.Close;\r\n DCOMConnection1.Close;\r\n end\r\n else // Abre (vai conectar automaticamente)\r\n begin\r\n// DCOMConnection1.Open;\r\n ClientDataSet1.Open;\r\n end\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Perceba que para desconectar&#160;voc&#234; precisa fechar o TClientDataSet e fechar tamb&#233;m o TDCOMConnection, enquanto que para estabelecer a conex&#227;o voc&#234; apenas precisa abrir o TClientDataSet que ir&#225;, implicitamente, abrir o TDCOMConnection tamb&#233;m.</p>\r\n<p style=\"text-align: justify;\">Finalmente, existe mais uma coisa que precisa ser feita:&#160;garantir que o TDCOMConnection e o TClientDataSet n&#227;o estejam conectados ao SDS em tempo de projeto, do contr&#225;rio, sempre que voc&#234; reabrir o projeto do Cliente DataSnap no Delphi ele precisar&#225; realizar uma conex&#227;o com o SDS, que precisar&#225; ser carregado e se, por um motivo qualquer, o SDS n&#227;o for encontrado em sua m&#225;quina o carregamento do projeto ficar&#225; congelado e, consequentemente, o Delphi ficar&#225; sem responder por um bom tempo. Para resolver esse problema, eu sempre garanto que estes componentes n&#227;o estejam conectados em tempo de projeto. Para fazer isso voc&#234; deve sempre atribuir false &#224;&#160;propriedade Connected do componente TDCOMConnection (que vai fechar o formul&#225;rio principal do SDS) e false &#224; propriedade Active do componente TClientDataSet (que, como resultado, far&#225; com que voc&#234; n&#227;o veja mais qualquer dado em tempo de projeto).</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Gostaria de abrir um par&#234;ntese para discutir o processo de timing dos clientes quando eles n&#227;o conseguem conversar com o servidor. Se voc&#234; tentar conectar-se ao servidor DCOM, mas n&#227;o puder alcan&#231;a-lo, o sistema n&#227;o desistir&#225; imediatamente da busca, ao inv&#233;s disso, ele vai continuar tentando por um per&#237;odo de tempo que raramente exceder&#225; dois minutos. Durante estes dois minutos, entretanto, a aplica&#231;&#227;o ficar&#225; ocupada e parecer&#225; travada. Se esta aplica&#231;&#227;o estiver carregada na IDE, ent&#227;o o Delphi como um todo ficar&#225; travado. Voc&#234; pode ter esse problema simplesmente ao configurar a propriedade Connected do componente TDCOMConnection como true.</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Agora, quando voc&#234; recompilar e executar seu Cliente DataSnap, o formul&#225;rio principal ser&#225; exibido sem nenhum dado sendo exibido no TDBGrid.&#160;&#201; hora ent&#227;o de clicar no bot&#227;o \"Conectar\", de forma que a conex&#227;o com o SDS seja estabelecida e todos os registros sejam obtidos a partir de TableCustomer. Entretanto haver&#225; momentos em que voc&#234; n&#227;o ter&#225; acesso ao SDS, seja porque voc&#234; est&#225; \"na rua\" ou simplesmente porque a m&#225;quina com o SDS encontra-se inacess&#237;vel. Nestes casos voc&#234; poder&#225; clicar o bot&#227;o \"Carregar\" e trabalhar com uma c&#243;pia local dos registros. Tenha em mente que esta c&#243;pia local &#233; aquela que voc&#234; salvou pela &#250;ltima vez e ser&#225; atualizada apenas quando voc&#234; clicar o bot&#227;o \"Salvar\" para escrever todo o conte&#250;do do TClientDataSet no disco.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>ApplyUpdates</h3>\r\n<p style=\"text-align: justify;\">&#201; excelente ser capaz de conectar-se a um conjunto de dados remoto ou carregar um conjunto de dados local e depois salv&#225;-lo no disco de novo,&#160;mas como se aplicam as <a title=\"Quero ressaltar novamente que a palavra &quot;atualizar&quot; aqui significa qualquer opera&#231;&#227;o que altere o banco de dados. No caso, uma inser&#231;&#227;o, uma exclus&#227;o e uma altera&#231;&#227;o propriamente dita, todas estas opera&#231;&#245;es alteram o banco de dados, inserindo, excluindo ou alterando registros\" href=\"#\" rel=\"bookmark\">atualiza&#231;&#245;es</a> ao banco de dados de fato (remoto)? Isso pode ser feito usando-se o m&#233;todo ApplyUpdates do TClientDataSet. Inclua mais um TButton no formul&#225;rio principal, configure seu nome como \"ButtonApplyUpdates\" e seu Caption como \"Aplicar Altera&#231;&#245;es\". Tal como o bot&#227;o \"Salvar\", este bot&#227;o deve apenas aplicar altera&#231;&#245;es caso haja altera&#231;&#245;es a serem aplicadas. O manipulador do evento OnClick deste bot&#227;o segue:</p>\r\n<pre class=\"language-pascal\"><code>procedure TClientForm.ButtonApplyUpdatesClick(Sender: TObject);\r\nbegin\r\n if ClientDataSet1.ChangeCount &gt; 0 then\r\n ClientDataSet1.ApplyUpdates(0);\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">O m&#233;todo ApplyUpdates tem apenas um argumento: o n&#250;mero m&#225;ximo de erros que ser&#227;o permitidos antes que o processo de aplica&#231;&#227;o de altera&#231;&#245;es seja interrompido (<strong>MaxErrors</strong>). Com apenas um Cliente DataSnap conectado ao SDS voc&#234; nunca ter&#225; qualquer problema, ent&#227;o, fique &#224; vontade para executar o Cliente DataSnap agora. Clique o bot&#227;o \"Conectar\" para conectar (e carregar) o SDS e use os bot&#245;es \"Salvar\" e \"Carregar\" para escrever /ler o conte&#250;do do TClientDataSet no/do disco. Voc&#234; pode at&#233; remover sua m&#225;quina da rede e trabalhar apenas nos dados locais por uma quantidade significante de tempo, que &#233; exatamente a ideia por tr&#225;s do modelo de maleta (o seu laptop sendo a maleta!). Qualquer altera&#231;&#227;o feita na sua c&#243;pia local permanecer&#225; vis&#237;vel e voc&#234; poder&#225; aplicar as altera&#231;&#245;es no banco de dados remoto com um clique no bot&#227;o \"Aplicar Altera&#231;&#245;es\", claro, desde que voc&#234; reconecte &#224; rede e ao SDS novamente.</p>\r\n<h3>Manipula&#231;&#227;o de erros</h3>\r\n<p style=\"text-align: justify;\">Ent&#227;o o que aconteceria se dois clientes, ambos usando o MDM, conectassem ao SDS, obtivessem todo conte&#250;do de TableCustomer e ambos fizessem algumas altera&#231;&#245;es no primeiro registro? De acordo com o que foi codificado at&#233; o momento, ambos os clientes poderiam enviar suas altera&#231;&#245;es ao SDS usando o m&#233;todo ApplyUpdates. Se ambos passaram zero como argumento do ApplyUpdates, ent&#227;o o cliente que&#160;ficou em segundo lugar na \"corrida da atualiza&#231;&#227;o\" <strong>n&#227;o ter&#225;</strong> suas modifica&#231;&#245;es persistidas no banco de dados remoto, pois seu ApplyUpdates vai ser interrompido imediatamente. O&#160;segundo cliente poderia utilizar um valor maior que zero em MaxErrors a fim de indicar um n&#250;mero fixo de erros/conflitos que seriam permitidos antes de o ApplyUpdates ser interrompido,&#160;entretanto, mesmo se o segundo par&#226;metro fosse -1 (o que indicaria que o ApplyUpdates deve continuar mesmo que haja erros), ele jamais iria alterar os dados que foram previamente alterados pelo primeiro cliente. Em outras palavras: voc&#234; precisa executar algumas a&#231;&#245;es de concilia&#231;&#227;o para manipular atualiza&#231;&#245;es em registros e campos que foram alterados por outros usu&#225;rios.</p>\r\n<p style=\"text-align: justify;\">Felizmente o Delphi possui uma caixa de di&#225;logo muito &#250;til especialmente desenvolvida para este prop&#243;sito. Sempre que voc&#234; precisar realizar alguma <a title=\"&quot;Conciliar erros&quot; ou &quot;Reconciliar erros&quot; vem do ingl&#234;s &quot;Reconcile Error&quot; e pode ser interpretado como uma forma de resolver problemas (erros) nos dados quando o DataSnap n&#227;o consegue determinar o que deve ser feito. A responsabilidade da decis&#227;o do que deve ser feito com dados conflitantes ou errados &#233; sempre passada &#224; aplica&#231;&#227;o ou ao usu&#225;rio, o qual tem o poder de decidir o que deve ser feito\" href=\"#\" rel=\"bookmark\">concilia&#231;&#227;o de erros</a>, voc&#234; deve considerar a adi&#231;&#227;o desta caixa de di&#225;logo ao seu Cliente DataSnap (ou escrever uma voc&#234; mesmo, mas algo sempre precisa ser feito a respeito destes erros). Para usar a caixa de di&#225;logo disponibilizada pelo Delphi, v&#225; em File &gt; New &gt; Other, acesse a aba (ou item) <a title=\"Nos Delphis mais recentes, o &#237;tem do Object Repository &#233; &quot;Delphi Files&quot; e o nome do &#237;cone a ser selecionado &#233; &quot;VCL Reconcile Error Dialog&quot;\" href=\"#\" rel=\"bookmark\">\"Dialogs\"</a> do Object Repository e selecione o &#237;cone \"Reconcile Error Dialog\". Uma vez que voc&#234; tenha selecionado este &#237;cone e clicado OK, uma nova unit ser&#225; adicionada ao projeto do Cliente DataSnap. Esta unit cont&#233;m a defini&#231;&#227;o e a implementa&#231;&#227;o da caixa de di&#225;logo \"Update Error\", que pode ser usada para resolver erros decorrentes de conflitos nas opera&#231;&#245;es de banco de dados.</p>\r\n<p style=\"text-align: justify;\">Quando esta unit for adicionada ao projeto existe uma coisa muito importante que voc&#234; precisa verificar. Primeiramente salve seu trabalho. Salve a nova unit em um arquivo de nome ErrorDialog.pas. Feito isso, a menos que voc&#234; tenha desmarcado a op&#231;&#227;o para criar automaticamente TForms e TDataModules (na aba \"Designer\" da caixa de di&#225;logo Tools &gt; Environment Options), voc&#234; precisar&#225; garantir que a classe da caixa de di&#225;logo \"Update Error\" (TReconcileErrorForm) n&#227;o seja&#160;uma das que s&#227;o&#160;automaticamente criadas por sua aplica&#231;&#227;o. Veja a aba \"Forms\" da caixa de di&#225;logo Project &gt; Options. Nesta aba, voc&#234; encontrar&#225; uma lista de formul&#225;rios que s&#227;o automaticamente criados e uma lista de formul&#225;rios que est&#227;o dispon&#237;veis no projeto. Apenas verifique se&#160;TReconcileErrorForm n&#227;o est&#225; na lista de formul&#225;rios que s&#227;o criados automaticamente e, se estiver, mova-a para a lista de formul&#225;rios dispon&#237;veis.&#160;Uma inst&#226;ncia de&#160;TReconcileErrorForm ser&#225; criada dinamicamente quando for necess&#225;ria.</p>\r\n<p style=\"text-align: justify;\">Ent&#227;o, quando ou como voc&#234; pode usar esta caixa de di&#225;logo especial? Bem, na verdade &#233; muito simples. Para cada registro cuja opera&#231;&#227;o &#160;de banco de dados (inser&#231;&#227;o, exclus&#227;o, altera&#231;&#227;o) n&#227;o seja&#160;bem sucedida, o evento OnReconcileError do TClientDataSet ser&#225; chamado. O manipulador deste evento tem a seguinte assinatura:</p>\r\n<pre class=\"language-pascal\"><code>procedure TClientForm.ClientDataSet1ReconcileError(DataSet: TCustomClientDataSet;\r\n E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction);\r\nbegin\r\n\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Este &#233; um manipulador de evento com quatro argumentos. O primeiro deles &#233; o TClientDataSet que levantou o erro.&#160;O segundo &#233; a exce&#231;&#227;o de (re)concilia&#231;&#227;o propriamente dita, a qual cont&#233;m a mensagem acerca da causa da condi&#231;&#227;o de erro. O terceiro argumento &#233; o <strong>UpdateKind</strong>&#160;(ukInsert, ukDelete, ukModify) e indica qual opera&#231;&#227;o estava sendo tentada quando o erro aconteceu. Finalmente, o quarto argumento &#233; a a&#231;&#227;o que deve ser tomada para resolver o conflito. Neste argumento voc&#234; pode retornar os seguintes valores da enumera&#231;&#227;o&#160;<strong>TReconcileAction</strong>, declarada na unit <strong>DBClient</strong> (ou <strong>DataSnap.DBClient</strong>):</p>\r\n<ul>\r\n<li style=\"text-align: justify;\"><strong>raSkip</strong> - N&#227;o altere nada no banco de dados mas deixe as altera&#231;&#245;es n&#227;o aplicadas no log de modifica&#231;&#245;es (localmente), para permitir uma tentativa posterior;</li>\r\n<li style=\"text-align: justify;\"><strong>raAbort</strong> - Abortar toda a manipula&#231;&#227;o de erros. Nenhum outro registro vai passar pelo&#160;evento OnReconcileError;</li>\r\n<li style=\"text-align: justify;\"><strong>raMerge</strong> - Mescla os dados existentes no registro salvo (banco de dados remoto) com as altera&#231;&#245;es sendo tentadas. Isso vai alterar remotamente APENAS aqueles campos que foram alterados localmente;</li>\r\n<li style=\"text-align: justify;\"><strong>raCorrect</strong> - Substitui dados do registro salvo (banco de dados remoto), com valores corrigidos informados. Esta &#233; a op&#231;&#227;o na qual a interven&#231;&#227;o do usu&#225;rio &#233; requerida;</li>\r\n<li style=\"text-align: justify;\"><strong>raCancel</strong> - Desfaz todas as altera&#231;&#245;es no registro atual, transformando-o de volta no registro original (local) que voc&#234; tinha;</li>\r\n<li style=\"text-align: justify;\"><strong>raRefresh</strong> - Desfaz todas as altera&#231;&#245;es no registro atual, mas recarrega o registro com os valores contidos no banco de dados (remoto).</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">A coisa mais legal a respeito do&#160;TReconcileErrorForm &#233; que voc&#234; n&#227;o precisa se preocupar&#160;com nada disso. Voc&#234; precisa apenas fazer duas coisas. Primeiro, voc&#234; precisa incluir a unit ErrorDialog na cl&#225;usula uses do formul&#225;rio principal. Com o formul&#225;rio principal aberto no Delphi, pressione a combina&#231;&#227;o de teclas Alt+F11. A caixa de di&#225;logo \"Use unit\" vai aparecer. Selecione a unit ErrorDialog e clique OK.</p>\r\n<p style=\"text-align: justify;\">A segunda coisa a fazer &#233; escrever uma linha de c&#243;digo no manipulador do evento OnReconcileError e chamar a fun&#231;&#227;o&#160;<strong>HandleReconcileError</strong>, dispon&#237;vel na unit ErrorDialog, a qual voc&#234; acabou de incluir na cl&#225;usula uses de seu formul&#225;rio principal. A fun&#231;&#227;o&#160;HandleReconcileError tem os mesmos quatro argumentos do manipulador do evento OnReconcileError (n&#227;o &#233; coincid&#234;ncia, claro), ent&#227;o, tudo agora &#233; quest&#227;o de passar estes argumentos do manipulador para a fun&#231;&#227;o, nada mais, nada menos. Sendo assim, o manipulador completo do evento OnReconcileError pode ser visto abaixo:</p>\r\n<pre class=\"language-pascal\"><code>procedure TClientForm.ClientDataSet1ReconcileError(DataSet: TCustomClientDataSet;\r\n E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction);\r\nbegin\r\n Action := HandleReconcileError(DataSet,UpdateKind,E);\r\nend;</code></pre>\r\n<h3>Demonstrando Erros de (Re)Concilia&#231;&#227;o</h3>\r\n<p style=\"text-align: justify;\">A maior quest&#227;o agora &#233;: como tudo isso funciona na pr&#225;tica? Para poder testar, voc&#234; obviamente precisa de dois ou mais Clientes DataSnap rodando simultaneamente. Para um teste completo usando o Cliente DataSnap e o Servidor DataSnap, voc&#234; precisa executar os seguintes passos:</p>\r\n<ul>\r\n<li style=\"text-align: justify;\">Inicie o primeiro Cliente DataSnap e clique o bot&#227;o \"Conectar\". Neste momento o SDS ser&#225; carregado tamb&#233;m e dados ser&#227;o obtidos;</li>\r\n<li style=\"text-align: justify;\">Inicie o segundo Cliente DataSnap e clique o bot&#227;o \"Conectar\". Dados ser&#227;o obtidos do mesmo SDS que j&#225; est&#225; em execu&#231;&#227;o;</li>\r\n<li style=\"text-align: justify;\">Usando o primeiro Cliente DataSnap, altere o campo \"Company\" do primeiro registro;</li>\r\n<li style=\"text-align: justify;\">Usando o segundo Cliente DataSnap, altere o mesmo campo&#160;\"Company\" do primeiro registro, tomando o cuidado de n&#227;o alterar ele para o mesmo valor informado no primeiro Cliente DataSnap;</li>\r\n<li style=\"text-align: justify;\">Clique o bot&#227;o \"Aplicar Altera&#231;&#245;es\" do primeiro Cliente DataSnap. A altera&#231;&#227;o ser&#225; aplicada sem qualquer problema;</li>\r\n<li style=\"text-align: justify;\">Clique o bot&#227;o \"Aplicar Altera&#231;&#245;es\" do segundo Cliente DataSnap. Desta vez, um ou mais erros v&#227;o ocorrer porque o primeiro registro teve o campo \"Company\" alterado (pelo primeiro Cliente DataSnap). Para esse e para outros registros conflitantes o evento OnReconcileError ser&#225; executado e a caixa de di&#225;logo \"Update Error\" ser&#225; apresentada;</li>\r\n<li style=\"text-align: justify;\">Dentro da caixa de di&#225;logo \"Update Error\" voc&#234; poder&#225; experimentar as v&#225;rias a&#231;&#245;es de (re)concilia&#231;&#227;o (<strong>Skip</strong>, <strong>Abort</strong>, <strong>Merge</strong>, <strong>Correct</strong>,&#160;<strong>Cancel</strong> e <strong>Refresh</strong>) a fim de obter um bom entendimento do que cada uma delas faz. Preste aten&#231;&#227;o redobrada nas diferen&#231;as entre Skip e Cancel, e as diferen&#231;as entre Correct, Refresh e Merge.</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">\"Skip\" vai ignorar&#160;o erro atual sem aplicar nada ao banco de dados remoto e seguir para o pr&#243;ximo registro da fila de atualiza&#231;&#245;es no Data Packet (se houver). A altera&#231;&#227;o n&#227;o aplicada no banco de dados remoto permanecer&#225; no log de modifica&#231;&#245;es e poder&#225; ser enviada novamente ao se pressionar o bot&#227;o \"Aplicar Altera&#231;&#245;es\".&#160;\"Cancel\" tamb&#233;m vai ignorar o erro atual e mant&#234;-lo no log de modifica&#231;&#245;es, a diferen&#231;a &#233; que ele vai interromper o processo de aplica&#231;&#227;o de altera&#231;&#245;es, ou seja, se houver mais registros a serem atualizados, eles n&#227;o o ser&#227;o!</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Para deixar isso bem claro, imagine que existem 10 registros modificados e voc&#234; pressiona o bot&#227;o \"Aplicar Altera&#231;&#245;es\". O primeiro, o segundo e o terceiro registros, passaram. O quarto registro tem um problema e vai exibir a caixa de di&#225;logo \"Update Error\". Se voc&#234; escolher \"Skip\", a atualiza&#231;&#227;o do quarto registro ser&#225; ignorada, ele permanecer&#225; no log de modifica&#231;&#245;es locais&#160;do TClientDataSet e o fluxo do programa continua tentando aplicar o quinto registro. Supondo que o quinto registro passe, o sistema vai tentar o sexto e depois o s&#233;timo. Suponha que esses passaram sem problemas, a&#237;, ao tentar aplicar as atualiza&#231;&#245;es do oitavo registro, houve um erro, ent&#227;o, novamente, a caixa de di&#225;logo \"Update Error\" vai aparecer, mas desta vez a a&#231;&#227;o que voc&#234; escolhe &#233; \"Cancel\". Neste caso, o restante da&#160;opera&#231;&#227;o do ApplyUpdates terminar&#225; e os registros oitavo, nono e d&#233;cimo, n&#227;o ser&#227;o aplicados e permanecer&#227;o no log de altera&#231;&#245;es locais.</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">\"Refresh\" apenas \"esquece\" todas as atualiza&#231;&#245;es que voc&#234; fez no registro e atualiza ele localmente com os valores que est&#227;o persistidos no servidor de banco de dados.&#160;\"Merge\" vai tentar mesclar o registro modificado (local) com o registro

Part of diff was cut off due to size limit. Use your local client to view the full diff.

Show on old repository browser