修訂 | 115 (tree) |
---|---|
時間 | 2021-08-10 04:40:44 |
作者 | derekwildstar |
Backup pós atualização
@@ -384,7 +384,7 @@ | ||
384 | 384 | |
385 | 385 | LOCK TABLES `joomla_banners` WRITE; |
386 | 386 | /*!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); | |
388 | 388 | /*!40000 ALTER TABLE `joomla_banners` ENABLE KEYS */; |
389 | 389 | UNLOCK TABLES; |
390 | 390 |
@@ -575,7 +575,7 @@ | ||
575 | 575 | |
576 | 576 | LOCK TABLES `joomla_content` WRITE; |
577 | 577 | /*!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-Ømnis Soluções Tecnológicas</strong> é uma empresa formada por profissionais especialistas em Delphi e PHP, todos com experiência média de pelo menos 5 anos no mercado, desenvolvendo as mais diversas soluções nestas linguagens. Nossa base de funcionamento é a cooperação entre nossos profissionais, cada um deles sendo, portanto, dono de um pedaço da empresa</p>\r\n<p style=\"text-align: justify;\"> </p>\r\n<p style=\"text-align: justify;\"> </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-Ømnis Soluções Tecnológicas</strong> é uma empresa de Tecnologia da Informação que desenvolve sistemas customizados e 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->szName, szdsERRMESSAGE);\r\n// 2129\r\npFldDes->iFldType = fldZSTRING;\r\n// 2130\r\npFldDes->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õ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!\" :)</p>\r\n','\r\n<p style=\"text-align: justify;\">Segue abaixo minha tradução/versão do texto original disponí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ão, não existe uma tradução ao pé da letra, mas sim uma interpretação daquilo que o autor original escreveu. Esta interpretação, contudo, não alterou em nada o sentido do texto, mas facilitará 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 é muito interessante. Você aprende uma linguagem de programação para desenvolver um grande variedade de softwares e assim \"digitalizar o mundo\", mas enquanto faz isso, você às vezes enfrenta situações onde pequenos erros tiram seu sono por muitos dias e noites. A melhor parte dessa experiência é quando você finalmente encontra a solução e dá 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 é sempre gratificante, e é por isso que o desenvolvimento de softwares é um trabalho que está agradando mais e mais jovens ao redor do mundo.</p>\r\n<p style=\"text-align: justify;\">Dependendo das linguagens de programação que você estudou e implementou, as chances de ficar travado em um pequeno problema pode variar. A utilização de uma linguagem complexa pode tirar seu sono muitas vezes durante o desenvolvimento de um software, por outro lado, a utilização de uma linguagem de programação com uma sintaxe simples de escrever e recursos realmente úteis pode transformar sua vida de desenvolvedor em uma vida que todos adorariam ter. A Linguagem Delphi (Object Pascal) é este tipo de linguagem de programação, largamente conhecida por ser uma das linguagens de sintaxe mais simples de escrever.</p>\r\n<p style=\"text-align: justify;\">Se você pretende ser um desenvolvedor de softwares você pode não querer começar com uma linguagem difícil, que ponha seu moral pra baixo. Então, a fim de manter a sua paixão em ser um desenvolvedor no mais alto grau de entusiasmo, começar com a Linguagem Delphi será uma opção sem igual. Começando sua carreira como um Desenvolvedor Delphi traz para você muitos benefícios. Vejamos agora algumas das principais razões que farão você entender porque você deve ser um Desenvolvedor Delphi:</p>\r\n<ol type=\"I\">\r\n<li>A Linguagem Delphi é uma combinação de linguagem de programação e SDK, o que permite o desenvolvimento de de aplicações para desktop, dispositivos móveis, <a title=\"Eu não tenho conhecimento sobre a possibilidade de desenvolvimento para consoles usando o Delphi. Eu já 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í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> e web;</li>\r\n<li>A Linguagem Delphi é uma linguagem de programação simples e com sintaxe limpa;</li>\r\n<li>Código escrito em Delphi é facilmente legível, por exemplo, você pode concatenar strings usando o caractere \"+\" ao invés de usar uma função para isso;</li>\r\n<li>A documentação do Delphi é bem organizada para ajudá-lo a iniciar rapidamente;</li>\r\n<li>Vem com uma IDE que lhe permite desenvolver facilmente uma GUI usando arrastar & soltar, adicionando manipuladores de eventos e muitas outras características realmente úteis;</li>\r\n<li>Suporta teste em tempo real (debug), tornando muito mais simples e rápido encontrar e corrigir problemas;</li>\r\n<li>Suporta o Desenvolvimento Rápido de Aplicações -- Rapid Application Development (RAD) -- com características tais como, o framework de aplicação e o designer de layout de 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ção de componentes que são facilmente integrá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 > 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 >= 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 < 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 >= 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 > New > 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->szName, szdsERRMESSAGE);\r\npFldDes->iFldType = fldZSTRING;\r\npFldDes->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->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->szName, szdsERRMESSAGE);\r\npFldDes->iFldType = fldZSTRING;\r\npFldDes->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->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&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 <> \'\' 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 > 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&view=article&id=132&catid=80&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><Feriados>\r\n <item>\r\n <id>638</id>\r\n <data>2017-07-16T00:00:00-03:00</data>\r\n <descricao>Dia da Padroeira Nossa Senhora do Carmo</descricao>\r\n <esfera>M</esfera>\r\n <dataFixa>S</dataFixa>\r\n <cidade>\r\n <id>1</id>\r\n <nome>Recife</nome>\r\n <uf>PE</uf>\r\n <regiaoMetropolitana>true</regiaoMetropolitana>\r\n </cidade>\r\n </item>\r\n</Feriados></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><Feriados></strong>, o qual contém vários nós de nome <strong><item></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><id></strong>, <strong><data></strong>, <strong><descricao></strong>, <strong><esfera></strong>, <strong><dataFixa></strong> e <strong><cidade></strong>. O nó <strong><cidade></strong>, por sua vez, possui os subnós <strong><id></strong>, <strong><nome></strong>, <strong><uf></strong> e <strong><regiaoMetropolitana></strong>. O nó <cidade> é 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 > 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ó <cidade> 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 <cidade>:\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 <cidade>, 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 > 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&view=article&id=127&catid=80&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é mesmo eu o usei 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.</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ção eram as barras de progresso. Eu não sei porque, mas eu sempre olhava a barrinha enchendo e achava muito engenhoso. No início, eu queria porque queria, que meus projetos utilizassem aquilo e cheguei até mesmo a utilizar uma barra de progresso na inicializaçã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ão era correto carregar todos os TForm na inicialização e foi aí que eu entendi que uma barra de progresso só faz sentido quando ela é realmente necessária, ou seja, não adianta forç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ário o andamento de uma tarefa realmente demorada <span style=\"text-decoration: underline;\">da qual você conheça o evento no qual o processamento termina</span>. Este \"evento\" pode ser qualquer coisa que seja representada por um número. <strong>O processamento dos bytes de um arquivo</strong> <span style=\"text-decoration: underline;\">de tamanho conhecido</span>, <strong>o processamento de arquivos num diretório</strong> <span style=\"text-decoration: underline;\">quando você previamente sabe a quantidade de arquivos</span>, <strong>a validação de um arquivo XML com um arquivo XSD</strong>, <span style=\"text-decoration: underline;\">desde que você obtenha previamente a quantidade total de nós dentro do XML</span>, etc., são exemplos de processamento onde uma barra de progresso pode ser usada. Sua utilização é válida também quando você não conhece o evento final, mas tem meios de conseguí-lo de forma muito rápida, por exemplo, é possível contar de forma relativamente rápida a quantidade de arquivos dentro de uma estrutura de diretórios, dada uma raiz inicial, recursivamente ou mesmo a quantidade total de nó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évia de arquivos dentro de uma estrutura de diretó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ásica é: se você <strong>não tiver</strong> condições de saber o evento no qual o processamento termina <strong>antes do processamento começar</strong> ou <strong>se a obtenção deste evento for muito complexa ou demorada</strong>, então uma barra de progresso não se aplica e você deve informar ao usuário que algo está acontecendo, mas que não tem meios de dizer quando vai acabar. Normalmente nesse caso, se usa uma animação qualquer para indicar que algo está sendo feito em segundo plano.</p>\r\n<p style=\"text-align: justify;\">A seguir vou começ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ão e que existem outros meios de se fazer a mesma coisa. Por exemplo, aqui eu recomendo que as rotinas demoradas sejam agrupadas em um método simples que faz todo o processamento, mas isso não é requerido, apenas escolhi esse meio porque <strong>EU</strong> achei mais fácil. O importante é entender o que está sendo feito e seguir minha recomendação ou criar seu próprio modo de fazer a mesma coisa. Fica a seu crité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ão existe má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ão são todos que tem paciência ou mesmo conhecimento para criar threads da forma correta, quanto mais uma apenas para fazer funcionar uma simples barra de progresso. Muitos de nós, eu me incluía neste meio, preferimos usar <em>Application.ProcessMessages</em> e obter o mesmo efeito, contudo, esta sensação de que está tudo bem, é falsa! O uso indiscriminado de <em>ProcessMessages</em> pode causar problemas difíceis de debugar, access violations inesperados e outros tipos de comportamentos bizarros em programas mais complexos. Já tive problemas em um programa que fazia uso de FTP assíncrono e só 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&view=article&id=103&catid=80&Itemid=493\">O lado negro do Application.ProcessMessages</a>, são apresentados alguns motivos para evitar seu uso indiscriminado.</p>\r\n<p style=\"text-align: justify;\">Eu falei no parágrafo anterior que eu me incuí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 barras de progresso e por isso, vou passar a usar sempre. Eu só tenho a ganhar e você também, caro leitor, caso entenda como tudo foi feito e tenha um pouco menos de preguiça ;) Abaixo eu lhes apresento uma unit com uma classe que configura uma thread básica e introduz alguns eventos, propriedades e métodos que vão ser de grande ajuda para fazer tudo funcionar como um reló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>é simplesmente uma descendente de <strong>TThread</strong> (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ão e instrui a mesma a ser liberada da memória automaticamente (linha <a href=\"#UProgressThread.64\" rel=\"alternate\">64</a>). Dessa forma, basta fazer <strong>TProgressThread.Create</strong> e automaticamente  a thread é criada em modo suspenso e será liberada da memória tão logo ela termine. Esta classe expõe dois eventos essenciais nesta implementaçã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 é executado a cada iteração do processo demorado e o segundo é executado quando há a necessidade de contagem prévia da quantidade total de iterações, permitindo a configuração da barra de progresso antes do iní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ão retornadas pelos eventos OnProgress e OnMax. Os mé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ão métodos que, ao serem executados, executam os eventos correspondentes.</p>\r\n<p style=\"text-align: justify;\">Esta classe nã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á nebuloso até aqui, mas continue lendo, no final tudo ficará claro.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">Agrupar é preciso</h2>\r\n<p style=\"text-align: justify;\">Se você já tem um projeto que, em algum ponto, realiza algum processamento demorado o primeiro passo é pegar este processamento e colocá-lo dentro de um mé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ém o tamanho de todos os arquivos dentro de uma estrutura de diretórios recursivamente. É uma função simples, com um parâmetro para passar o diretó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 <> \'.\') and (SearchRecord.Name <> \'..\') 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) <> 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ção é bem demorada, porque ela usa um método redundante para obter o tamanho de um arquivo. Este método é redundante porque o tamanho dos arquivos já poderia ser obtido instantaneamente lendo <strong>SearchRecord.FileSize</strong>. Utilizei uma função para leitura genérica do tamanho de um arquivo apenas para desacelerar o processo de forma a fazer sentido o uso de uma barra de progresso.  A função FileSize, para leitura genérica do tamanho de um arquivo, é 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ção DirectoryTreeSize percorre recursivamente uma estrutura de diretórios e lê o tamanho de cada arquivo encontrado. Não há como saber quantos arquivos existem dentro de um diretório recursivamente, logo, a única solução é varrer a mesma estrutura e simplesmente contar cada arquivo. Isso parece estúpido, pois vamos acabar percorrendo a mesma estrutura de diretórios duas vezes, uma para obter a quantidade de arquivos e a outra exatamente igual, para processá-los, mas a varredura para contar os arquivos é extremamente rápida, pelo menos é MUITO MAIS RÁPIDA que a obtenção do tamanho de cada arquivo e sendo assim esse procedimento é válido para este exemplo. Abaixo está a função que retorna o total de arquivos a processar dentro da estrutura de diretó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 <> \'.\') and (SearchRecord.Name <> \'..\') 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) <> 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ção <strong>DirectoryTreeFileCount</strong> é praticamente igual à função <strong>DirectoryTreeSize</strong>. Isso era esperado neste caso, já que precisamos passar pelos arquivos que serão processados, mas apenas precisamos contá-los.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">Estendendo a classe TProgressThread</h2>\r\n<p style=\"text-align: justify;\">Agora que temos o procedimento demorado isolado em uma função, e temos uma segunda função que nos dará de forma rápida a quantidade de elementos que serão processados, podemos adaptá-las dentro de uma classe filha da classe TProgressThread. O nome da classe e o nome da unit que a contém eu convenciono como sendo o mesmo nome da função original, logo, a classe se chama <strong>TDirectoryTreeSize</strong> e sua unit <strong>UDirectoryTreeSize</strong>. A implementação completa você vê 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 <> \'.\') and (SearchRecord.Name <> \'..\') 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) <> 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 <> \'.\') and (SearchRecord.Name <> \'..\') 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) <> 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ão entre em pânico! Eu vou explicar tudo o que foi feito nesta unit da maneira mais simples e direta quanto for possível.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h2 style=\"text-align: justify;\">Como TDirectoryTreeSize foi implementada? (E como criar a sua própria classe) </h2>\r\n<p style=\"text-align: justify;\">Anteriormente eu falei que seria necessário agrupar em um método (function ou procedure) todo o processamento demorado, portanto estou considerando que você já fez isso. Siga os passos a seguir e olhe com cuidado a classe TDirectoryTreeSize. Use os links nos números das linhas para pular para o ponto exato no código da classe acima.</p>\r\n<ol>\r\n<li style=\"text-align: justify;\"><strong>Declare na seção private o método que é o responsável por gerar o processamento demorado</strong>, doravante chamado simplesmente de <strong>MD</strong> (Método Demorado). No nosso exemplo, este método é 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ção de nosso método adicional (doravante chamado simplesmente de <strong>MA</strong>) que tem o objetivo de contar a quantidade de elementos que serão processados (<strong>DirectoryTreeFileCount</strong>). Ainda na seção private declare quaisquer outros métodos adicionais (<strong>MAs</strong>) que serão necessários para que o MD possa funcionar. Use a classe como você faria com qualquer classe comum, declarando todos os métodos como privados. Não use variáveis globais ou locais na unit UDirectoryTreeSize. Caso precise de variáveis, use campos da classe e todos eles também privados e com propriedades públicas correspondentes. Se você já criou uma classe em sua vida, vai notar que nada disso é novo ou especial;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Altere a definição do seu MD, de forma a incluir nele o método DoProgress</strong>, o qual gera o evento <strong>OnProgress</strong> no qual o <strong>TProgressBar</strong> pode ser atualizado na aplicação. O local exato da colocação do DoProgress depende da sua implementação. Normalmente ele deve ser posto dentro de um loop que vai \"rodar\" na mesma quantidade de vezes que o número de iteraçõ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 a implementação do método DoProgress. O evento OnProgress tem dois parâmetros, um parâmetro <strong>PText</strong> do tipo String e outro parâmetro <strong>PNumber</strong> do tipo Cardinal (DWord). Toda vez que o evento OnProgress é ativado sua aplicação pode ler os valores de PText e PNumber a fim de atualizar um TLabel com informações relevantes, por exemplo. Por este motivo, antes de executar o DoProgress as propriedades <strong>Text</strong> e <strong>Number</strong> (declaradas em <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âmetros PText e PNumber estarão preenchidos com informações que podem ser exibidas na aplicação. <strong>Usar estes dois parâmetros é absolutamente <span style=\"text-decoration: underline;\">opcional</span></strong>. Eles existem apenas como um bônus. O mais importante é chamar DoProgress, porque ele gerará o evento que informa que o TProgressBar precisa ser atualizado, usando seu método StepIt, ou incrementando sua propriedade Position;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Para cada parâmetro do MD, crie um campo privado na classe, que seja do mesmo tipo do parâmetro</strong>. No nosso exemplo o MD possui o parâmetro <strong>PInitialDir</strong>, logo, criamos um campo <strong>FInitialDir</strong> (linha <a href=\"#udirectorytreesize.11\" rel=\"alternate\">11</a>). Caso houvesse mais parâ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âmetro dos MAs, crie campos privados que sejam dos mesmos tipos de cada parâmetro</strong>. No nosso exemplo o MA <strong>DirectoryTreeFileCount</strong> possui o mesmo parâmetro que o MD (<strong>PInitialDir</strong>), logo, a criação de um campo privado para ele (<strong>FInitialDir</strong>) já foi coberta no passo 3;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Para cada campo criado nos passos 3 e 4 crie uma propriedade correspondente</strong>. Na linha <a href=\"#udirectorytreesize.17\" rel=\"alternate\">17</a> foi criada a propriedade <strong>InitialDir</strong>. A classe precisa conhecer todas as variáveis que serão necessárias para que seus métodos internos possam funcionar, portanto, se seus métodos internos precisarem de mais informaçõ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ção de acesso, ou seja, esta propriedade é somente para escrita (<strong>write-only</strong>). Isso não é necessário, mas como o parâmetro <strong>PInitialDir é somente de entrada</strong>, eu resolvi criar a propriedade mantendo o mesmo padrão. <strong>Se houvesse um parâmetro out</strong>, a propriedade correspondente deveria ser read-write. Em suma, por motivos óbvios, as propriedades que representam parâ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çã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çã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ê 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ção incorreta da classe, além disso, faz todo sentido que esta propriedade seja somente leitura, já 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étodo Execute na seção public da classe</strong> (linha <a href=\"#udirectorytreesize.16\" rel=\"alternate\">16</a>). Este é o método que deve conter a lógica da thread, em outras palavras é nele onde o MD precisa ser executado. <strong>Vá para a implementação do mé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ê tenha precisado implementar um MA para calcular a quantidade de iterações do seu MD</strong>, chame primeiramente o MA e retorne na <strong>propriedade Max</strong> a quantidade de iterações calculadas, em seguida, execute o <strong>método DoMax</strong>. Ao executar o método DoMax, um <strong>evento OnMax será ativado</strong> e sua propriedade <strong>PMax</strong> conterá aquilo que foi retornado na propriedade Max. Na sua aplicação, no manipulador do evento OnMax, <strong>você configura a propriedade Max do seu TProgressBar</strong>. As linhas <a href=\"#udirectorytreesize.92-93\" rel=\"alternate\">92 e 93</a> mostram como deve ser feito; primeiro chamamos o método <strong>DirectoryTreeFileCount</strong>, o qual retorna na propriedade Max e logo em seguida há a chamada ao método DoMax, o qual vai gerar o evento OnMax, que pode ser manipulado na aplicação principal para configurar o TProgressBar. Note que DirectoryTreeFileCount foi chamada com o parâmetro FInitialDir, o qual deve ter sido configurado após a criação da instância de TDirectoryTreeSize. <strong>Não se preocupe, isso será mostrado posteriormente</strong>;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Imediatamente após a execução do MA, caso haja um, é hora de chamar o MD</strong>. Caos seu MD seja uma função, execute-o exatamente como no exemplo (linha <a href=\"#udirectorytreesize.94\" rel=\"alternate\">94</a>). Note que o retorno da função é colocado em <strong>FResult</strong>, o que é esperado, já que a propriedade associada (<strong>Result</strong>) serve exatamente para isso. Caso seu MD não precise retornar nenhum valor, apenas processar algo, entã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 precisem de inicialização prévia (criação), você deve, na seção public da classe, declarar o mé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;\"> e não se esqueça de implementar o mé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ém na seção public, destruindo (Free), cada um dos campos criados no construtor. </span></li>\r\n</ol>\r\n<div> </div>\r\n<hr class=\"adsensesnippet\" />\r\n<h2>Como utilizar a classe TDirectoryTreeSize? (ou sua própria classe, como preferir)</h2>\r\n<p style=\"text-align: justify;\">Esta é a parte mais fácil e mais compensadora deste artigo. Abaixo está uma parte da unit <strong>UFORMPrincipal</strong>, que contém a classe <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órios \"\' + EDITDiretorioInicial.Text + \'\" é \' + FormatFloat(\'###,###,###,###,##0 bytes\',FDirectoryTreeSize.Result)),PChar(Format(\'Processamento concluí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çã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çã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 a 71</a>). No nosso exemplo, este manipulador é ativado sempre que um arquivo tem seu tamanho lido. Na linha <a href=\"#uformprincipal.69\" rel=\"alternate\">69</a> <strong>o valor de TProgressBar é incrementado</strong>. <strong>TProgressBar.StepIt é um método que adiciona o valor da propriedade TProgressBar.Step à propriedade TProgressBar.Position</strong>. Existem pessoas que preferem atribuir diretamente à propriedade TProgressBar.Position seu valor +1, mas eu prefiro ser mais prático e usar TProgressBar.StepIt, que já 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ão atribuídos de forma formatada a um TLabel. Na linha <a href=\"#uformprincipal.71\" rel=\"alternate\">71</a>, como uma característica adicional, eu faço um <strong>calculo simples que retorna em um TLabel o percentual atual</strong>, porque todos nó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ção private de <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> é ativado quando o MA de contagem de arquivos retorna a quantidade total de arquivos que serão processados. Este evento tem apenas um parâmetro (<strong>PMax</strong>), o qual retorna esta quantidade. É neste evento onde devemos configurar o <strong>TProgressBar</strong>. Na linha <a href=\"#uformprincipal.61\" rel=\"alternate\">61</a> <strong>é 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 é configurada como zero</strong> para que o TProgressBar \"esvazie\". Na linha <a href=\"#uformprincipal.63\" rel=\"alternate\">63</a> é configurada a propriedade <strong>TProgressBar.Max com o valor do parâmetro PMax</strong>. É 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 é 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ção private de <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>). O evento <strong>OnTerminate</strong> é ativado <strong>quando o método Execute de uma thread termina</strong>. O fim do método Execute <strong>sinaliza o fim do MD</strong> e por isso, caso o MD seja uma função, <strong>é neste evento onde devemos obter o valor retornado pela mesma</strong>. É também neste evento onde devemos <strong>reabilitar controles que foram desabilitados antes do início da thread</strong>. Na linha <a href=\"#uformprincipal.76\" rel=\"alternate\">76</a> exibimos uma mensagem para o usuário informando o tamanho total de todos os arquivos somados na estrutura de diretórios. Esse valor está em <strong>FDirectoryTreeSize.Result</strong>. Exibimos também o tempo que o procedimento levou para ser concluí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í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étodo para iniciar e configurar a instância de TDirectoryTreeSize</strong>. Isso também pode ser feito diretamente num manipulador de eventos, como eu fiz, mas admito que criar um método para isso é mais elegante. Como o intuito aqui é ser o mais direto possível nas explicações, codificar tudo no manipulador do evento OnClick de um botão é perfeitamente válido e vai funcionar à contento. As linhas <a href=\"#uformprincipal.41-56\" rel=\"alternate\">41 a 56</a> 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ó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ório inicial, a partir do qual será feita a varredura recursiva. <strong>EDITDiretorioInicial</strong> é 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ós a outra, a fim de manter o có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ó precisa ser criado e atribuído <strong>caso haja um método interno que calcula a quantidade de elementos (itens) iteráveis ANTES da realização do MD</strong>. Caso a quantidade de elementos iteráveis seja conhecida previamente, você não deve ter criado um MA para calcular este valor e consequentemente o evento OnMax nunca será ativado, porque não é necessário. Como no nosso exemplo nós precisamos calcular a quantidade total de arquivos dos quais o tamanho deve ser retornado, então <strong>o evento OnMax será ativado assim que o total de arquivos for obtido </strong>e o parâmetro <strong>PMax conterá este total</strong>. O evento <strong>OnProgress</strong> será ativado a cada vez que um arquivo tiver seu tamanho obtido. No manipulador desse evento (<strong>DoProgress</strong>) é possível obter o nome do arquivo e o seu tamanho individual nos parâmetros <strong>PText</strong> e <strong>PNumber</strong>. O evento <strong>OnTerminate</strong>, manipulado pelo método <strong>DoTeminate</strong>, é um evento de <strong>TThread</strong> e <strong>é ativado sempre que uma thread termina, e imediatamente ANTES dela ser liberada da memória (caso ela tenha sido criada com FreeOnTerminate = True)</strong>. <strong>É 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ção, seu retorno já é conhecido;<br /><br /></li>\r\n<li style=\"text-align: justify;\"><strong>Execute procedimentos \"pré-MD\"</strong>. As linhas <a href=\"#uformprincipal.50-53\" rel=\"alternate\">50 a 53</a> contém código que <strong>deve ser executado ANTES do MD ser executado na thread</strong> e o que vai nestas linhas, depende de sua implementação. Não há nada de muito especial. No caso do exemplo, um TLabel está sendo \"zerado\" na linha <a href=\"#uformprincipal.50\" rel=\"alternate\">50</a>, um campo de contagem de tempo está sendo preenchido com o valor atual (Now) na linha <a href=\"#uformprincipal.51\" rel=\"alternate\">51</a>, o TButton onde todo este código está, é desabilitado (para evitar cliques múltiplos) na linha <a href=\"#uformprincipal.52\" rel=\"alternate\">52</a> e, finalmente, o TEdit onde se digita o parâmetro InitialDir é 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étodo Resume</strong> (linha <a href=\"#uformprincipal.55\" rel=\"alternate\">55</a>). Como por padrão nossa classe <strong>TDirectoryTreeSize é uma neta de TThread com CreateSuspended = True</strong>, então, para iniciar a thread, simplesmente executamos seu método Resume, o qual tem por finalidade continuar a execução de uma thread suspensa. Se você tem um Delphi mais recente, no lugar de Resume coloque Start, pois o método Resume foi depreciado;</li>\r\n</ol>\r\n</li>\r\n</ol>\r\n<p>Apó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é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ê agora deve estar pensando que isso tudo foi feito apenas para implementar o uso correto de uma simples TProgressBar, mas não é bem assim. Primeiramente, não é tanto trabalho como parece. O artigo ficou grande porque ele é didático, mas ao olhar o código fonte do exemplo anexado, se nota que não tem muita coisa escrita e se você levar em conta que precisa implementar apenas uma classe (TDirectoryTreeSize no exemplo) e depois usá-la, menos código ainda vai sobrar para ser escrito.</p>\r\n<p style=\"text-align: justify;\">Em segundo lugar, mas não menos importante, apesar do título do artigo falar do TProgressBar, este é apenas a ponta do iceberg dentro do contexto, em outras palavras, o \"pretexto\", para se usar um TProgressBar de forma correta é 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 é: <strong>Sempre que você tiver algo que vai demorar um tempo considerável, é preciso colocar esse \"algo\" em uma thread, isso garante que sua aplicação permaneça responsiva e processando outras mensagens (Windows Messages) importantes sem imprevistos. Além disso, para não deixar seu usuário a ver navios, você precisa mantê-lo informado acerca do andamento da tarefa e é aí 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 > 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&view=article&id=122&catid=80&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&view=article&id=122&catid=80&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&view=article&id=97&catid=80&Itemid=493\">Serialização de Objetos & 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 & Library Path\" alt=\"Apresentando os vilões: Search Path & Library Path\" />\r\n<h2>Apresentando os vilões: Search Path & 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 > 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 "programação teórica". 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 <b>IInterface</b> 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 <b>TObject</b> 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 > New > Other > Delphi Projects > 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 <> 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á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 <a href=\"https://notepad-plus-plus.org/\" rel=\"alternate\">Notepad++</a>, 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.</p>\r\n','\r\n<p style=\"text-align: justify;\">Por uma feliz coincidê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&view=article&id=102&catid=80&Itemid=493\">Como usar corretamente uma barra de progresso (TProgressBar)?</a>\", então, a validação de um XML com um XSD foi a primeira solução real na qual a classe <strong>TProgressThread</strong> foi utilizada. Se você ainda não leu o artigo em questão, recomendo que o faça agora, clicando <a href=\"index.php?option=com_content&view=article&id=102&catid=80&Itemid=493\" rel=\"alternate\">aqui</a>.</p>\r\n<p style=\"text-align: justify;\">A primeira versão desta solução utilizou uma forma de validação muito bá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, mostrando o resultado da validação para cada elemento (nó xml) validado. Isso dá ao usuário uma informação precisa acerca do que está errado.</p>\r\n<p style=\"text-align: justify;\">Após algumas pesquisas eu descobri o que faltava e ajustei a versão inicial. O resultado veio do jeito que eu queria e eu já ia entregar a solução, quando resolvi comparar a saída de meu validador, com a saída do validador do Notepad++. Foi aí que eu descobri que ele não estava validando corretamente as restrições ú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çã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íficas para manipulação de conteúdo e manipulação de erros de validação (<strong>ContentHandler</strong> e <strong>ErrorHandler</strong> respectivamente). Cada uma destas propriedades deve receber uma instância de uma classe específica. Uma classe que implementa <strong>IVBSAXErrorHandler</strong> e outra que implementa <strong>IVBSAXContentHandler</strong>. Isso dá ao programador um controle absoluto do que fazer em cada situação. Eu não usei todas as capacidades desta implementação, me concentrei apenas na validação, mas certamente é possível realizar muito mais coisas com ela.</p>\r\n<p style=\"text-align: justify;\">Abaixo está a unit <strong>UValidateXMLXSD</strong>. É nela onde está a classe <strong>TValidateXMLXSD</strong>, derivada de TProgressThread, e que é a responsável por executar a validaçã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çã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çã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ários. TSaxErrorHandler manipula apenas os erros\r\n // e TTagReaded manipula cada nó lido\r\n SAXXMLReader.errorHandler := TSaxErrorHandler.Create(Result,PIgnoreDuplicates);\r\n SAXXMLReader.contentHandler := TTagReaded.Create(Self);\r\n\r\n // Executa a validação\r\n try\r\n SAXXMLReader.ParseURL(PXMLFile);\r\n except\r\n { Evita que as exceções decorrentes de erros de validaçã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ão usado }\r\nend;\r\n\r\nfunction TSaxErrorHandler.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { Não usado }\r\nend;\r\n\r\nfunction TSaxErrorHandler.GetTypeInfoCount(out Count: Integer): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { Nã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ão usado }\r\nend;\r\n\r\n{ TMySaxHandler }\r\n\r\nprocedure TSaxContentHandler.characters(var strChars: WideString);\r\nbegin\r\n { Este método é executado para exibir o conteúdo de um tag. Normalmente o que\r\n acontece é <tag>strChars</tag>, logo este procedure pode ser usado para obter\r\n texto plano que está 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étodo é executado apó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étodo é executado quando um tag de fechamento é encontrado }\r\n FPath.Delete(Pred(FPath.Count));\r\nend;\r\n\r\nprocedure TSaxContentHandler.endPrefixMapping(var strPrefix: WideString);\r\nbegin\r\n { Nã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ão usado }\r\nend;\r\n\r\nfunction TSaxContentHandler.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { Não usado }\r\nend;\r\n\r\nfunction TSaxContentHandler.GetTypeInfoCount(out Count: Integer): HResult;\r\nbegin\r\n Result := E_NOTIMPL; { Não usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler.ignorableWhitespace(var strChars: WideString);\r\nbegin\r\n { Nã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ão usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler.processingInstruction(var strTarget, strData: WideString);\r\nbegin\r\n { Não usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler.skippedEntity(var strName: WideString);\r\nbegin\r\n { Não usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler.startDocument;\r\nbegin\r\n { Este método é executado antes da leitura do documento começar }\r\nend;\r\n\r\nprocedure TSaxContentHandler.startElement(var strNamespaceURI, strLocalName, strQName: WideString; const oAttributes: IVBSAXAttributes);\r\nbegin\r\n { Este método é executado quando um tag de abertura é encontrado }\r\n FPath.Add(strLocalName);\r\nend;\r\n\r\nprocedure TSaxContentHandler.startPrefixMapping(var strPrefix, strURI: WideString);\r\nbegin\r\n { Não usado }\r\nend;\r\n\r\nprocedure TSaxContentHandler._Set_documentLocator(const Param1: IVBSAXLocator);\r\nbegin\r\n { Nã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ão tem como eu explicar todas as linhas dessa unit, por preguiça e por falta de conhecimentos aprofundados (<strong>não vou falar do que não tenho certeza</strong>), mas vou explicar o que é mais relevante. Para coisas mais simples eu mantive os comentários na pró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>) é nossa thread de trabalho (derivada de TProgressThread). O método <strong>ValidateXMLXSD</strong> (linha <a href=\"#uvalidatexmlxsd.15\" rel=\"alternate\">15</a>) dessa classe, é o responsável pela validação do XML. Note que a classe possui 3 propriedades de entrada (write-only) e uma propriedade de saída (read-only) nas linhas <a href=\"#uvalidatexmlxsd.20-23\" rel=\"alternate\">20 a 23</a>. As propriedades de entrada são <strong>XMLFile</strong>, <strong>XSDFile</strong> e <strong>IgnoreDuplicates</strong>. Elas mapeiam para parâmetros do método ValidadeXMLXSD. Já a propriedade Result vai conter o resultado da validação, retornado pela função ValidadeXMLXSD. Este resultado é uma lista de erros (<strong>TStringList</strong>) a qual, obviamente, estará 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ém uma unit estranha de nome <strong>MSXML2_TLB</strong>. Esta unit não vem com o Delphi, mas você precisa adicioná-la também ao seu projeto. Eu também não a estou distribuindo no zip anexado a estre artigo por um simples motivo: você pode gerar esta unit usando o próprio Delphi! Se você nunca importou uma biblioteca de tipos (Type Library), <a href=\"#cotld\" rel=\"alternate\">no final deste artigo eu ensino como gerar a MSXML2_TLB.pas</a>, mas por ora, vou considerar que você já 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> (<strong>TSaxErrorHandler</strong>), <a href=\"#uvalidatexmlxsd.49\" rel=\"alternate\">49</a> (<strong>TSaxContentHandler</strong>) e <a href=\"#uvalidatexmlxsd.74\" rel=\"alternate\">74</a> (<strong>TTagReaded</strong>) definem 3 classes que serão usadas como classes manipuladoras pela interface ISAXXMLReader. As duas primeiras são implementações diretas das interfaces IVBSAXErrorHandler e IVBSAXContentHandler respectivamente. A terceira (TTagReaded) é uma especialização (classe filha) de TSaxContentHandler. Eu poderia usar diretamente a classe TSaxContentHandler, mas resolvi implementar uma classe filha como exemplo, pois podemos implementar várias classes filhas com funcionalidades distintas, e que contém uma funcionalidade comum (classe pai). Isso poderia ter sido feito també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étodo principal da thread, responsável por realizar a validaçã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á a atribuição das classes manipuladoras às propriedades <strong>SAXXMLReader.ErrorHandler</strong> e <strong>SAXXMLReader.ContentHandler</strong>. Note que a atribuição é feita simplesmente criando uma instância de cada uma das classes diretamente na sua propriedade correspondente. Como as propriedades são interfaces, não é necessá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ão foi erro de digitação e nem é uma gambiarra. Eu chamo essa técnica de \"mudinho\" ou \"bico calado\". Bom, não é uma técnica de fato, mas foi uma necessidade. O método <strong>ParseURL</strong> (linha <a href=\"#uvalidatexmlxsd.147\" rel=\"alternate\">147</a>) inicia o processo de validação nó a nó do XML. Quando um erro é encontrado em um nó, um flag interno de erro é ativado, mas o processamento continua até que não sobre mais nenhum nó. Ao terminar o processamento, o método ParseURL verifica o flag interno e se ele estiver ativado, uma exceção é levantada. Como nesta implementação eu estou salvando cada erro numa lista (TStringList), não é necessário manipular a exceção, pois caso a lista contenha itens eu saberei que houve erros, logo, eu estou emudecendo qualquer exceção levantada por ParseURL. Você poderia manipular a exceção e exibir nela uma mensagem mais amigável ao usuário. Eu não fiz isso porque este método seria usado por mim numa rotina em lote (vários arquivos sendo validados), logo, eu não poderia mostrar uma mensagem toda vez que houvesse erros. Outra coisa que poderia ser feita na manipulação desta exceção seria adicionar um item a mais na lista de erros. Tudo isso fica a critério do desenvolvedor. A solução, como está, atende às minhas necessidades.</p>\r\n<p style=\"text-align: justify;\">Nas 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> estão declarados os métodos que são executados quando o parser encontra problemas. <strong>TSaxErrorHandler.Error</strong> é executado quando há um erro de validação em um nó mas o parser continua. <strong>TSaxErrorHandler.FatalError</strong> é executado quando um erro grave é encontrado e neste caso o parser para imediatamente. <strong>TSaxErrorHandler.IgnorableWarning</strong> é executado quando há algo estranho em algum nó, mas que nã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á o método que é executado para cada nó lido. Especificamente, o método <strong>TTagReaded.StartElement</strong> (<strong>TSaxContentHandler.StartElement</strong>) é executado cada vez que um tag de abertura é encontrado. Nas linguagens de marcação, os elementos podem ser compostos por um tag de abertura (<a>, por exemplo) e um tag de fechamento (</a>, por exemplo) ou apenas um tag de abertura (<br />, por exemplo), logo, este método é executado para todo e qualquer tag (nó do XML) encontrado e é o local ideal para se executar o mé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í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ão vou explicar o que cada uma destas linhas faz porque isso já foi coberto no artigo \"<a href=\"index.php?option=com_content&view=article&id=102&catid=80&Itemid=493\">Como usar corretamente uma barra de progresso (TProgressBar)?</a>\". Se você ainda não o leu, recomendo fazer isso. O artigo está muito bem escrito por um grande amigo meu, o qual conheço muito bem :) Ao terminar a validação do arquivo, o evento <strong>OnTerminate</strong> vai ser ativado. É nele onde devemos verificar a resposta (lista de erros) e gerar uma saída adequada para o usuário. Neste exemplo eu usei um <strong>TMemo</strong> de nome <strong>MEMO</strong> para mostrar o resultado da validaçã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á-los }\r\n if FValidateXMLXSD.Result.Count > 0 then\r\n begin\r\n MEMO.Lines.Add(\'Os seguintes erros de validaçã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ão contém erros!\'#13#10);\r\n finally\r\n FValidateXMLXSD.Result.Free;\r\n end;\r\n MEMO.Lines.Add(\'-------------\'); { isso é apenas um separador. Pode ser omitido }\r\nend;</code></pre>\r\n<p>Basicamente isso é tudo que você 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) é:</p>\r\n<blockquote>\r\n<p style=\"text-align: justify;\">Um arquivo binário que armazena informações sobre propriedades e métodos de objetos COM ou DCOM, numa forma que é acessível a outras aplicações em tempo de execução. Usando uma biblioteca de tipos uma aplicação pode determinar quais interfaces um objeto suporta e invocar os métodos da interface deste objeto. Isso pode ser feito mesmo se o objeto e a aplicaçã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ém informações sobre tipos (objetos) e podem ser usadas para instanciar estes objetos em qualquer aplicaçã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íveis na biblioteca <strong>MSXML</strong> que é uma biblioteca de tipos. Precisamos então importar esta dll com o Delphi a fim de gerar a unit MSXML_TLB.pas, a qual é 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 > 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ção <strong>Import a Type Library</strong> e pressione o botão <strong>Next >></strong>. A pró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ões de biblioteca disponíveis, v3.0, v4.0 e v6.0. Escolha a versão mais recente, no meu caso é a versão 6.0. Existe uma observação muito importante a respeito destas versões. Não esqueça de ler a <a href=\"#versoes\" rel=\"alternate\">última seção deste artigo</a>. Clique o botão <strong>Next >></strong>. A pró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á e pressione <strong>Next >></strong>. A pró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ção <strong>Add unit to <NomeDoProjeto> project</strong> e pressione o botã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 <NomeDoProjeto> e poderá 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ões de MSXML e os arquivos anexados a este exemplo</h2>\r\n<p style=\"text-align: justify;\">Quando eu desenvolvi esta solução de validação eu importei a versão 6.0 de MSXML, sendo assim, dentro do arquivo UValidateXMLXSD, existem referências a esta versã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  da versã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ão mais recente disponível não seja a 6.0, você precisará alterar as 3 linhas citadas no parágrafo anterior de forma a conter a versão correta, do contrário podem acontecer coisas estranhas ou a classe TValidateXMLXSD pode não funcionar por completo. Você precisará, pois, usar 3.0 ou 4.0 nos identificadores, dependendo da versã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) > 0)\r\n or (Pos(\'-\',Result) > 0)\r\n or (Pos(\'*\',Result) > 0)\r\n or (Pos(\'/\',Result) > 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(\'> ValorString = \', Op1.ValorString);\r\n WriteLn(\'> ValorInteiro = \', Op1.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',Op1.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', Op2.ValorString);\r\n WriteLn(\'> ValorInteiro = \', Op2.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',Op2.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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(\'> ValorString = \', OpR.ValorString);\r\n WriteLn(\'> ValorInteiro = \', OpR.ValorInteiro);\r\n WriteLn(\'> ValorDouble = \', FormatFloat(\'0.##########\',OpR.ValorDouble));\r\n WriteLn(\'> 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ão necessá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ão necessários. Neste artigo, por outro lado, serão abordados 3 usos típicos do TClientDataSet que certamente farão você tirar o máximo de proveito deste que é sem dúvida alguma um dos componentes mais versáteis disponíveis no Delphi. O terceiro uso (Modelo de Maleta), no meu entendimento, é o uso mais avançado e é a razão da existência do TClientDataSet. Se você não conhece o modelo de maleta, leia este artigo! Esta é mais uma <strong>versão brasileira by myself</strong> de um excelente artigo publicado por <strong>Robert E. Swart</strong>. Prepare-se para o peso desse conteúdo!</p>\r\n','\r\n<p style=\"text-align: justify;\">Este artigo está sendo organizado de forma diferente dos demais artigos do meu site. Ele foi dividido em páginas, já que serão abordados três assuntos. A intenção com isso é facilitar a busca de informações, fornecendo links diretos para os três assuntos dentro do mesmo artigo. Vou utilizar este modo de organização em outras publicaçõ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 é MVP da Embarcadero e vencedor do prêmio Spirit Of Delphi de 1999 (junto com Marco Cantù). Ele é autor, instrutor, revendedor, desenvolvedor, solucionador de problemas, consultor e webmaster. O artigo original encontra-se em <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ão hoje obsoletas, entretanto, o assunto de uma forma geral é indispensável para o entendimento pleno do TClientDataSet, partindo do uso mais básico até culminar com o seu uso de forma plena no DataSnap, através da habilitaçã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é. Recomendo fortemente a leitura dessas notas, as quais são identificadas por números entre colchetes, assim: <em>[1]</em>. As explicações incluem aprofundamento das informações e ajuda a respeito de possíveis mudanças de tecnologia e localização de menus no Delphi.</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">Abaixo segue a versão brasileira.</p>\r\n<hr />\r\n<p style=\"text-align: justify;\">Neste artigo, cobriremos o uso do componente TClientDataSet em três situaçõ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ções DataSnap. Vou usar Delphi 7, bem como Delphi 8 (para .NET) e Delphi 2005 para ilustrar o uso do TClientDataSet em aplicações VCL (para .NET). No entanto, técnicas semelhantes podem ser aplicadas no Kylix (no Linux) e C ++ <a title=\"Apesar de aparentemente as tecnologias usadas parecerem defasadas, todo o conteúdo deste artigo pode ser usado em quaisquer versões de Delphi mais recentes. As técnicas descritas aqui são atemporais e extremamente úteis. Eu refiz todos os exemplos descritos em Delphi XE5 com um mínimo de alteraçõ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 stand-alone é a solução perfeita para situações em que são necessários executáveis autônomos, sem a necessidade de instalar mecanismos de banco de dados adicionais ou drivers. Como o TClientDataSet carrega todos os dados na memória ele é muito rápido, mas o tamanho das tabelas é limitado à quantidade de memória disponível (e o tempo de carga/persistência de dados também aumenta correspondentemente). Além disso, não podemos executar consultas SQL, mas filtros e outras operações podem ser realizadas.</p>\r\n<h3>Por que um TClientDataSet local?</h3>\r\n<p style=\"text-align: justify;\">Os benefícios da utilização do TClientdataSet como um DataSet local sobre, por exemplo, o BDE são numerosos. Em primeiro lugar, o TClientdataSet está contido dentro de uma única DLL chamada <strong>MIDAS.DLL</strong>. Apenas soltá-la dentro do diretório <a title=\"Atualmente o mais indicado é usar os diretórios System32 ou SysWOW64\" href=\"#\" rel=\"bookmark\">Windows\\System</a> (ou deixá-la no mesmo diretório que o executável Delphi) e você está apto a usar o componente. A partir do Delphi 6, você pode até mesmo adicionar a unit <strong>MidasLib</strong> à cláusula <strong>uses</strong> do arquivo <strong>.dpr</strong> do seu projeto. Ao fazer isso, toda a biblioteca MIDAS.DLL será incorporada dentro do seu executável (que irá crescer cerca de 200K), resultando em um verdadeiro executável autônomo de configuração-zero! Compare isto com a instalação do BDE no seu computador cliente. E mesmo que você decida usar <a title=\"Ou qualquer outro tipo de componente de conexão que não exija instalação na máquina-cliente, como o FireDAC, o UniDAC, o ZeosLib, etc.\" href=\"#\" rel=\"bookmark\">ado</a>, por exemplo, que provavelmente já estará disponível na máquina do seu cliente, você precisará garantir que o banco de dados a acessar (Access, SQL Server, etc.) também esteja presente, funcional e com as bibliotecas de acesso (client libraries) disponíveis. Em suma, o MIDAS.DLL é provavelmente um dos \"bancos de dados\" mais fáceis de instalar que você já viu até então!</p>\r\n<p style=\"text-align: justify;\">O TClientDataSet é também uma das implementações de DataSet mais rápidas que existem. Classificação e filtragem são feitas com uma velocidade impressionante. Com toda essa velocidade então ele é um componente perfeito, não é? Não é bem assim! <strong>Essa velocidade toda tem um custo alto</strong> e é também uma das desvantagens potenciais do TClientDataSet. <strong>Tudo é gerenciado na memória</strong>, e cada operação, como classificação, filtragem ou busca também é feito na memória. Isso explica a velocidade, mas também significa que <strong>um TClientDataSet com uma grande quantidade de dados exige uma grande quantidade de memória em sua má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ácil de carregar um TClientDataSet em tempo de design, é clicar com o botão direito no componente TClientDataSet e selecionar a opção do menu pop-up \"<strong>Assign local data</strong>\". Isto irá mostrar uma caixa de diálogo que lista todos os conjuntos de dados (tabelas, consultas, etc.) disponíveis no TForm atual ou em um TDataModule associado. Ao escolher um item nessa lista e pressionar OK, todos os dados do conjunto de dados escolhido serão atribuídos ao TClientDataSet e você poderá então remover o conjunto de dados fonte do TForm ou TDataModule, ficando apenas com um TClientDataSet stand-alone que contém todos os dados do conjunto de dados de origem. Note que você ainda precisará remover a unit DBTables caso você esteja usando o BDE e quiser fazer sua aplicação totalmente independente do BDE.</p>\r\n<p style=\"text-align: justify;\">Além de usar a opção \"Assign Local Data\", um TClientDataSet também pode carregar e armazenar suas informações no disco. Isto é visível em tempo de design pelas opçõ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 <b>Load from MyBase table...</b>, <b>Save to MyBase XML table...</b>, <b>Save to MyBase XML UTF8 Table...</b> e <b>Save to Binary MyBase file...</b>\" href=\"#\" rel=\"bookmark\">pop-up</a>. O componente TClientDataSet em si contém também estes métodos acessíveis em tempo de execução, bem como os métodos <strong>LoadFromStream</strong> e <strong>SaveToStream</strong>, que você pode redirecionar a diferentes tipos de fluxos (como um TMemoryStream, imediatamente antes de enviar esse fluxo através de uma conexã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 é normalmente chamado formato \"<strong>cds</strong>\", e é o formato binário interno (e não documentado). Pequeno, nativo e quase impossível de 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 é o <strong>XML,</strong> e todos nós sabemos que arquivos XML são abertos, independentes de plataforma e portáteis, no entanto, o formato XML que é usado pelo TClientDataSet ainda é um formato proprietário definido originalmente pela Borland, por isso não é fácil usá-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 é preenchido com dados, nós podemos navegar por ele como qualquer outro conjunto de dados, no entanto há uma diferença que pode ser um benefício ou uma maldição (se você não estiver ciente disso). A principal diferença entre o TClientDataSet e outros conjuntos de dados tradicionais (TTable ou similares) é o fato de o TClientDataSet não salvar automaticamente o seu conteúdo em disco. E mesmo se isso acontecer, ele só salva as alterações que tiverem sido realizadas e nã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 TClientDataSet. <strong>Se os dados dentro do TClientDataSet são carregados por quaisquer outros meios que não a propriedade FileName do TClientDataSet, então ele, obviamente, não sabe onde guardar os dados que tenham sido modificados</strong> (os dados originais devem ter sido carregados a partir do arquivo .dfm). <strong>Se a propriedade FileName for usada, então o TClientDataSet vai salvar o seu conteúdo de volta para o arquivo quando ele for explicitamente desativado (close) (ou destruído)</strong>. No entanto, em algumas situações, pode ser uma boa ideia chamar explicitamente o método SaveToFile (assim como você teria de usar LoadFromFile para carregar os novos dados de volta).</p>\r\n<p style=\"text-align: justify;\">Agora, se você usar LoadFromFile no início da sua aplicação, e SaveToFile no final da mesma, então você vai perceber que o arquivo externo (com o conteúdo do TClientDataset) cresce a uma taxa mais elevada do que seria esperado de poucas mudanças e possíveis adições que você faça no TClientDataSet. Algo está acontecendo dentro desse arquivo e se você o salvar no formato XML, então você descobrirá rapidamente que todas as alterações individuais são salvas, tanto com o valor \"original\" como com o valor \"alterado\" de cada registro. Isto significa que mesmo poucas mudanças vão encher o banco de dados rapidamente com várias versões de registros com alterações, que ocupam mais espaço do que as próprias alterações em si (ou as alterações aplicadas ao conjunto de dados).</p>\r\n<p style=\"text-align: justify;\">Por que isso é 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étodo ApplyUpdates, o qual envia as atualizações pendentes para a camada de conjunto de dados remoto (middleware) e isto tem de incluir tanto a versão original dos registros, como as alterações que serão aplicadas. Se a versão original de um registro não coincide com a versão atual do registro (persistido em banco de dados), a atualização pode não ser aplicada. Este é um problema típico de ambientes multi-usuários que precisa ser resolvido para evitar problemas de integridade de dados, mas ao usar o TClientDataSet como uma solução stand-alone este comportamento é muito indesejado.</p>\r\n<p style=\"text-align: justify;\">E fica ainda pior quando você percebe que o TClientDataSet contém o valor original de todos os registros, bem como todas as alterações, o que significa que quando você carregar o arquivo externo, terá que voltar a aplicar todas as alterações e isto levará tempo, tornando a carga mais e mais lenta com o tempo, à medida que mais e mais mudanças são aplicadas.</p>\r\n<h3>MegeChangeLog</h3>\r\n<p style=\"text-align: justify;\">Felizmente existe um método especial chamado <strong>MergeChangeLog</strong>, o qual faz, tal como o nome sugere, a mesclagem de todas as alterações contidas no TClientDataSet, o que resulta em um arquivo bem pequeno novamente (com apenas os registros e todas as mudanças efetivas aplicadas neles). Obviamente este método nunca deve ser executado em um ambiente multi-camadas, pois ele vai quebrar todas as possíveis chamadas ao método ApplyUpdates que você queira fazer. Contudo, em um ambiente de camada única local, este método é estritamente necessário, pois ele diminui o tamanho (e acelera o carregamento) da representação local do TClientDataSet.</p>\r\n<p style=\"text-align: justify;\">Antes de executar o método MergeChangeLog, é recomendável verificar o valor de <strong>ChangeCount</strong>, que representa o número de alterações que estão atualmente disponíveis no TClientDataSet. Apenas se ChangeCount for maior que zero é que você deve executar o método MergeChangeLog, do contrário isso seria apenas perda de tempo.</p>\r\n<p style=\"text-align: justify;\">Como uma última dica, você pode querer considerar o fato de que um log de modificações inclui a habilidade para \"desfazer\" alterações registro a registro usando os métodos <strong>UndoLastChange</strong>, <strong>CancelUpdates</strong> ou <strong>RevertRecord</strong>. Leia a ajuda do Delphi para maiores detalhes. Estes métodos podem també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 \"cache\" (ou maleta), no qual os cursores unidirecionais podem armazenar seus dados, assim os usuários podem visualizá-los como um conjunto de registros bidirecionais. Isto habilita capacidades SQL, pois é o DBX quem está fornecendo os dados ao TClientDataSet. Nós não precisamos mais nos preocupar com armazenamento local, apenas precisamos executar ApplyUpdates para enviar as alterações ao banco de dados no qual o DBX está conectado.</p>\r\n<h3>O que é o dbExpress?</h3>\r\n<p style=\"text-align: justify;\">O DBX <strong>é uma arquitetura de acesso a dados multi-plataformas, leve, rá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ível (ou seria compilante?) com o DBX para uso com o Kylix, Delphi ou C++ Builder. Um artigo sobre o funcionamento intrí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ç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ático, a EasySoft desenvolveu o \"dbExpress Gateway for ODBC\", que pode ser usado para conectar ao ODBC do UNIX e, via \"ponte ODBC-ODBC\", ele também é 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é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 é literalmente a conexão entre os drivers do DBX e os outros componentes da paleta DBX. Ele é o meio de ligação entre a aplicação e drivers que possibilitam o acesso aos bancos de dados. Se você soltar este componente em um TForm ou TDataModule, você verá apenas 12 propriedades e aquela que é provavelmente a mais usada é a propriedade <strong>ConnectionName</strong>, que pode ser atribuída a um dos valores da lista apresentada. Se você selecionar <strong>IBConnection</strong> então a propriedade <strong>DriverName</strong> terá o valor \"<strong>INTERBASE</strong>\", a propriedade <strong>GetDriverFunc</strong> terá o valor \"<strong>getSQLDriverINTERBASE</strong>\" a propriedade <strong>LibraryName</strong> terá o valor \"<strong>dbxint30.dll</strong>\" e a propriedade <strong>VendorLib</strong> terá 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ê pode abrir a propriedade Params e editar os valores dos parâmetros. Estes são também automaticamente preenchidos, aliás, quando você seleciona um valor para a propriedade ConnectionName. Se você não quiser que isso aconteça, por exemplo, quando estiver escrevendo código não visual de acesso a banco de dados e você quiser informar seus próprios valores para os parâmetros, você pode configurar a propriedade LoadParamsOnConnection como false.</p>\r\n<p style=\"text-align: justify;\">Se você der duplo clique no componente TSQLConnection, você verá as configurações de conexão para cada valor possível da propriedade 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ém é possível alterar as configurações atuais e criar novas configurações, de forma que seja possível selecioná-la na propriedade ConnectionName.</p>\r\n<p style=\"text-align: justify;\">Uma vez que tudo tenha sido configurado de forma adequada, você pode configurar a pripriedade <strong>Connected</strong> como true e, ou obter uma mensagem de erro de conexão, ou ver a propriedade simplesmente mudando para true, o que indicaria sucesso na conexã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ê poderá usar qualquer um dos outros componentes DBX disponíveis, como o TSQLDataSet, que é o mais \"geral\" destes componentes. Sempre comece por configurar a propriedade SQLConnection deste componente para um TSQLConnection disponível. Os componentes TSQLQuery, TSQLStoredProc e TSQLTable podem ser vistos como instâncias especiais de TSQLDataSet. De fato, isso me lembra muito o <strong>ADOExpress </strong>(<strong>dbGo</strong>), no qual o componente <strong>TADODataSet</strong> é o \"pai\" de <strong>TADOQuery</strong>, <strong>TADOStoredProc</strong> e <strong>TADOTable</strong>. Na verdade o núcleo dos componentes TxxxDataSet de ambos, dbGo e DBX, compartilham as propriedades <strong>CommandType</strong> e <strong>CommandText</strong>, com as quais você pode determinar o subtipo do componente. Se você configurar o valor de CommandType para <strong>ctQuery</strong>, então a propriedade CommandText é interpretada como uma consulta SQL. Se você configurar o valor de CommandType para <strong>ctStoredProc</strong>, então a propriedade CommandText especifica o nome de um stored procedure e, finalmente, se CommandType for configurado como <strong>ctTable</strong>, entã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ós podemos configurar CommandType como ctTable e CommandText como \"customer\" e com isso selecionar a tabela \"customer\". Se você configurar a propriedade Active como true, você obterá os dados atuais em tempo de projeto e, se você configurar a propriedade LoginPrompt do componente TSQLConnection como false, você nem mesmo verá a tela de login. Esse é um comportamento padrã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ós podemos direcionar o foco para a paleta <strong>Data Controls</strong> e começar a usar um de seus componentes para exibir os dados que recebemos do TSQLDataSet ativo. Note que nós não podemos usar todos estes componentes agora sem algumas considerações especiais. Este é o ponto onde a maior diferença entre as arquiteturas do BDE e do DBX está presente. O componente TSQLDataSet e seus componentes derivados retornam um <strong>cursor unidirecional</strong>. Isso significa que você pode mover-se apenas para frente e nunca para trás e isso não é nada útil quando pretendemos usar, por exemplo, um <strong>TDBGrid</strong> (nós vemos apenas um registro de cada vez!) e muito menos quando usamos um <strong>TDBNavigator</strong>, porque clicando nos botões <strong>Back</strong> ou <strong>First</strong> irá invariavelmente levantar uma exceção!</p>\r\n<p style=\"text-align: justify;\">Por que um cursor unidirecional? Bem, a resposta óbvia é <strong>velocidade</strong>. O BDE nunca foi nosso melhor amigo (podemos considerá-lo um bom amigo, ou um conhecido amigável), mas ele nos ajudou com necessidades simples e pequenas dos bancos de dados. Infelizmente o ovehead e os requisitos gerais de memória do BDE não são pequenos e as tabelas do BDE nunca foram conhecidos por suas \"velocidades incríveis\". Esta era uma área onde a Borland procurou mostrar algumas melhorias reais. A nova arquitetura chamada de 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 é especialmente útil quando você apenas precisa ver os resultados uma vez, ou precisa percorrer os resultados do início ao fim (novamente, uma só vez), por exemplo, em um loop <strong>while not Eof</strong>, ao processar os resultados de uma query ou stored procedure. Situações do mundo real onde isso é útil incluem a geração de relatórios e aplicações para servidores web que produzem dinamicamente páginas html como resultado.</p>\r\n<p style=\"text-align: justify;\">Mas especialmente quando combinado com componentes visuais de acesso a dados, você rapidamente percebe que em ambientes gráficos os usuários certamente vão querer navegar em qualquer direção dentro de Result Sets. Então, você precisa, de alguma forma, colocar os registros em cache a fim de permitir sua exibição em um TDBGrid e também a navegação em qualquer direção. É aí onde o TClientDataSet entra em ação. É totalmente possível e pertinente usar um <strong>TDataSetProvider</strong> ligado ao TSQLDataSet e então usar um TClientDataSet para obter os dados do TDataSetProvider. O resultado é um TClientDataSet que obtém registros (uma vez) de uma fonte unidirecional, o TSQLDataSet. O TDataSetProvider é usado apenas como um meio local de transporte de dados.</p>\r\n<h3>Migração de tecnologia</h3>\r\n<p style=\"text-align: justify;\">O fato de o TClientDataSet estar disponível tanto no Delphi quanto no Kylix significa que existe uma forma simples e rápida de migrar tabelas de bancos de dados locais, como tabelas em Paradox ou dBASE. Este é o primeiro passo a ser feito para migrar do BDE para o DBX: migração de dados. O segundo passo é migrar a aplicaçã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ódigo abaixo de uma aplicação chamada <strong>dbAlias</strong> que eu escrevi. Esta aplicação vai converter todas as tabelas acessadas por um alias (passado via parâ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 é uma forma rápida e grosseira de <a title=\"Esta aplicação não foi testada por mim, contudo mesmo que ela não se aplique mais, a ideia que dela se pode tirar é útil, até mesmo como forma de backup local, por exemplo\" href=\"#\" rel=\"bookmark\">converter</a> suas tabelas Paradox e dBASE referenciadas por um alias do BDE para arquivos cds, os quais podem ser carregados novamente por componentes TClientDataSet em uma outra aplicação. O uso que você fará 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áximo proveito dessa nova tecnologia?</p>\r\n<p style=\"text-align: justify;\">Vou mostrar agora que, ao usar o dbExpress, nós precisamos adotar uma nova forma de olhar os dados (especialmente na hora de salvá-los), porque o DBX dispõe apenas de datasets somente leitura com cursores unidirecionais, por isso, não há como confirmar nossas alteraçõ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 construir uma aplicação usando o DBX. Primeiro inicialize um novo projeto Win32 de forma usual. Solte um componente TSQLConnection no TForm disponível e configure sua propriedade ConnectionName para IBCONNECTION (ou a de sua preferência). Você pode dar duplo clique no TSQLConnection para iniciar o editor de conexõ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íveis na propriedade <strong>TableName</strong>. Neste ponto nós temos um dataset somente leitura e unidirecional (o qual suporta movimentação de registros apenas um passo de cada vez para frente ou de volta ao início, mas nenhuma outra operação). Esse comportamento é excelente caso se pretenda usar um componente TDataSetTableProducer, onde precisamos andar para frente no resultset apenas uma vez, mas não é nada útil em outras situações.</p>\r\n<h3>TClientDataSet</h3>\r\n<p style=\"text-align: justify;\">Para permitir a exibição das informações contidas no TSQLTable (ou qualquer dataset DBX) precisamos armazená-los dentro de um TClientDataSet usando um TDataSetProvider como \"conector\". Então, solte no TForm um componente TDataSetProvider e um TClientDataSet, os quais se encontram na paleta Data Access. Atribua o componente TSQLtable à propridade DataSet do TDataSetProvider e então atribua o nome do TDataSetProvider à propriedade ProviderName do TClientDataSet. No momento em que você abrir o TClientDataSet (por exemplo, configurando sua propriedade Active como True) o conteúdo de TSQLTable será percorrido (apenas uma vez) e os registros serão providos ao TClientDataSet, o qual os armazenará daquele momento em diante. Nós podemos agora usar um TDataSource e (por exemplo) um TDBGrid para exibir o conteúdo disponível no TClientDataSet.</p>\r\n<h3>Manipulação de dados</h3>\r\n<p style=\"text-align: justify;\">O problema da manipulação de dados ocorre quando executamos a aplicação, fazemos algumas alterações em alguns campos e registros e saímos da mesma. Quando abrimos a aplicação novamente nós vemos apenas os valores antigos! O TClientDataSet é perfeito para colocar dados em cache, mas não é capaz de <a title=\"A palavra "atualizar" neste contexto, significa qualquer operação de banco de dados, e não apenas as atualizações propriamente ditas\" href=\"#\" rel=\"bookmark\">atualizar</a> o banco de dados para nós de forma automática.</p>\r\n<p style=\"text-align: justify;\">Como o componente TSQLTable é somente leitura e não é capaz de manipular de forma tradicional um banco de dados (usando métodos <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ções diretamente. O TDataSetProvider, é capaz de gerar os comandos SQL de inserção, atualização e exclusão e enviá-los diretamente ao banco de dados, sem usar o métodos de alto nível, indisponíveis nos componentes do DBX.</p>\r\n<p style=\"text-align: justify;\">Então, para que as operações de banco de dados sejam executadas, precisamos executar o método ApplyUpdates do TClientDataSet. Podemos colocar no TForm um TButton, configurar sua propriedade Caption para \"Aplicar Alterações\" e, dentro do manipulador do evento OnClick desse botão devemos escrever apenas uma linha de có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ções pendentes no TClientDataSet para o banco de dados acessado pelo DBX. Caso um erro aconteça no momento da execução dos comandos -- por exemplo, quando um registro alterado por nós já tiver sido alterado por outro usuário --, um erro conhecido como <strong>Reconcile</strong> (<strong>EReconcileError</strong>) será gerado e poderá ser manipulado no evento <strong>OnReconcileError</strong> do TClientDataSet. Neste evento o usuário poderá decidir como o erro será corrigido. Maiores detalhes de como manipular erros EReconcileError serão dados mais adiante na seção sobre DataSnap deste mesmo artigo.</p>\r\n<h3>Automatizando o ApplyUpdates</h3>\r\n<p style=\"text-align: justify;\">Apesar da solução apresentada funcionar, você não pode realisticamente esperar que seus clientes e usuários finais se lembrem de clicar no botão \"Aplicar Alterações\" quando eles quiserem simplesmente salvar seus trabalhos. Nós podemos facilitar a vida dos nossos usuários executando o método ApplyUpdates de forma automá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ê estar se sentindo bem com essa solução, ainda existem situações que precisamos considerar, por exemplo, ao excluir um registro não existe método Post, logo, o evento AfterPost não vai funcionar neste caso e por isso precisamos colocar também o mesmo código no evento <strong>AfterDelete</strong>.</p>\r\n<p style=\"text-align: justify;\">Finalmente, quando você fecha sua aplicação imediatamente após sua última alteração, mas antes de executar um Post, por exemplo, se você alterou o valor em um <strong>TDBEdit</strong>, mas não confirmou sua ação (Post), então você pode querer que esta alteração seja confirmada também. Isso significa que você precisa executar novamente a mesma linha de código descrita anteriormente, nos eventos <strong>OnDestroy</strong> ou <strong>OnClose</strong> de seu TForm, TDataModule ou TFrame (ou qualquer outros contêiner que você 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ção, na qual usaremos a maior parte de nosso tempo aqui, posiciona o TClientDataSet como uma \"maleta\" do lado do cliente em uma aplicação DataSnap multicamadas. Isso significa que nós podemos desconectar a aplicação cliente e armazenar os dados localmente em arquivo, no formato binário MyBase. Nós podemos, sempre que quisermos, recarregar esses dados e continuar o trabalho localmente até quando uma conexão com o servidor DataSnap for restabelecida, permitindo que nós possamos enviar nossas alterações de volta ao servidor, mediante o uso do método ApplyUpdates.</p>\r\n<p style=\"text-align: justify;\">Vale salientar que nós <strong>não focaremos</strong> nos detalhes do lado do servidor da arquitetura DataSnap, nem nos protocolos de comunicação utilizados nesta arquitetura. O foco será o TClientDataSet e suas habilidades de conexão com provedores locais ou remotos e a aplicação de atualizações ao middleware.</p>\r\n<p style=\"text-align: justify;\">Por fim, a aplicação de atualizações (ApplyUpdates) pode resultar em erros de reconciliação (<strong>Reconcile Errors</strong>), os quais normalmente acontecem quando um ou mais usuários realizaram alterações conflitantes em um mesmo campo de um mesmo registro. Veremos então como podemos detectar e responder a estes erros usando a caixa de diálogo padrão para erros de reconciliaçã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ê pode particionar uma aplicação de forma que ela possa acessar um banco de dados sem precisar de bibliotecas ou programas adicionais nas máquinas locais. Esta arquitetura também permite que você centralize as regras de negócio e processos, além de distribuir o processamento através da rede. O DataSnap suporta uma tecnologia de 3 camadas, a qual em sua forma clá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áquina (servidor)</li>\r\n<li style=\"text-align: justify;\">Um <strong>servidor de aplicação</strong> em uma segunda máquina (camada do meio)</li>\r\n<li style=\"text-align: justify;\">Um <strong>cliente magro</strong> (thin client) em uma terceira má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ência, InterBase, Postgres, Oracle, MySQL, SQL Server, dentre outros. O servidor de aplicação e o cliente magro podem ser desenvolvidos em Delphi, Kylix ou C++ Builder. É no servidor de aplicação onde se concentram as regras de negócio e todas as ferramentas necessárias para acessar e manipular os dados que estão no servidor de banco de dados. Os programas que serão utilizados pelos usuários não fazem nada além de mostrar-lhes os dados de forma que eles possam visualizá-los e editá-los.</p>\r\n<h3>O que é o DataSnap?</h3>\r\n<p style=\"text-align: justify;\">O DataSnap é baseado na tecnologia que permite que os conjuntos de dados sejam empacotados e enviados através da rede como parâmetros nas chamadas a métodos remotos (<strong>Remote Procedure Call - RPC</strong>). Ele inclui tecnologias que permitem converter um TDataSet em dados Variant ou XML do lado do servidor. No lado do cliente esses dados são transformados novamente em <a title=\"Costuma-se chamar estas conversões de objetos em bytes portáveis e vice-versa de <i>serialização de objetos</i>\" href=\"#\" rel=\"bookmark\">TDataSet</a> e exibidos ao usuá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 ângulo um pouco diferente, <span style=\"text-decoration: underline;\">o DataSnap é uma tecnologia que permite mover um conjunto de dados (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ão precisa estar ligado ao BDE ou outros componentes de conexão, como UniDAC ou FireDAC. O único requisito no cliente é a presença da biblioteca do DataSnap, a qual, aliás, se chama MIDAS.DLL. Mesmo assim, você pode dispensar a presença desta DLL, incluindo numa das cláusulas uses de sua aplicaçã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é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ê utilize todos os componentes padrão do Delphi, incluindo <span style=\"text-decoration: underline;\">componentes de acesso a dados <strong>do lado do servidor</strong></span>, mas o lado do cliente é um verdadeiro cliente magro, isto é, ele não deve incluir ou ligar-se diretamente a nenhum banco de dados. Como foi dito antes, a única dependência do cliente magro é 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é agora, muita teoria, mas a melhor maneira de entender o que é o DataSnap e como ele funciona é construindo uma aplicação DataSnap, que consiste de um <strong>Cliente DataSnap</strong> e um <strong>Servidor DataSnap</strong>. Costumo começar com o servidor, onde encapsulo e exporto os conjuntos de dados. Com um servidor funcional, o próximo passo é construir o cliente que se conectará com este servidor e exibirá 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ê começa com uma aplicação nova vazia (<a title=\"Esse caminho muda em alguns Delphis mais recentes. Em Delphi 2006, por exemplo, o caminho é <b>File > New > VCL Forms Application</b>\" href=\"#\" rel=\"bookmark\">File > New > Application</a>). Enquanto o formulário principal da aplicação estiver sendo exibido significa que o SDS estará ativo, em outras palavras, o loop de mensagens da aplicação vai manter o SDS vivo. Configure um título para o formulário principal de forma a identificar o SDS. Além disso eu costumo incluir neste formulário um TLabel com uma fonte grande e legí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ção regular em um servidor de aplicação (middleware database server), você precisa adicionar um Remote DataModule (<strong>RDM</strong>) nela. Este DataModule especial pode ser encontrado na <a title=\"Em versões mais modernas do Delphi, o Object Repository é composto de uma visualização estilo "Windows Explorer" com itens sendo exibidos em pastas, logo, nestas versões existe uma <b>pasta</b> chamada Multitier\" href=\"#\" rel=\"bookmark\">aba</a> \"Multitier\" do Object Repository do Delphi, logo, acesse File > New > Other e vá direto à aba Multitier, a qual mostra vários <a title=\"Este item pode não existir na sua versão de Delphi\" href=\"#\" rel=\"bookmark\">Wizards CORBA</a>, um Remote DataModule e um <a title=\"Este item pode não existir na sua versão de Delphi\" href=\"#\" rel=\"bookmark\">Transitional DataModule</a>. O último pode ser usado com o MTS (Microsoft Transaction Server) antes do Windows 2000 ou COM+ no Windows 2000 ou posterior e não será coberto aqui. É o RDM \"normal\" que você precisa selecionar para criar seu primeiro SDS simples. Ao selecionar o ícone do RDM e clicar no botão OK, o assistente <a title=\"Este nome pode variar\" href=\"#\" rel=\"bookmark\">\"New Remote DataModule Object\"</a> será iniciado.</p>\r\n<p style=\"text-align: justify;\">Existem algumas opções que você precisa especificar (ou garantir que estejam selecionadas corretamente). Primeiramente, \"CoClass Name\", que é o nome da classe interna. Este precisa ser um nome que você possa lembrar facilmente depois, então, eu recomendo que você use <strong>SimpleRemoteDataModule</strong> desta vez. Mantenha a opção \"Instancing\" configurada como <strong>Multiple Instance</strong>, desta forma seu SDS poderá conter múltiplas instâncias do RDM. Mantenha outras opções como estão e pressione OK para, finalmente, gerar o RDM.</p>\r\n<h3><a title=\"Esta seção está focada no BDE, que era a tecnologia nativa de conexão disponível no Delphi na época em que este artigo foi escrito. Obviamente você não precisa (e nem deve) usar mais o BDE. Resumidamente, ignore as referências ao BDE e inclua no RDM componentes correlatos disponíveis no suíte de componentes de conexão de sua preferê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ção do assistente é um RDM que parece muito com um TDataModule regular. Visualmente não existem diferenças, e se você planeja usar o BDE, então você pode começ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 é imprescindível caso você esteja usando o modelo de thread <a title=\"Ao criar o RDM, o modelo de thread padrão é o Apartment, logo, você precisará 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ê tenha incluído um componente TSession você poderá adicionar os outros componentes. Por exemplo, você pode incluir um <strong>TTable</strong> e nomeá-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é agora tudo que foi feito poderia sê-lo em um DataModule regular, mas agora é hora de olhar os aspectos remotos deste DataModule especial (remoto). Na paleta de componentes, vá até a aba <strong>Data Access</strong> onde você encontrará o componente <strong>TDataSetProvider</strong>. Este componente é a chave para exportar conjuntos de dados de um RDM para o mundo exterior, mais especificamente para clientes DataSnap. Solte um componente TDataSetProvider no RDM e configure sua propriedade <strong>DataSet</strong> para TableCustomer. Isto significa que o TDataSetProvider irá prover ou exportar TableCustomer para um cliente DataSnap que se conectar nele (esse cliente será construído posteriormente neste artigo).</p>\r\n<p style=\"text-align: justify;\">Uma propriedade muito importante do TDataSetProvider é a propriedade <strong>Exported</strong>, que é configurada como <strong>true</strong> para indicar que TableCustomer é exportada. Você pode configurar esta propriedade como false para \"esconder\" o fato de que TableCustomer é exportada a partir do RDM, assim, nenhum cliente poderá se conectar a ela. Isso pode ser útil, por exemplo em um servidor rodando constantemente (24x7), onde você precisa fazer o backup de algumas tabelas e precisa garantir que ninguém está trabalhando com elas durante o backup. Com a propriedade Exported do TDataSetProvider configurada como false ninguém pode fazer conexões a ele até que você configure novamente a propriedade como true.</p>\r\n<h3>Compilando o Servidor DataSnap</h3>\r\n<p style=\"text-align: justify;\">Basicamente isso é tudo que precisa ser feito para criar o seu primeiro SDS. A única coisa que resta ser feita é salvar o projeto. Salvei o formulário principal no arquivo \"ServerMainForm.pas\", o RDM foi salvo no arquivo <a title=\"Um arquivo adicional de extensão .tlb pode ter sido criado pelo assistente "New Remote DataModule Object". Caso haja necessidade de salvar este arquivo, nomeie-o com o mesmo nome do RDM, ou seja, <b>RDataMod.tlb</b>.\" href=\"#\" rel=\"bookmark\">\"RDataMod.pas\"</a>, e salvei o projeto do SDS no arquivo \"SimpleDataSnapServer.dpr\". Após ter salvo o projeto, precisamos compilá-lo e executá-lo. Ao executar o SDS -- o qual mostrará apenas o formulário principal, claro -- ele será <a title=\"A partir do Delphi 2007 o registro automático foi descontinuado devido a presença maciça do UAC nos Windows, por isso, se você tentar rodar este programa ele não será registrado automaticamente. Se este for seu caso, basta executá-lo com a linha de comando <b>/RegServer</b>. Para desregistrá-lo, use a linha de comando <b>/UnRegServer</b>. 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á encontrá-lo e então conectar-se a ele. Mesmo que você mova o SDS para outro diretório (na mesma máquina), você precisará apenas executá-lo novamente para que ele seja re-registrado na nova localização. Esta é uma forma muito conveniente de gerenciar servidores DataSnap.</p>\r\n<p style=\"text-align: justify;\">Até aqui, você não escreveu uma linha de código sequer para implementar este servidor DataSnap simples. Vamos ver então o que teremos de escrever ao implementar o cliente DataSnap que se conectará a ele.</p>\r\n<h3>O Cliente DataSnap</h3>\r\n<p style=\"text-align: justify;\">Existem vários tipos de clientes DataSnap que você pode desenvolver. Aplicações comuns de Windows (GUI), Active Forms e até mesmo servidores Web (usando Web Broker ou Internet Express). De fato, praticamente qualquer coisa pode atuar como um cliente DataSnap, como você verá em breve. Por enquanto você deve criar apenas uma aplicação regular de Windows a qual irá atuar como o seu primeiro cliente DataSnap simples que vai se conectar no seu SDS, o qual foi desenvolvido nas seções anteriores. Neste ponto você não deve tentar executar o cliente e o servidor em máquinas separadas. Ao invés disso, execute tudo em uma máquina e então, depois, você pode distribuir a aplicação na rede.</p>\r\n<p style=\"text-align: justify;\">Acesse File > New > Application para inicializar a construção de uma aplicação simples. Neste ponto você decide se quer adicionar ou não um DataModule. A fim de evitar screenshots desnecessários neste artigo, eu não pretendo usar um DataModule. Ao invés disso, vou usar o formulário principal como contêiner para meus componentes nã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ão com o SDS. Esta conexão pode ser feita usando vá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ão são, respectivamente, TDCOMConnection, TSocketConnection, TWebConnection e <a title=\"Este componente pode não existir na sua versão de Delphi\" href=\"#\" rel=\"bookmark\">TCorbaConnection</a> na aba <strong>DataSnap</strong> e o TSoapConnection na aba <strong>Web Services</strong>. Para este primeiro cliente DataSnap simples usaremos o componente <strong>TDCOMConnection</strong>, logo, inclua este componente no seu formulário principal.</p>\r\n<p style=\"text-align: justify;\">O componente TDCOMConnection tem uma propriedade chamada ServerName a qual contém o nome do SDS no qual você pretende conectar. Na verdade, se você abrir a lista disponível na propriedade ServerName no Object Inspector, você verá uma lista de todos os servidores DataSnap registrados na máquina local. No seu caso, esta lista deve conter apenas um item de nome <strong>SimpleDataSnapServer.SimpleRemoteDataModule</strong>, mas eventualmente todos os servidores MIDAS 3 e DataSnap que estiverem registrados serão vistos nesta lista. Os nomes nesta lista consistem de duas partes; a parte antes do ponto, denota o nome da aplicação e a parte após o ponto denota o nome do RDM, então, no caso atual, você vai selecionar o SimpleRemoteDataModule contido na aplicação SimpleDataSnapServer.</p>\r\n<p style=\"text-align: justify;\">Quando você selecionar um servidor, a propriedade ServerGUID será automaticamente preenchida com o valor correto que está salvo no registro do Windows. Desenvolvedores com uma memória prodigiosa podem digitar um valor na propriedade ServerGUID e comprovar que a propriedade ServerName será preenchida como o nome do SDS correspondente. A diversão começa mesmo quando você configura a propriedade <strong>Connected</strong> do componente TDCOMConnection como true. Para que a conexão seja feita, o SDS precisa estar em execuçã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ção do formulário principal do SDS criado nas seçõ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ê comprovou que é capaz de conectar-se a ele é hora de importar alguns DataSets que foram exportados pelo componente TDataSetProvider no RDM. Inclua um TClientDataSet, localizado na aba Data Access, no formulário principal e conecte a sua propriedade <strong>RemoteServer</strong> ao componente TDCOMConnection. Para que o TClientDataSet obtenha dados do SDS você precisa especificar qual TDataSetProvider usar. Em outras palavras, a partir de qual TDataSetProvider você deseja importar os dados que preencherão o TClientDataSet. Utilize a propriedade <strong>ProviderName</strong> para especificar o TDataSetProvider de sua escolha. Abra a lista do combobox associado a propriedade e você verá todos os TDataSetProvider disponí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ê fechou a conexão com o SDS, entretanto, quando você abriu a lista da propriedade a fim de listar todos os componentes TDataSetProvider disponí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ão disponíveis: perguntar ao SDS! 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ê clica no combobox da propriedade ProviderName para exibir sua lista, o SDS será automaticamente iniciado novamente.</p>\r\n<p style=\"text-align: justify;\">Quando você selecionar o RemoteServer e o ProviderName você poderá abrir (ou ativar) o TClientDataSet. Você pode fazer isso configurando a propriedade <strong>Active</strong> do componente como true. Neste momento o SDS estará alimentando dados a partir do TTable de nome TableCustomer, via componente TDataSetProvider através de uma conexão COM para o componente TDCOMConnection que roteia estes dados para o componente TClientDataSet no seu Cliente DataSnap, que estará pronto para ser usado.</p>\r\n<p style=\"text-align: justify;\">Você 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á estava ativado, você verá dados \"ao vivo\" em tempo de projeto, dados estes providos pelo nosso SDS.</p>\r\n<p style=\"text-align: justify;\">Está tudo quase pronto agora. Para finalizar o Cliente DataSnap, primeiramente altere a propriedade Caption do formulário principal para algum nome útil, como por exemplo \"Simple DataSnap Client\" e salve seu trabalho. Nomeie o formulário principal como \"ClientForm\", salve-o como \"ClientMainForm.pas\", e nomeie o projeto como \"SimpleDataSnapClient\". Então, você estará pronto para compilar e executar o Simple DataSnap Client! Novamente você não escreveu nenhuma simples linha de código, mas esteja avisado que isso vai mudar em breve nas próximas seçõ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ê vê os dados de TableCustomer dentro do TDBGrid. Você pode navegar através destes dados, alterar o valor dos campos e mesmo incluir novos registros ou excluir registros existentes. Entretanto, uma vez que você fechar a aplicação, todas as mudanças serão perdidas. Não importa quantas vezes você tentar; as mudanças feitas nos dados do TDBGrid em tempo de execução do Cliente DataSnap afetam apenas o TClientDataSet local e não o TableCustomer no servidor.</p>\r\n<p style=\"text-align: justify;\">O que você acaba de experimentar aqui é aquilo que é chamado de <strong>Modelo de Maleta</strong> (<strong>Briefcase Model</strong>). Usando o modelo de maleta (<strong>MDM</strong>) você é 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áquina e desconecte-a da rede. Você pode então religar a sua máquina e editar o seu conjunto de dados sem estar conectado à rede<br /><br /></li>\r\n<li style=\"text-align: justify;\">Quando a rede estiver disponível novamente, você pode reconectar e atualizar o banco de dados. Um mecanismo especial está disponível para notificá-lo acerca de erros de banco de dados. Isso permite que o usuário possa resolver quaisquer conflitos que ocorram. Por exemplo, se duas pessoas editarem o mesmo registro, então uma delas será notificada do fato e serão apresentadas opções para a resolução do conflito</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">O ponto chave do MDM é que <span style=\"text-decoration: underline;\">você não precisa ter o servidor disponível todo o tempo</span> para poder trabalhar nos seus dados. Esta capacidade é perfeita para usuários com notebooks ou sites, onde você quer diminuir ao máximo o tráfego no banco de dados.</p>\r\n<p style=\"text-align: justify;\">Você agora deve entender que seu Cliente DataSnap atua apenas nos dados locais que estão dentro do TClientDataSet e que é possível salvar estes dados em um arquivo local e carregá-lo novamente em momento oportuno, logo, indo ao que interessa, para salvar o conteúdo atual do TClientDataSet, inclua um TButton no formulário, nomei-o como \"ButtonSave\", configure seu Caption como \"Salvar\" e escreva o seguinte có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 > 0 then\r\n ClientDataSet1.SaveToFile(\'customer.cds\')\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">Este código salva todos os registros contidos no TClientDataSet em um arquivo de nome \"customer.cds\" no diretório atual da aplicação, mas apenas se houve alterações no TClientDataSet (<strong>ClientDataSet1.ChangeCount > 0</strong>). Aliás, \"CDS\" significa \"Client DataSet\", mas você pode usar qualquer nome de arquivo e qualquer extensão, claro. Observe que o segundo parâmetro do método SaveToFile é implicitamente <strong>dfBinary</strong>. Este valor indica que queremos salvar os dados no formato binário (proprietário da Borland). Alternativamente poderíamos utilizar no segundo parâmetro o valor <strong>dfXML</strong> para salvar os dados em formato XML. Um arquivo XML é 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ções. Eu prefiro o formato biná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ário principal, nomeie-o como \"ButtonLoad\", configure seu Caption como \"Carregar\" e escreva o seguinte có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étodo LoadFromFile não precisa de um segundo argumento; ele é suficientemente esperto para determinar se está lendo um arquivo binário ou um <a title=\"O arquivo XML que pode ser carregado pelo TClientDataSet tem uma estrutura específca, no entanto, qualquer XML pode ser carregado por um TClientDataSet após sofrer uma transformação XML, ou seja, uma conversão que transforma uma estrutura XML qualquer numa estrutura XML que o TClientDataSet é capaz de entender (DATAPACKET). Para realizar a conversão, utilize o XML Mapper, disponível no diretório bin do Delphi. O XML Mapper vai gerar um arquivo de transformação XML (XSLT) com o qual, em tempo de execução, um arquivo XML "padrão" pode ser convertido em um XML DataPacket\" href=\"#\" rel=\"bookmark\">XML</a>. </p>\r\n<p style=\"text-align: justify;\">Armado com estes dois botões você poderá agora, localmente, salvar as alterações feitas em seus dados e recarregá-los posteriormente. A fim de controlar o fato de quando ou não o TClientDataSet estará conectado \"ao vivo\" ao SDS, você pode incluir mais um TButton no formulá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ó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 você precisa fechar o TClientDataSet e fechar também o TDCOMConnection, enquanto que para estabelecer a conexão você apenas precisa abrir o TClientDataSet que irá, implicitamente, abrir o TDCOMConnection também.</p>\r\n<p style=\"text-align: justify;\">Finalmente, existe mais uma coisa que precisa ser feita: garantir que o TDCOMConnection e o TClientDataSet não estejam conectados ao SDS em tempo de projeto, do contrário, sempre que você reabrir o projeto do Cliente DataSnap no Delphi ele precisará realizar uma conexão com o SDS, que precisará ser carregado e se, por um motivo qualquer, o SDS não for encontrado em sua máquina o carregamento do projeto ficará congelado e, consequentemente, o Delphi ficará sem responder por um bom tempo. Para resolver esse problema, eu sempre garanto que estes componentes não estejam conectados em tempo de projeto. Para fazer isso você deve sempre atribuir false à propriedade Connected do componente TDCOMConnection (que vai fechar o formulário principal do SDS) e false à propriedade Active do componente TClientDataSet (que, como resultado, fará com que você nã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êntese para discutir o processo de timing dos clientes quando eles não conseguem conversar com o servidor. Se você tentar conectar-se ao servidor DCOM, mas não puder alcança-lo, o sistema não desistirá imediatamente da busca, ao invés disso, ele vai continuar tentando por um período de tempo que raramente excederá dois minutos. Durante estes dois minutos, entretanto, a aplicação ficará ocupada e parecerá travada. Se esta aplicação estiver carregada na IDE, então o Delphi como um todo ficará travado. Você 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ê recompilar e executar seu Cliente DataSnap, o formulário principal será exibido sem nenhum dado sendo exibido no TDBGrid. É hora então de clicar no botão \"Conectar\", de forma que a conexão com o SDS seja estabelecida e todos os registros sejam obtidos a partir de TableCustomer. Entretanto haverá momentos em que você não terá acesso ao SDS, seja porque você está \"na rua\" ou simplesmente porque a máquina com o SDS encontra-se inacessível. Nestes casos você poderá clicar o botão \"Carregar\" e trabalhar com uma cópia local dos registros. Tenha em mente que esta cópia local é aquela que você salvou pela última vez e será atualizada apenas quando você clicar o botão \"Salvar\" para escrever todo o conteúdo do TClientDataSet no disco.</p>\r\n<hr class=\"adsensesnippet\" />\r\n<h3>ApplyUpdates</h3>\r\n<p style=\"text-align: justify;\">É excelente ser capaz de conectar-se a um conjunto de dados remoto ou carregar um conjunto de dados local e depois salvá-lo no disco de novo, mas como se aplicam as <a title=\"Quero ressaltar novamente que a palavra "atualizar" aqui significa qualquer operação que altere o banco de dados. No caso, uma inserção, uma exclusão e uma alteração propriamente dita, todas estas operações alteram o banco de dados, inserindo, excluindo ou alterando registros\" href=\"#\" rel=\"bookmark\">atualizações</a> ao banco de dados de fato (remoto)? Isso pode ser feito usando-se o método ApplyUpdates do TClientDataSet. Inclua mais um TButton no formulário principal, configure seu nome como \"ButtonApplyUpdates\" e seu Caption como \"Aplicar Alterações\". Tal como o botão \"Salvar\", este botão deve apenas aplicar alterações caso haja alterações a serem aplicadas. O manipulador do evento OnClick deste botão segue:</p>\r\n<pre class=\"language-pascal\"><code>procedure TClientForm.ButtonApplyUpdatesClick(Sender: TObject);\r\nbegin\r\n if ClientDataSet1.ChangeCount > 0 then\r\n ClientDataSet1.ApplyUpdates(0);\r\nend;</code></pre>\r\n<p style=\"text-align: justify;\">O método ApplyUpdates tem apenas um argumento: o número máximo de erros que serão permitidos antes que o processo de aplicação de alterações seja interrompido (<strong>MaxErrors</strong>). Com apenas um Cliente DataSnap conectado ao SDS você nunca terá qualquer problema, então, fique à vontade para executar o Cliente DataSnap agora. Clique o botão \"Conectar\" para conectar (e carregar) o SDS e use os botões \"Salvar\" e \"Carregar\" para escrever /ler o conteúdo do TClientDataSet no/do disco. Você pode até remover sua máquina da rede e trabalhar apenas nos dados locais por uma quantidade significante de tempo, que é exatamente a ideia por trás do modelo de maleta (o seu laptop sendo a maleta!). Qualquer alteração feita na sua cópia local permanecerá visível e você poderá aplicar as alterações no banco de dados remoto com um clique no botão \"Aplicar Alterações\", claro, desde que você reconecte à rede e ao SDS novamente.</p>\r\n<h3>Manipulação de erros</h3>\r\n<p style=\"text-align: justify;\">Então o que aconteceria se dois clientes, ambos usando o MDM, conectassem ao SDS, obtivessem todo conteúdo de TableCustomer e ambos fizessem algumas alterações no primeiro registro? De acordo com o que foi codificado até o momento, ambos os clientes poderiam enviar suas alterações ao SDS usando o método ApplyUpdates. Se ambos passaram zero como argumento do ApplyUpdates, então o cliente que ficou em segundo lugar na \"corrida da atualização\" <strong>não terá</strong> suas modificações persistidas no banco de dados remoto, pois seu ApplyUpdates vai ser interrompido imediatamente. O segundo cliente poderia utilizar um valor maior que zero em MaxErrors a fim de indicar um número fixo de erros/conflitos que seriam permitidos antes de o ApplyUpdates ser interrompido, entretanto, mesmo se o segundo parâ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ê precisa executar algumas ações de conciliação para manipular atualizações em registros e campos que foram alterados por outros usuários.</p>\r\n<p style=\"text-align: justify;\">Felizmente o Delphi possui uma caixa de diálogo muito útil especialmente desenvolvida para este propósito. Sempre que você precisar realizar alguma <a title=\""Conciliar erros" ou "Reconciliar erros" vem do inglês "Reconcile Error" e pode ser interpretado como uma forma de resolver problemas (erros) nos dados quando o DataSnap não consegue determinar o que deve ser feito. A responsabilidade da decisão do que deve ser feito com dados conflitantes ou errados é sempre passada à aplicação ou ao usuário, o qual tem o poder de decidir o que deve ser feito\" href=\"#\" rel=\"bookmark\">conciliação de erros</a>, você deve considerar a adição desta caixa de diálogo ao seu Cliente DataSnap (ou escrever uma você mesmo, mas algo sempre precisa ser feito a respeito destes erros). Para usar a caixa de diálogo disponibilizada pelo Delphi, vá em File > New > Other, acesse a aba (ou item) <a title=\"Nos Delphis mais recentes, o ítem do Object Repository é "Delphi Files" e o nome do ícone a ser selecionado é "VCL Reconcile Error Dialog"\" href=\"#\" rel=\"bookmark\">\"Dialogs\"</a> do Object Repository e selecione o ícone \"Reconcile Error Dialog\". Uma vez que você tenha selecionado este ícone e clicado OK, uma nova unit será adicionada ao projeto do Cliente DataSnap. Esta unit contém a definição e a implementação da caixa de diálogo \"Update Error\", que pode ser usada para resolver erros decorrentes de conflitos nas operaçõ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ê precisa verificar. Primeiramente salve seu trabalho. Salve a nova unit em um arquivo de nome ErrorDialog.pas. Feito isso, a menos que você tenha desmarcado a opção para criar automaticamente TForms e TDataModules (na aba \"Designer\" da caixa de diálogo Tools > Environment Options), você precisará garantir que a classe da caixa de diálogo \"Update Error\" (TReconcileErrorForm) não seja uma das que são automaticamente criadas por sua aplicação. Veja a aba \"Forms\" da caixa de diálogo Project > Options. Nesta aba, você encontrará uma lista de formulários que são automaticamente criados e uma lista de formulários que estão disponíveis no projeto. Apenas verifique se TReconcileErrorForm não está na lista de formulários que são criados automaticamente e, se estiver, mova-a para a lista de formulários disponíveis. Uma instância de TReconcileErrorForm será criada dinamicamente quando for necessária.</p>\r\n<p style=\"text-align: justify;\">Então, quando ou como você pode usar esta caixa de diálogo especial? Bem, na verdade é muito simples. Para cada registro cuja operação  de banco de dados (inserção, exclusão, alteração) não seja bem sucedida, o evento OnReconcileError do TClientDataSet será 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 é um manipulador de evento com quatro argumentos. O primeiro deles é o TClientDataSet que levantou o erro. O segundo é a exceção de (re)conciliação propriamente dita, a qual contém a mensagem acerca da causa da condição de erro. O terceiro argumento é o <strong>UpdateKind</strong> (ukInsert, ukDelete, ukModify) e indica qual operação estava sendo tentada quando o erro aconteceu. Finalmente, o quarto argumento é a ação que deve ser tomada para resolver o conflito. Neste argumento você pode retornar os seguintes valores da enumeração <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ão altere nada no banco de dados mas deixe as alterações não aplicadas no log de modificações (localmente), para permitir uma tentativa posterior;</li>\r\n<li style=\"text-align: justify;\"><strong>raAbort</strong> - Abortar toda a manipulação de erros. Nenhum outro registro vai passar pelo 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çõ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 é a opção na qual a intervenção do usuário é requerida;</li>\r\n<li style=\"text-align: justify;\"><strong>raCancel</strong> - Desfaz todas as alterações no registro atual, transformando-o de volta no registro original (local) que você tinha;</li>\r\n<li style=\"text-align: justify;\"><strong>raRefresh</strong> - Desfaz todas as alteraçõ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 TReconcileErrorForm é que você não precisa se preocupar com nada disso. Você precisa apenas fazer duas coisas. Primeiro, você precisa incluir a unit ErrorDialog na cláusula uses do formulário principal. Com o formulário principal aberto no Delphi, pressione a combinação de teclas Alt+F11. A caixa de diá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 é escrever uma linha de código no manipulador do evento OnReconcileError e chamar a função <strong>HandleReconcileError</strong>, disponível na unit ErrorDialog, a qual você acabou de incluir na cláusula uses de seu formulário principal. A função HandleReconcileError tem os mesmos quatro argumentos do manipulador do evento OnReconcileError (não é coincidência, claro), então, tudo agora é questão de passar estes argumentos do manipulador para a funçã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ção</h3>\r\n<p style=\"text-align: justify;\">A maior questão agora é: como tudo isso funciona na prática? Para poder testar, você obviamente precisa de dois ou mais Clientes DataSnap rodando simultaneamente. Para um teste completo usando o Cliente DataSnap e o Servidor DataSnap, você 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ão \"Conectar\". Neste momento o SDS será carregado também e dados serão obtidos;</li>\r\n<li style=\"text-align: justify;\">Inicie o segundo Cliente DataSnap e clique o botão \"Conectar\". Dados serão obtidos do mesmo SDS que já está em execuçã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 \"Company\" do primeiro registro, tomando o cuidado de não alterar ele para o mesmo valor informado no primeiro Cliente DataSnap;</li>\r\n<li style=\"text-align: justify;\">Clique o botão \"Aplicar Alterações\" do primeiro Cliente DataSnap. A alteração será aplicada sem qualquer problema;</li>\r\n<li style=\"text-align: justify;\">Clique o botão \"Aplicar Alterações\" do segundo Cliente DataSnap. Desta vez, um ou mais erros vã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á executado e a caixa de diálogo \"Update Error\" será apresentada;</li>\r\n<li style=\"text-align: justify;\">Dentro da caixa de diálogo \"Update Error\" você poderá experimentar as várias ações de (re)conciliação (<strong>Skip</strong>, <strong>Abort</strong>, <strong>Merge</strong>, <strong>Correct</strong>, <strong>Cancel</strong> e <strong>Refresh</strong>) a fim de obter um bom entendimento do que cada uma delas faz. Preste atenção redobrada nas diferenças entre Skip e Cancel, e as diferenças entre Correct, Refresh e Merge.</li>\r\n</ul>\r\n<p style=\"text-align: justify;\">\"Skip\" vai ignorar o erro atual sem aplicar nada ao banco de dados remoto e seguir para o próximo registro da fila de atualizações no Data Packet (se houver). A alteração não aplicada no banco de dados remoto permanecerá no log de modificações e poderá ser enviada novamente ao se pressionar o botão \"Aplicar Alterações\". \"Cancel\" também vai ignorar o erro atual e mantê-lo no log de modificações, a diferença é que ele vai interromper o processo de aplicação de alterações, ou seja, se houver mais registros a serem atualizados, eles não o serã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ê pressiona o botão \"Aplicar Alterações\". O primeiro, o segundo e o terceiro registros, passaram. O quarto registro tem um problema e vai exibir a caixa de diálogo \"Update Error\". Se você escolher \"Skip\", a atualização do quarto registro será ignorada, ele permanecerá no log de modificações locais 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étimo. Suponha que esses passaram sem problemas, aí, ao tentar aplicar as atualizações do oitavo registro, houve um erro, então, novamente, a caixa de diálogo \"Update Error\" vai aparecer, mas desta vez a ação que você escolhe é \"Cancel\". Neste caso, o restante da operação do ApplyUpdates terminará e os registros oitavo, nono e décimo, não serão aplicados e permanecerão no log de alterações locais.</p>\r\n</blockquote>\r\n<p style=\"text-align: justify;\">\"Refresh\" apenas \"esquece\" todas as atualizações que você fez no registro e atualiza ele localmente com os valores que estão persistidos no servidor de banco de dados. \"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.