DELPHI Com API Do Windows
DELPHI Com API Do Windows
de shell no Windows
16
NESTE CAPÍTULO
l Um componente de ícone da bandeja de
notificação
l Barras de ferramentas do desktop da aplicação
l Vínculos do shell
l Extensões do shell
Lançada no Windows 95, o shell do Windows também tem suporte em todas as versões seguintes do
Windows (NT 3.51 e superior, 98, 2000, Me e XP). Parente distante do Program Manager (geren-
ciador de programas), o shell do Windows inclui alguns grandes recursos para estender o shell de
modo a atender suas necessidades. O problema é que muitos desses poderosos recursos extensíveis
são temas de desenvolvimento do Win32 mal documentados. Este capítulo tem como objetivo dar
informações e exemplos de que você precisa para ter acesso aos recursos do shell, como os ícones da
bandeja de notificação, barras de ferramentas do desktop da aplicação, vínculos do shell e extensões
do shell.
A API
Acredite se quiser, mas somente uma chamada da API está envolvida na criação, modificação e re-
moção dos ícones da bandeja de notificação. A função se chama Shell_NotifyIcon( ). Esta e outras
funções relacionadas ao shell do Windows estão contidas na unidade ShellAPI. Shell_Notify-
Icon( ) é definida da seguinte maneira:
O parâmetro dwMessage descreve a ação a ser executada pelo ícone, que pode ser qualquer um
dos valores mostrados na Tabela 16.1.
type
2 TNotifyIconData = record
cbSize: DWORD;
Wnd: HWND;
uID: UINT;
uFlags: UINT;
uCallbackMessage: UINT;
hIcon: HICON;
szTip: array [0..63] of AnsiChar;
end;
O campo cbSize armazena o tamanho do registro e deve ser inicializado como SizeOf(TNo-
tifyIconData).
Wnd é a alça da janela à qual as mensagens de “callback” da bandeja de notificação devem ser en-
viadas. (Callback está entre aspas, pois não se trata de uma callback no sentido estrito da palavra; no
entanto, a documentação do Win32 usa essa terminologia para mensagens enviadas para uma jane-
la, não para um ícone da bandeja de notificação.)
uID é um número de ID exclusivo definido pelo programador. Se você tiver uma aplicação com
diversos ícones, precisará identificar cada um deles colocando um número diferente nesse campo.
uFlags descreve qual dos campos do registro TNotifyIconData deve ser considerado ativo pela
função Shell_NotifyIcon( ) e, por essa razão, qual das propriedades do ícone será afetada pela ação
especificada pelo parâmetro dwMessage. Esse parâmetro pode ser qualquer combinação de flags
mostrada na Tabela 16.2.
uCallbackMessage contém o valor da mensagem do Windows a ser enviada para a janela identi-
ficada pelo campo Wnd. Geralmente, o valor desse campo é obtido pela chamada de RegisterWindow-
Message( ) ou pelo uso de um deslocamento de WM_USER. O lParam dessa mensagem terá o mesmo va-
lor que o campo uID, e wParam armazenará a mensagem de mouse gerada pelo ícone de notificação.
hIcon identifica a alça para o ícone que será colocado na bandeja de notificação.
szTip armazena uma string terminada em nulo, que aparecerá na janela de dica exibida quando
o ponteiro do mouse for mantido sobre o ícone de notificação.
O componente TTrayNotifyIcon encapsula Shell_NotifyIcon( ) em um método chamado
SendTrayMessage( ), que é mostrado a seguir:
initialization
{ Obtém um ID de mensagem exclusivo para a callback da bandeja }
DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);
hIcon assume o valor de retorno fornecido pelo método ActiveIconHandle( ). Esse método re-
torna a alça do ícone atualmente selecionado na propriedade Icon.
Manipulando mensagens
Já dissemos que todas as mensagens da bandeja de notificação são enviadas para uma janela mantida
pelo objeto IconMgr global. Este objeto é construído e liberado nas seções initialization e finali-
zation da unidade do componente, conforme mostrado a seguir:
initialization
{ Obtém o ID de mensagem exclusivo para a callback da bandeja }
DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);
IconMgr := TIconManager.Create;
finalization
IconMgr.Free;
A janela para a qual as mensagens da bandeja de notificação serão enviadas é criada no constru-
tor desse objeto usando a função AllocateHWnd( ):
constructor TIconManager.Create;
begin
FHWindow := AllocateHWnd(TrayWndProc);
4 end;
O método TrayWndProc( ) serve como procedimento de janela para a janela criada no constru-
tor. Voltaremos a falar sobre esse método mais adiante.
Ícones e dicas
O método mais objetivo para expor ícones e dicas para o usuário final do componente é através das
propriedades. Além disso, a criação de uma propriedade Icon do tipo TIcon significa que ela pode
automaticamente tirar proveito do editor de propriedades do Delphi para os ícones, o que é um re-
curso muito interessante. Como o ícone da bandeja é visível inclusive durante o projeto, você preci-
sa se certificar de que o ícone e a dica podem mudar dinamicamente. Fazer isso não implica, como se
pode pensar a princípio, muito trabalho extra; basta se certificar de que o método SendTrayMessa-
ge( ) é chamado (usando a mensagem NIM_MODIFY) no método write das propriedades Hint e Icon.
Veja, a seguir, os métodos write para essas propriedades:
Cliques do mouse
Uma das partes mais desafiadoras desse componente é garantir que os cliques do mouse sejam mani-
pulados de modo apropriado. Você pode ter percebido que muitos ícones da bandeja de notificação
executam três ações diferentes devido a cliques do mouse:
l Abre uma janela com um clique único
l Abre uma janela diferente (geralmente uma folha de propriedades) com um clique duplo
l Chama um menu local com um clique no botão direito
O desafio está na criação de um evento que represente o clique duplo sem acionar também o
evento de clique único.
Em termos de mensagem do Windows, quando o usuário dá um clique duplo no botão esquerdo
do mouse, a janela selecionada recebe tanto a mensagem WM_LBUTTONDOWN como a mensagem
WM_LBUTTONDBLCLK. Para permitir que uma mensagem de clique duplo seja processada independente-
mente de um clique único, é preciso um mecanismo para retardar a manipulação da mensagem de cli-
que único o tempo necessário para garantir que uma mensagem de clique duplo não esteja acessível.
O intervalo de tempo a esperar antes que você possa ter certeza de que uma mensagem
WM_LBUTTONDBLCLK não esteja vindo depois de uma mensagem M_LBUTTONDOWN é bastante fácil de deter-
minar. A função GetDoubleClickTime( ) da API, que não utiliza parâmetros, retorna o maior inter- 5
valo de tempo possível (em milissegundos) que o Control Panel (painel de controle) permitirá entre
os dois cliques de um clique duplo. A escolha óbvia para um mecanismo permitir que você aguarde
o número de milissegundos especificado por GetDoubleClickTime( ), garantindo que um clique du-
plo não venha depois de um clique, é o componente TTimer. Por essa razão, um componente TTimer
é criado e inicializado no construtor do componente TTrayNotifyIcon, com o seguinte código:
FTimer := TTimer.Create(Self);
with FTimer do
begin
Enabled := False;
Interval := GetDoubleClickTime;
OnTimer := OnButtonTimer;
end;
OnButtonTimer( ) é um método que será chamado quando o intervalo do timer expirar. Vamos
mostrar esse método daqui a pouco.
Já mencionamos que as mensagens da bandeja de notificação são filtradas através do método
TrayWndProc( ) de IconMgr. Agora chegou a hora de você conhecer esse método, e aqui está ele:
O que faz isso tudo funcionar é que a mensagem de clique único se limita a ativar o timer, en-
quanto a mensagem de clique duplo define um flag para indicar que o clique duplo ocorreu antes do
acionamento do seu evento OnDblClick. O clique com o botão direito, a propósito, chama o menu
instantâneo dado pela propriedade PopupMenu do componente. Agora dê uma olhada no método
OnButtonTimer( ):
Esse método primeiro desativa o timer, para garantir que o evento só será acionado uma vez a
cada clique no mouse. Em seguida, o método verifica o status do flag FNoShowClick. Lembre-se de
que esse flag será definido pela mensagem de clique duplo no método OwnerWndProc( ). Por essa ra-
zão, o evento OnClick só será acionado quando OnDblClk não o for.
Ocultando a aplicação
Outro aspecto das aplicações da bandeja de notificação é que elas não aparecem como botões na
barra de tarefas do sistema. Para fornecer essa funcionalidade, o componente TTrayNotifyIcon ex-
põe uma propriedade HideTask, que permite que o usuário decida se a aplicação deve ser visível na
barra de tarefas. O método write para essa propriedade é mostrado no código a seguir. A linha de
código que o trabalho executa é a chamada para o procedimento ShowWindow( ) da API, que passa a
propriedade Handle de Application e uma constante, para indicar se a aplicação tem que ser mostra-
da normalmente ou ocultada. Veja o código a seguir:
7
Listagem 16.1 TrayIcon.pas – código fonte do componente TTrayNotifyIcon
unit TrayIcon;
interface
type
ENotifyIconError = class(Exception);
TTrayNotifyIcon = class(TComponent)
private
FDefaultIcon: THandle;
FIcon: TIcon;
FHideTask: Boolean;
FHint: string;
FIconVisible: Boolean;
FPopupMenu: TPopupMenu;
FOnClick: TNotifyEvent;
FOnDblClick: TNotifyEvent;
FNoShowClick: Boolean;
FTimer: TTimer;
Tnd: TNotifyIconData;
procedure SetIcon(Value: TIcon);
procedure SetHideTask(Value: Boolean);
procedure SetHint(Value: string);
procedure SetIconVisible(Value: Boolean);
procedure SetPopupMenu(Value: TPopupMenu);
procedure SendTrayMessage(Msg: DWORD; Flags: UINT);
function ActiveIconHandle: THandle;
procedure OnButtonTimer(Sender: TObject);
protected
procedure Loaded; override;
procedure LoadDefaultIcon; virtual;
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Icon: TIcon read FIcon write SetIcon;
property HideTask: Boolean read FHideTask write SetHideTask default False;
property Hint: String read FHint write SetHint;
property IconVisible: Boolean read FIconVisible write SetIconVisible
default False;
property PopupMenu: TPopupMenu read FPopupMenu write SetPopupMenu;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick;
end;
implementation
{ TIconManager }
{ Esta classe cria uma janela oculta que manipula e direciona }
{ as mensagens de ícone da bandeja }
8 type
Listagem 16.1 Continuação
TIconManager = class
private
FHWindow: HWnd;
procedure TrayWndProc(var Message: TMessage);
public
constructor Create;
destructor Destroy; override;
property HWindow: HWnd read FHWindow write FHWindow;
end;
var
IconMgr: TIconManager;
DDGM_TRAYICON: Integer;
constructor TIconManager.Create;
begin
FHWindow := AllocateHWnd(TrayWndProc);
end;
destructor TIconManager.Destroy;
begin
if FHWindow < > 0 then DeallocateHWnd(FHWindow);
inherited Destroy;
end;
GetCursorPos(Pt);
TheIcon.FPopupMenu.Popup(Pt.X, Pt.Y);
{ Postagem da mensagem exigida pela API para forçar a troca de tarefa }
PostMessage(IconMgr.HWindow, WM_USER, 0, 0);
end;
end;
end;
end
else
{ Se não for uma mensagem de callback da bandeja, chama DefWindowProc }
Result := DefWindowProc(FHWindow, Msg, wParam, lParam);
end;
end;
{ TTrayNotifyIcon }
destructor TTrayNotifyIcon.Destroy;
begin
if FIconVisible then SetIconVisible(False); //ícone de destruição
FIcon.Free; // dados livres
FTimer.Free;
inherited Destroy;
end;
procedure TTrayNotifyIcon.LoadDefaultIcon;
{ Carrega o ícone de janela padrão para ficar por perto. }
{ Isso permitirá que o componente use o ícone do logotipo }
{ do Windows como o padrão quando nenhum ícone estiver }
{ selecionado na propriedade Icon. }
begin
FDefaultIcon := LoadIcon(0, IDI_WINLOGO);
10 end;
Listagem 16.1 Continuação
procedure TTrayNotifyIcon.Loaded;
{ Chamado depois que o componente é carregado do fluxo }
begin
inherited Loaded;
{ Se o ícone deve ser visível, crie-o. }
if FIconVisible then
SendTrayMessage(NIM_ADD, NIF_MESSAGE or NIF_ICON or NIF_TIP);
end;
FHideTask := Value;
{ Não faz nada no modo de projeto }
if not (csDesigning in ComponentState) then
ShowWindow(Application.Handle, ShowArray[FHideTask]);
end;
end;
const
{ String para identificar mensagem registrada do Windows }
TrayMsgStr = ‘DDG.TrayNotifyIconMsg’;
initialization
{ Obtém ID exclusivo da mensagem do Windows para a callback da bandeja }
DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);
IconMgr := TIconManager.Create;
finalization
IconMgr.Free;
end.
12
A Figura 16.2 mostra uma imagem do ícone gerada por TTrayNotifyIcon na bandeja de notifi-
cação.
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ShellAPI, TrayIcon, Menus, ComCtrls;
type
TMainForm = class(TForm)
pmiPopup: TPopupMenu;
pgclPageCtl: TPageControl;
TabSheet1: TTabSheet;
btnClose: TButton;
btnTerm: TButton;
Terminate1: TMenuItem;
Label1: TLabel;
N1: TMenuItem;
Propeties1: TMenuItem;
TrayNotifyIcon1: TTrayNotifyIcon;
procedure NotifyIcon1Click(Sender: TObject);
procedure NotifyIcon1DblClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure btnTermClick(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;
13
Listagem 16.2 Continuação
var
MainForm: TMainForm;
implementation
{$R *.DFM}
end.
Além de ser encaixada nas bordas da tela, AppBars podem empregar recursos do tipo barra de
tarefas, como a funcionalidade de auto-ocultar e arrastar-e-soltar. No entanto, você pode se surpre-
14
ender com o pequeno tamanho da API (ela tem apenas uma função). Como se pode deduzir pelo seu
pequeno tamanho, a API não tem muita coisa a oferecer. A função da API é mais consultiva do que
funcional. Ou seja, em vez de controlar a AppBar com comandos do tipo “faça isto, faça aquilo”,
você interroga a AppBar com comandos do tipo “posso fazer isso, posso fazer aquilo?”.
A API
Como os ícones da bandeja de notificação, AppBars só tem uma função da API com a qual você vai
trabalhar – SHAppBarMessage( ), nesse caso. Veja a seguir como SHAppBarMessage( ) é definida na
unidade ShellAPI:
O primeiro parâmetro dessa função, dwMessage, pode conter um dos valores descritos na Tabe-
la 16.3.
type
PAppBarData = ^TAppBarData;
TAppBarData = record
cbSize: DWORD;
hWnd: HWND;
uCallbackMessage: UINT;
uEdge: UINT;
rc: TRect;
lParam: LPARAM; { específico da mensagem }
end;
Nesse registro, o campo cbSize armazena o tamanho do registro, o campo hWnd armazena a
alça da janela da AppBar especificada, uCallbackMessage armazena o valor da mensagem que será 15
enviada para a janela AppBar juntamente com as mensagens de notificação, rc armazena o retângu-
lo que envolve a AppBar em questão e lParam armazena algumas informações adicionais específicas
da mensagem.
DICA
Para obter maiores informações sobre a função SHAppBarMessage( ) da API e sobre o tipo
TAppBarData, consulte a ajuda on-line do Win32.
Se a AppBar for criada com sucesso, o método SetAppBarEdge( ) será chamado para definir a
AppBar como sua posição inicial. Este método, por sua vez, chama o método SetAppBarPos( ), pas-
sando o flag apropriado, definido pela API, que indica a borda de tela solicitada. Como era de se
imaginar, os flags ABE_TOP, ABE_BOTTOM, ABE_LEFT e ABE_RIGHT representam cada uma das bordas da
tela. Isso é mostrado no trecho de código a seguir:
Esse método primeiro define o campo uEdge de FABD como o valor passado através do parâme-
tro Edge. Em seguida, ele define o campo rc como as coordenadas da tela inteira e envia a mensagem
ABM_QUERYPOS. Essa mensagem redefine o campo rc de modo que ele contenha o retângulo apropria-
do para a borda indicada por uEdge. Uma vez obtido o retângulo apropriado, rc é ajustado nova-
mente de modo que tenha altura e largura razoáveis. Nesse ponto, rc armazena o retângulo final
para a AppBar. A mensagem ABM_SETPOS é em seguida enviada para informar ao shell quanto ao
novo retângulo, e o retângulo é definido por meio da propriedade BoundsRect do controle.
Já dissemos que as mensagens de notificação da AppBar serão enviadas para a janela indicada
por FABD.hWnd, usando o identificador de mensagem armazenado em FABD.uCallbackMessage. Essas
mensagens de notificação são manipuladas no método WndProc( ) mostrado a seguir:
Este método manipula algumas mensagens de notificação que permitem que a AppBar respon-
da às mudanças que podem ocorrer no shell enquanto a aplicação estiver sendo executada. O res-
tante do código do componente AppBar é mostrado na Listagem 16.3.
Listagem 16.3 AppBars.pas – a unidade que contém a classe básica para dar suporte à AppBar
unit AppBars;
interface
type
TAppBarEdge = (abeTop, abeBottom, abeLeft, abeRight);
EAppBarError = class(Exception);
TAppBar = class(TCustomForm)
private
FABD: TAppBarData;
FDockedHeight: Integer;
FDockedWidth: Integer;
FEdge: TAppBarEdge;
FOnEdgeChanged: TNotifyEvent;
FTopMost: Boolean;
procedure WMActivate(var M: TMessage); message WM_ACTIVATE;
procedure WMWindowPosChanged(var M: TMessage); message WM_WINDOWPOSCHANGED;
function SendAppBarMsg(Msg: DWORD): UINT;
procedure SetAppBarEdge(Value: TAppBarEdge);
procedure SetAppBarPos(Edge: UINT);
procedure SetTopMost(Value: Boolean);
procedure SetDockedHeight(const Value: Integer);
procedure SetDockedWidth(const Value: Integer);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure CreateWnd; override;
procedure DestroyWnd; override;
procedure WndProc(var M: TMessage); override;
public
constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
property DockManager;
published
property Action;
property ActiveControl;
property AutoScroll;
property AutoSize;
property BiDiMode;
property BorderWidth;
property Color;
18 property Ctl3D;
Listagem 16.3 Continuação
property OnShortCut;
property OnShow;
property OnStartDock;
property OnUnDock;
end;
implementation
var
AppBarMsg: UINT;
procedure TAppBar.CreateWnd;
begin
inherited CreateWnd;
FABD.hWnd := Handle;
if not (csDesigning in ComponentState) then
begin
if SendAppBarMsg(ABM_NEW) = 0 then
raise EAppBarError.Create(‘Failed to create AppBar’);
// Inicializa a posição
SetAppBarEdge(FEdge);
end;
end;
procedure TAppBar.DestroyWnd;
begin
// Deve informar ao shell que a AppBar está sendo fechada
SendAppBarMsg(ABM_REMOVE);
inherited DestroyWnd;
end;
initialization
AppBarMsg := RegisterWindowMessage(‘DDG AppBar Message’);
end.
Usando TAppBar
Se você instalou o software encontrado no CD-ROM que acompanha este livro, o uso de TAppBar
será muito fácil: basta selecionar a opção AppBar da página DDG da caixa de diálogo File, New.
Isso chama um assistente que gerará uma unidade contendo um componente TAppBar.
NOTA
O Capítulo 17 demonstra como criar um assistente que gera automaticamente uma TAppBar. Neste
capítulo, no entanto, você pode ignorar a implementação do assistente. Basta entender que algum
trabalho está sendo feito nos bastidores para gerar a unidade e o formulário da AppBar para você.
Nesta pequena aplicação de exemplo, TAppBar é usada para criar uma barra de ferramentas de
aplicação que contenha botões para diversos comandos de edição: Open (abrir), Save (salvar), Cut
(recortar), Copy (copiar) e Paste (colar). Os botões manipularão um componente TMemo encontrado
no formulário principal. O código-fonte dessa unidade aparece na Listagem 16.4, e a Figure 16.5
mostra a aplicação em ação com o controle da AppBar encaixado na parte inferior da tela.
unit ApBarFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
AppBars, Menus, Buttons;
type
TAppBarForm = class(TAppBar)
sbOpen: TSpeedButton; 23
Listagem 16.4 Continuação
sbSave: TSpeedButton;
sbCut: TSpeedButton;
sbCopy: TSpeedButton;
sbPaste: TSpeedButton;
OpenDialog: TOpenDialog;
pmPopup: TPopupMenu;
Top1: TMenuItem;
Bottom1: TMenuItem;
Left1: TMenuItem;
Right1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
procedure Right1Click(Sender: TObject);
procedure sbOpenClick(Sender: TObject);
procedure sbSaveClick(Sender: TObject);
procedure sbCutClick(Sender: TObject);
procedure sbCopyClick(Sender: TObject);
procedure sbPasteClick(Sender: TObject);
procedure Exit1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormEdgeChanged(Sender: TObject);
private
FLastChecked: TMenuItem;
procedure MoveButtons;
end;
var
AppBarForm: TAppBarForm;
implementation
uses Main;
{$R *.DFM}
{ TAppBarForm }
procedure TAppBarForm.MoveButtons;
// Este método parece complicado, mas ele apenas organiza os botões de
// modo apropriado, dependendo do lado em que a AppBar foi encaixada.
var
DeltaCenter, NewPos: Integer;
begin
if Edge in [abeTop, abeBottom] then
begin
DeltaCenter := (ClientHeight – sbOpen.Height) div 2;
sbOpen.SetBounds(10, DeltaCenter, sbOpen.Width, sbOpen.Height);
NewPos := sbOpen.Width + 20;
sbSave.SetBounds(NewPos, DeltaCenter, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Width + 10;
sbCut.SetBounds(NewPos, DeltaCenter, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Width + 10;
sbCopy.SetBounds(NewPos, DeltaCenter, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Width + 10;
sbPaste.SetBounds(NewPos, DeltaCenter, sbOpen.Width, sbOpen.Height);
end
24 else
Listagem 16.4 Continuação
begin
DeltaCenter := (ClientWidth – sbOpen.Width) div 2;
sbOpen.SetBounds(DeltaCenter, 10, sbOpen.Width, sbOpen.Height);
NewPos := sbOpen.Height + 20;
sbSave.SetBounds(DeltaCenter, NewPos, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Height + 10;
sbCut.SetBounds(DeltaCenter, NewPos, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Height + 10;
sbCopy.SetBounds(DeltaCenter, NewPos, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Height + 10;
sbPaste.SetBounds(DeltaCenter, NewPos, sbOpen.Width, sbOpen.Height);
end;
end;
end.
Vínculos do shell
O shell do Windows expõe uma série de interfaces que podem ser utilizadas para manipular dife-
rentes aspectos do shell. Essas interfaces são definidas na unidade ShlObj. Seria preciso um novo li-
vro para discutir em profundidade todos os objetos dessa unidade e, portanto, vamos concentrar
nossos esforços em uma das interfaces mais úteis (e mais usadas): IShellLink.
IShellLink é uma interface que permite a criação e manipulação dos vínculos do shell nas suas
aplicações. Caso esteja em dúvida, a maioria dos ícones do seu desktop provavelmente é composta
de vínculos do shell. Além disso, cada item no menu Send To (enviar para) local do shell ou no menu
Documents (documentos), ao qual você tem acesso pelo menu Start (iniciar) é um vínculo do shell.
A interface IShellLink é definida da seguinte maneira:
const
type
IShellLink = interface(IUnknown)
[‘{000214EE-0000-0000-C000-000000000046}’]
function GetPath(pszFile: PAnsiChar; cchMaxPath: Integer;
var pfd: TWin32FindData; fFlags: DWORD): HResult; stdcall;
26 function GetIDList(var ppidl: PItemIDList): HResult; stdcall;
function SetIDList(pidl: PItemIDList): HResult; stdcall;
function GetDescription(pszName: PAnsiChar; cchMaxName: Integer): HResult;
stdcall;
function SetDescription(pszName: PAnsiChar): HResult; stdcall;
function GetWorkingDirectory(pszDir: PAnsiChar; cchMaxPath: Integer):
HResult;
stdcall;
function SetWorkingDirectory(pszDir: PAnsiChar): HResult; stdcall;
function GetArguments(pszArgs: PAnsiChar; cchMaxPath: Integer): HResult;
stdcall;
function SetArguments(pszArgs: PAnsiChar): HResult; stdcall;
function GetHotkey(var pwHotkey: Word): HResult; stdcall;
function SetHotkey(wHotkey: Word): HResult; stdcall;
function GetShowCmd(out piShowCmd: Integer): HResult; stdcall;
function SetShowCmd(iShowCmd: Integer): HResult; stdcall;
function GetIconLocation(pszIconPath: PAnsiChar; cchIconPath: Integer;
out piIcon: Integer): HResult; stdcall;
function SetIconLocation(pszIconPath: PAnsiChar; iIcon: Integer): HResult;
stdcall;
function SetRelativePath(pszPathRel: PAnsiChar; dwReserved: DWORD):
HResult;
stdcall;
function Resolve(Wnd: HWND; fFlags: DWORD): HResult; stdcall;
function SetPath(pszFile: PAnsiChar): HResult; stdcall;
end;
NOTA
Como IshellLink e seus métodos são descritos com detalhes na ajuda on-line do Win32, não va-
mos discuti-los aqui.
var
SL: IShellLink;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IShellLink, SL));
// use SL aqui
end;
NOTA
Não se esqueça de que, antes de poder usar qualquer uma das funções OLE, você deve inicializar a
biblioteca COM usando a função CoInitialize( ). Quando você tiver acabado de usar a COM,
deverá excluí-la chamando CoUninitialize( ). Essas funções serão chamadas pelo Delphi em
uma aplicação que use ComObj e contenha uma chamada para Application.Initialize( ).
Caso contrário, você mesmo terá que chamar essas funções.
27
Usando IShellLink
Aparentemente, os vínculos do shell podem ser considerados algo mágico: você dá um clique com o
botão direito do mouse no desktop, cria um novo atalho e alguma coisa acontece, que faz com que
um ícone apareça no desktop. Na verdade, essa alguma coisa não tem mistério algum quando você
sabe o que está ocorrendo. Um vínculo de shell não passa de um arquivo com uma extensão LNK,
que reside em algum diretório. Quando o Windows é inicializado, ele procura arquivos LNK em
determinados diretórios, que representam vínculos que residem em diferentes pastas do shell. Essas
pastas do shell, ou pastas especiais, incluem itens como Network Neighborhood (ambiente de
rede), Send To (enviar para), Startup (iniciar) e Desktop (área de trabalho), entre outras coisas. O
shell armazena as correspondências vínculo/pasta no Registro do sistema – encontradas, em sua
maioria, abaixo da seguinte chave, caso você queira examinar:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer
å\Shell Folders
A criação de um vínculo do shell em uma pasta especial, portanto, é apenas uma questão de co-
locar um arquivo de vínculo em um determinado diretório. Em vez de mexer com o Registro do sis-
tema, você pode usar SHGetSpecialFolderPath( ) para obter o caminho de diretório para as diversas
pastas especiais. Esse método é definido da seguinte maneira:
hwndOwner contém a alça de uma janela que servirá como o proprietária para qualquer caixa de
diálogo que a função possa chamar.
lpszPath é um ponteiro para um buffer que receberá o caminho. Esse buffer deve ter, pelo me-
nos, o número de caracteres registrado em MAX_PATH.
nFolder identifica a pasta especial cujo caminho você deseja obter. A Tabela 16.4 mostra os
possíveis valores para esse parâmetro e uma descrição de cada um deles.
fCreate indica se uma pasta deve ser criada, caso não exista.
Flag Descrição
Flag Descrição
29
Criando um vínculo do shell
A interface IShellLink é um encapsulamento de um objeto de vínculo com o shell, mas não possui
um conceito quanto ao modo como é lida ou escrita em um arquivo no disco. No entanto, os imple-
mentadores da interface IShellLink também têm a obrigação de oferecer suporte à interface IPer-
sistFile para fornecer o acesso ao arquivo. IPersistFile é uma interface que fornece métodos de
leitura e gravação de/para disco e é definida da seguinte maneira:
type
IPersistFile = interface(IPersist)
[‘{0000010B-0000-0000-C000-000000000046}’]
function IsDirty: HResult; stdcall;
function Load(pszFileName: POleStr; dwMode: Longint): HResult;
stdcall;
function Save(pszFileName: POleStr; fRemember: BOOL): HResult;
stdcall;
function SaveCompleted(pszFileName: POleStr): HResult;
stdcall;
function GetCurFile(out pszFileName: POleStr): HResult;
stdcall;
end;
NOTA
Para obter uma descrição completa de IPersistFile e seus métodos, consulte a ajuda on-line do
Win32.
Como a classe que implementa IShellLink também é obrigatória para implementar IPeristFi-
le, você pode usar a QueryInterface para consultar a instância de IShellLink de uma instância de
IPersistFile usando o operador as, como mostramos a seguir:
var
SL: IShellLink;
PF: IPersistFile;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IShellLink, SL));
PF := SL as IPersistFile;
// use PF e SL
end;
Como já dissemos, o uso de objetos da interface COM corresponde a usar os objetos normais
da Object Pascal. O código mostrado a seguir, por exemplo, cria um vínculo com o shell do desktop
com a aplicação Notepad (bloco de notas):
procedure MakeNotepad;
const
// NOTA: Posição presumida do Notepad:
AppName = ‘c:\windows\notepad.exe’;
var
SL: IShellLink;
PF: IPersistFile;
LnkName: WideString;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
30 IShellLink, SL));
{ Os implementadores de IShellLink têm que implementar IPersistFile }
PF := SL as IPersistFile;
OleCheck(SL.SetPath(PChar(AppName))); // define caminho do vínculo para o arquivo apropriado
{ cria um caminho e um nome de arquivo para o arquivo de vínculo }
LnkName := GetFolderLocation(‘Desktop’) + ‘\’ +
ChangeFileExt(ExtractFileName(AppName), ‘.lnk’);
PF.Save(PWideChar(LnkName), True); // salva arquivo de vínculo
end;
Nesse procedimento, o método SetPath( ) de IShellLink é usado para apontar o vínculo para
um documento ou arquivo executável (nesse caso, o Notepad). Posteriormente, um caminho e
nome de arquivo para o vínculo é criado usando o caminho retornado por GetFolderLocati-
on(‘Desktop’) (já descrita nesta seção) e pelo uso da função ChangeFileExt( ) para alterar a exten-
são do Notepad de .EXE para .LNK. Esse novo nome de arquivo é armazenado em LnkName. Depois
disso, o método Save( ) salva o vínculo em um arquivo no disco. Como você já aprendeu, quando o
procedimento terminar e as instâncias de interface SL e PF saírem do escopo, suas respectivas refe-
rências serão liberadas.
type
TShellLinkInfo = record
PathName: string;
Arguments: string;
Description: string;
WorkingDirectory: string;
IconLocation: string;
IconIndex: Integer;
ShowCmd: Integer;
HotKey: Word;
end;
Dado esse registro, você pode criar funções que recuperam as definições de um determinado
vínculo do shell com o registro ou que definem os valores do vínculo como aqueles indicados pelo
conteúdo do registro. Essas funções aparecem na Listagem 16.5; WinShell.pas é uma unidade que
contém o código-fonte completo dessas funções.
Listagem 16.5 WinShell.pas – a unidade que contém as funções que operam nos vínculos do
shell
unit WinShell;
interface
type
EShellOleError = class(Exception);
TShellLinkInfo = record
PathName: string; 31
Listagem 16.5 Continuação
Arguments: string;
Description: string;
WorkingDirectory: string;
IconLocation: string;
IconIndex: integer;
ShowCmd: integer;
HotKey: word;
end;
TSpecialFolderInfo = record
Name: string;
ID: Integer;
end;
const
SpecialFolders: array[0..29] of TSpecialFolderInfo = (
(Name: ‘Alt Startup’; ID: CSIDL_ALTSTARTUP),
(Name: ‘Application Data’; ID: CSIDL_APPDATA),
(Name: ‘Recycle Bin’; ID: CSIDL_BITBUCKET),
(Name: ‘Common Alt Startup’; ID: CSIDL_COMMON_ALTSTARTUP),
(Name: ‘Common Desktop’; ID: CSIDL_COMMON_DESKTOPDIRECTORY),
(Name: ‘Common Favorites’; ID: CSIDL_COMMON_FAVORITES),
(Name: ‘Common Programs’; ID: CSIDL_COMMON_PROGRAMS),
(Name: ‘Common Start Menu’; ID: CSIDL_COMMON_STARTMENU),
(Name: ‘Common Startup’; ID: CSIDL_COMMON_STARTUP),
(Name: ‘Controls’; ID: CSIDL_CONTROLS),
(Name: ‘Cookies’; ID: CSIDL_COOKIES),
(Name: ‘Desktop’; ID: CSIDL_DESKTOP),
(Name: ‘Desktop Directory’; ID: CSIDL_DESKTOPDIRECTORY),
(Name: ‘Drives’; ID: CSIDL_DRIVES),
(Name: ‘Favorites’; ID: CSIDL_FAVORITES),
(Name: ‘Fonts’; ID: CSIDL_FONTS),
(Name: ‘History’; ID: CSIDL_HISTORY),
(Name: ‘Internet’; ID: CSIDL_INTERNET),
(Name: ‘Internet Cache’; ID: CSIDL_INTERNET_CACHE),
(Name: ‘Network Neighborhood’; ID: CSIDL_NETHOOD),
(Name: ‘Network Top’; ID: CSIDL_NETWORK),
(Name: ‘Personal’; ID: CSIDL_PERSONAL),
(Name: ‘Printers’; ID: CSIDL_PRINTERS),
(Name: ‘Printer Links’; ID: CSIDL_PRINTHOOD),
(Name: ‘Programs’; ID: CSIDL_PROGRAMS),
(Name: ‘Recent Documents’; ID: CSIDL_RECENT),
(Name: ‘Send To’; ID: CSIDL_SENDTO),
(Name: ‘Start Menu’; ID: CSIDL_STARTMENU),
(Name: ‘Startup’; ID: CSIDL_STARTUP),
(Name: ‘Templates’; ID: CSIDL_TEMPLATES));
implementation
32 uses ComObj;
Listagem 16.5 Continuação
end.
Um método de IShellLink que ainda tem que ser explicado é o método Resolve( ). Resolve( )
deve ser chamado depois que a interface IPersistFile de IShellLink for usada para carregar um ar-
quivo de vínculo. Isso pesquisa o arquivo de vínculo especificado e preenche o objeto IShellLink
com os valores especificados no arquivo.
34
DICA
Na função GetShellLinkInfo( ), mostrada na Listagem 16.5, observe o uso do array local AStr,
no qual os valores são recuperados. Essa técnica é usada no lugar de SetLength( ) para alocar es-
paço para as strings – o uso de SetLength( ) em tantas strings acarretaria na fragmentação do heap
da aplicação. O uso de Astr como um intermediário impede que isso ocorra. Além disso, como o
tamanho das strings só precisa ser definido uma vez, o uso de AStr acaba sendo ligeiramente mais
rápido.
Figura 16.6 O formulário principal de Shell Link, mostrando um dos vínculos com o desktop.
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, ExtCtrls, Spin, WinShell, Menus;
type
TMainForm = class(TForm)
Panel1: TPanel;
btnOpen: TButton;
edLink: TEdit;
btnNew: TButton;
btnSave: TButton;
Label3: TLabel;
Panel2: TPanel;
Label1: TLabel;
Label2: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
Label7: TLabel;
Label8: TLabel; 35
Listagem 16.6 Continuação
Label9: TLabel;
edIcon: TEdit;
edDesc: TEdit;
edWorkDir: TEdit;
edArg: TEdit;
cbShowCmd: TComboBox;
hkHotKey: THotKey;
speIcnIdx: TSpinEdit;
pnlIconPanel: TPanel;
imgIconImage: TImage;
btnExit: TButton;
MainMenu1: TMainMenu;
File1: TMenuItem;
Open1: TMenuItem;
Save1: TMenuItem;
NewLInk1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
Help1: TMenuItem;
About1: TMenuItem;
edPath: TEdit;
procedure btnOpenClick(Sender: TObject);
procedure btnNewClick(Sender: TObject);
procedure edIconChange(Sender: TObject);
procedure btnSaveClick(Sender: TObject);
procedure btnExitClick(Sender: TObject);
procedure About1Click(Sender: TObject);
private
procedure GetControls(var SLI: TShellLinkInfo);
procedure SetControls(const SLI: TShellLinkInfo);
procedure ShowIcon;
procedure OpenLinkFile(const LinkFileName: String);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
type
THotKeyRec = record
Char, ModCode: Byte;
end;
procedure TMainForm.ShowIcon;
{ Apanha ícone do arquivo apropriado e mostra em IconImage }
var
HI: THandle;
IcnFile: string;
IconIndex: word;
begin
{ Apanha nome do arquivo de ícone }
IcnFile := edIcon.Text;
{ Se estiver em branco, usa o nome exe }
if IcnFile = '' then
IcnFile := edPath.Text;
{ Certifica-se de que o arquivo existe }
if FileExists(IcnFile) then
begin
IconIndex := speIcnIdx.Value;
{ Extrai ícone do arquivo }
HI := ExtractAssociatedIcon(hInstance, PChar(IcnFile), IconIndex);
{ Atribui alça do ícone a IconImage }
imgIconImage.Picture.Icon.Handle := HI;
end;
end;
end.
Listagem 16.7 NewLinkU.pas – a unidade com formulário que ajuda a criar novo vínculo
unit NewLinkU;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Buttons, StdCtrls;
type
TNewLinkForm = class(TForm)
Label1: TLabel;
Label2: TLabel;
edLinkTo: TEdit;
btnOk: TButton;
btnCancel: TButton;
cbLocation: TComboBox;
sbOpen: TSpeedButton;
OpenDialog: TOpenDialog;
procedure sbOpenClick(Sender: TObject);
procedure FormCreate(Sender: TObject); 39
Listagem 16.7 Continuação
end;
implementation
uses WinShell;
{$R *.DFM}
end.
Listagem 16.8 PickU.pas – a unidade com formulário que permite que o usuário escolha o local
do vínculo
unit PickU;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, FileCtrl;
type
40
Listagem 16.8 Continuação
TLinkForm = class(TForm)
lbLinkFiles: TFileListBox;
btnOk: TButton;
btnCancel: TButton;
cbLocation: TComboBox;
Label1: TLabel;
procedure lbLinkFilesDblClick(Sender: TObject);
procedure cbLocationChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;
implementation
{$R *.DFM}
end.
Extensões do shell
Última palavra em termos de extensibilidade, o shell do Windows fornece um meio para você de-
senvolver um código capaz de ser executado dentro do próprio processo e namespace do shell. As
extensões do shell são implementadas como servidores COM in-process, que são criados e usados
pelo shell.
NOTA
Como as extensões do shell no fundo não passam de servidores COM, você só conseguirá entender
o que são se conhecer pelo menos um pouco do COM. Se você não tem a menor idéia do que seja
COM, o Capítulo 15 explica os fundamentos.
Diversos tipos de extensões do shell estão disponíveis para lidar com uma série de aspectos do
shell. Também conhecida como tratador (ou handler), uma extensão do shell deve implementar
uma ou mais interfaces do COM. O shell tem suporte para os seguintes tipos de extensões do shell:
l Tratadores copy hook implementam a interface ICopyHook. Essas extensões do shell permi-
tem que você receba notificações sempre que uma pasta é copiada, excluída, movida ou re-
nomeada e para opcionalmente impedir que a operação ocorra.
l Tratadores de menu de contexto implementam as interfaces IContextMenu e IShellExtInit.
Essas extensões do shell permitem que você adicione itens ao menu de contexto de um de-
terminado objeto de arquivo no shell.
l Tratadores de arrastar e soltar também implementam as interface IContextMenu e IShell-
ExtInit. A implementação dessas extensões do shell é quase idêntica à dos tratadores de
menu de contexto, exceto pelo fato de serem chamadas quando um usuário arrasta um ob-
jeto e o solta em um novo local.
l Tratadores de ícones implementam as interfaces IExtractIcon e IPersistFile. Os tratadores
de ícones permitem que você forneça diferentes ícones para instâncias múltiplas do mesmo
tipo de objeto de arquivo.
l Tratadores de folha de propriedades implementam as interfaces IShellPropSheetExt e
IShellExtInit e permitem que você adicione páginas à caixa de diálogo de propriedades as-
sociada a um tipo de arquivo.
l Tratadores de soltar no destino implementam as interfaces IDropTarget e IPersistFile.
Essas extensões do shell permitem que você controle o que acontece quando arrasta um ob-
jeto de shell sobre outro.
l Tratadores de objeto de dados implementam as interfaces IDataObject e IPersistFile e for-
necem o objeto de dados quando arquivos estão sendo arrastados e soltos ou copiados e co-
lados.
42
Depurando as extensões do shell
Antes de começarmos a discutir sobre a escrita de extensões do shell, considere a questão da depu-
ração das extensões do shell. Como as extensões do shell são executadas de dentro do próprio pro-
cesso do shell, como é possível criar um “gancho” para o shell para depurar as extensões do shell?
A solução para o problema é baseada no fato de que o shell é um executável (não muito dife-
rente do que qualquer outra aplicação), chamado explorer.exe. No entanto, explorer.exe pos-
sui uma propriedade exclusiva: a primeira instância de explorer.exe chamará o shell. As instâncias
subseqüentes simplesmente chamarão as janelas “Explorer” no shell.
Usando um macete pouco conhecido no shell, é possível fechar o shell sem fechar o Windows.
Siga estas etapas para depurar suas extensões do shell no Delphi:
1. Torne explorer.exe a aplicação host para a sua extensão do shell na caixa de diálogo que
aparece após a seleção de Run, Parameters. Certifique-se de incluir o caminho completo (ou
seja, c:\windows\explorer.exe).
2. No menu Start (iniciar) do shell, selecione Shut Down (desligar). Isso chamará a caixa de diálogo
Shut Down Windows (desligar Windows).
3. Na caixa de diálogo Shut Down Windows, mantenha pressionadas as teclas Ctrl+Alt+Shift e dê
um clique no botão No (não). Isso fechará o shell sem fechar o Windows.
4. Usando Alt+Tab, volte para o Delphi e execute a extensão do shell. Isso chamará uma nova có-
pia do shell, sendo executada no depurador do Delphi. Agora você pode definir pontos de inter-
rupção no seu código e depurar como sempre.
5. Quando você estiver pronto para fechar o Windows, poderá fazê-lo de modo apropriado sem o
uso do shell: use Ctrl+Esc para chamar a janela Tasks (tarefas) e em seguida selecione Win-
dows, Shutdown Windows; o Windows será desligado.
O restante deste capítulo é dedicado a mostrar uma descrição mais detalhada das extensões do
shell que acabamos de descrever. Você vai aprender sobre tratadores de copy hook, tratadores de
menu de contexto e tratadores de ícones.
type
ICopyHook = interface(IUnknown)
[‘{000214EF-0000-0000-C000-000000000046}’]
function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT;
pszSrcFile: PAnsiChar; dwSrcAttribs: DWORD; pszDestFile: PAnsiChar;
dwDestAttribs: DWORD): UINT; stdcall;
end;
O método CopyCallback( )
Como você pode ver, ICopyHook é uma interface bastante simples e implementa apenas uma função:
CopyCallback( ). Essa função será chamada sempre que uma pasta do shell for manipulada. Os pró-
ximos parágrafos descrevem os parâmetros dessa função.
Wnd é a alça da janela que o tratador de copy hook deve usar como o pai de qualquer janela que
ele apresente. wFunc indica a operação que está sendo executada. Isso pode ser qualquer um dos va-
lores mostrados na Tabela 16.5.
wFlags mantém os flags que controlam a operação. Esse parâmetro pode ser uma combinação
dos valores mostrados na Tabela 16.6.
FOF_NOCONFIRMATION $10 Responde com “Yes to All” (sim para todos) para qualquer caixa
de diálogo exibida.
FOF_NOCONFIRMMKDIR $200 Não confirma a criação dos diretórios necessários no caso de a
operação exigir que um novo diretório seja criado.
FOF_RENAMEONCOLLISION $8 Atribui um nome novo ao arquivo que está sendo processado
(como “Cópia 1 de...”) em uma operação de cópia, movimentação
ou renomeação quando já houver um arquivo com o nome do
destino.
FOF_SILENT $4 Não exibe uma caixa de diálogo de progresso.
FOF_SIMPLEPROGRESS $100 Exibe uma caixa de diálogo de progresso, mas a caixa de diálogo
não mostra os nomes dos arquivos.
Implementação de TCopyHook
Sendo um objeto que implementa uma interface com um método, não há muita coisa em TCopyHook:
type
TCopyHook = class(TComObject, ICopyHook)
protected
function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT;
pszSrcFile: PAnsiChar;
dwSrcAttribs: DWORD; pszDestFile: PAnsiChar; dwDestAttribs: DWORD): UINT;
stdcall;
end;
DICA
Você pode estar se perguntando por que a função MessageBox( ) da API é usada para exibir uma
mensagem em vez de se usar uma função do Delphi, como MessageDlg( ) ou ShowMessage( ). A
razão é simples: tamanho e eficiência. A chamada de qualquer função fora da unidade Dialogs ou
Forms faria com que uma grande parte da VCL fosse vinculada à DLL. Mantendo essas unidades
fora da cláusula uses, a DLL da extensão do shell ocupa irrisórios 70KB.
Acredite se quiser, mas isso é tudo que há para se falar sobre o objeto TCopyHook propriamente
dito. No entanto, ainda há um importante trabalho a ser feito antes de algum dia ele poder ser cha-
mado: a extensão do shell deve ser registrada com o System Registry antes de ele poder funcionar.
Registro
Além do registro normal exigido de qualquer servidor COM, um tratador de copy hook deve ter uma
entrada adicional no Registro, sob
HKEY_CLASSES_ROOT\directory\shellex\CopyHookHandlers
Além disso, o Windows NT exige que todas as extensões do shell sejam registradas, conforme
as extensões do shell aprovadas, sob
HKEY_LOCAL_MACHINE\ SOFTWARE\Microsoft\Windows\CurrentVersion
å\Shell Extensions\Approved
Você pode utilizar várias técnicas para registrar as extensões do shell: elas podem ser registra-
das através de um arquivo REG ou através de um programa de instalação. A DLL da extensão do
shell propriamente dita pode ser auto-registrável. Embora isso signifique um pouco mais de traba-
lho, a melhor solução é tornar cada DLL de extensão do shell auto-registrável. Isso é mais legível,
pois cria sua extensão do shell em um pacote de um arquivo independente.
Conforme você aprendeu no Capítulo 15, os objetos COM são sempre criados a partir de fá-
bricas de classes. Dentro da estrutura da VCL, os objetos de fábrica de classes também são responsá-
veis pelo registro do objeto COM que criarão. Se um objeto COM requer entradas de Registro per-
sonalizadas (como é o caso com uma extensão do shell), a definição dessas entradas é só uma
questão de modificar o método UpdateRegistry( ) da fábrica de classes. A Listagem 16.9 mostra a
unidade CopyMain completa, que inclui uma fábrica de classes especializada para executar um regis-
tro personalizado.
unit CopyMain;
interface
type
TCopyHook = class(TComObject, ICopyHook)
protected
function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PAnsiChar; dwSrcAttribs:
DWORD; pszDestFile: PAnsiChar; dwDestAttribs: DWORD): UINT; stdcall;
end;
TCopyHookFactory = class(TComObjectFactory)
protected
function GetProgID: string; override;
procedure ApproveShellExtension(Register: Boolean; const ClsID: string);
virtual;
public
procedure UpdateRegistry(Register: Boolean); override;
end;
implementation
{ TCopyHook }
{ TCopyHookFactory }
SApproveKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Shell
åExtensions\Approved';
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if not OpenKey(SApproveKey, True) then Exit;
if Register then WriteString(ClsID, Description)
else DeleteValue(ClsID);
finally
Free;
end;
end;
const
CLSID_CopyHook: TGUID = '{66CD5F60-A044-11D0-A9BF-00A024E3867F}';
initialization
TCopyHookFactory.Create(ComServer, TCopyHook, CLSID_CopyHook,
'DDG_CopyHook', 'DDG Copy Hook Shell Extension Example',
ciMultiInstance, tmApartment);
end.
O que faz a fábrica de classes TCopyHookFactory funcionar é o fato de uma instância dela, não o
TComObjectFactory normal, estar sendo criada na parte initialization da unidade. A Figura 16.7
mostra o que acontece quando você tenta trocar o nome de uma pasta no shell depois que a DLL da
extensão do shell copy hook é instalada.
IShellExtInit
A interface IShellExtInit é usada para inicializar uma extensão do shell. Essa interface é definida na
unidade ShlObj da seguinte maneira:
type
IShellExtInit = interface(IUnknown)
[‘{000214E8-0000-0000-C000-000000000046}’]
function Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
hKeyProgID: HKEY): HResult; stdcall;
end;
Initialize( ), sendo o único método dessa interface, é chamado para inicializar o tratador do
menu de contexto. Os próximos parágrafos descrevem os parâmetros desse método.
pidlFolder é um ponteiro para uma estrutura PItemIDList (lista de identificadores de item)
para a pasta que contém o item cujo menu de contexto está sendo exibido. lpdobj mantém o objeto
de interface IDataObject, usado para recuperar o objeto sobre o qual a ação está sendo executada.
hkeyProgID contém a chave do Registro para o objeto de arquivo ou tipo de pasta.
A implementação desse método é mostrada no código a seguir. À primeira vista, o código pode
parecer complexo, mas na realidade ele se reduz a três coisas: uma chamada para lpobj.GetData( )
para obter dados de IDataObject e duas chamadas para DragQueryFile( ) (uma chamada para obter
o número de arquivos e outra para obter o nome do arquivo). O nome do arquivo é armazenado no
campo FFileName do objeto. Veja o código a seguir:
IContextMenu
A interface IContextMenu é usada para manipular o menu instantâneo associado a um arquivo no
shell. Essa interface é definida na unidade ShlObj da seguinte maneira:
type
IContextMenu = interface(IUnknown)
[‘{000214E4-0000-0000-C000-000000000046}’]
function QueryContextMenu(Menu: HMENU;
indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall;
function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
end;
Depois que o tratador for inicializado através da interface IShellExtInit, o próximo método a
ser chamado é IContextMenu.QueryContextMenu( ). Os parâmetros passados para esse método inclu-
em uma alça de menu, o índice no qual o primeiro item de menu será inserido, os valores mínimo e
máximo dos IDs de item de menu e flags que indicam os atributos de menu. A implementação de
TContextMenu desse método, mostrada a seguir, adiciona um item de menu com o texto “Package
Info...” à alça de menu passada no parâmetro Menu (observe que o valor de retorno de QueryContext-
Menu( ) é o índice do último item de menu inserido mais um):
O próximo método chamado pelo shell é GetCommandString( ). O objetivo desse método é re-
cuperar a string de comando ou ajuda independente de linguagem de um determinado item de
menu. Os parâmetros desse método incluem o offset do item de menu, flags indicando o tipo de in-
formação a ser recebida, um parâmetro reservado e um buffer de string com o tamanho do buffer. A
implementação de TContextMenu desse método, mostrada a seguir, só precisa lidar com o forneci-
50 mento da string de ajuda para o item de menu:
function TContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HRESULT;
begin
Result := S_OK;
try
// certifica-se de que o índice de menu está correto
// e de que o shell está solicitando a string de ajuda
if (idCmd = FMenuIdx) and ((uType and GCS_HELPTEXT) < > 0) then
// retorna string de ajuda para o item de menu
StrLCopy(pszName, ‘Get information for the selected package.’, cchMax)
else
Result := E_INVALIDARG;
except
Result := E_UNEXPECTED;
end;
end;
Quando você dá um clique no novo item no menu de contexto, o shell chamará o método
InvokeCommand( ). O método aceita um registro TCMInvokeCommandInfo como parâmetro. Esse regis-
tro é definido na unidade ShlObj da seguinte maneira:
type
PCMInvokeCommandInfo = ^TCMInvokeCommandInfo;
TCMInvokeCommandInfo = packed record
cbSize: DWORD; { deve ser SizeOf(TCMInvokeCommandInfo) }
fMask: DWORD; { qualquer combinação de CMIC_MASK_* }
hwnd: HWND; { pode ser NULL (ausência de janela de proprietário) }
lpVerb: LPCSTR; { uma string de MAKEINTRESOURCE(idOffset) }
lpParameters: LPCSTR; { pode ser NULL (ausência de parâmetro) }
lpDirectory: LPCSTR; { pode ser NULL (ausência de diretório específico) }
nShow: Integer; { um dos valores SW_ da API ShowWindow( ) API }
dwHotKey: DWORD;
hIcon: THandle;
end;
A palavra de baixa ordem ou o campo lpVerb conterá o índice do item de menu selecionado.
Veja, a seguir, a implementação desse método:
function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
begin
Result := S_OK;
try
// Certifica-se de que não estamos sendo chamados por uma aplicação
if HiWord(Integer(lpici.lpVerb)) < > 0 then
begin
Result := E_FAIL;
Exit;
end;
// Executa o comando especificado por lpici.lpVerb.
// Retorna E_INVALIDARG se passarmos um número de argumentos inválido.
if LoWord(lpici.lpVerb) = FMenuIdx then
ExecutePackInfoApp(FFileName, lpici.hwnd)
else
Result := E_INVALIDARG;
except
MessageBox(lpici.hwnd, ‘Error obtaining package information.’, ‘Error’,
MB_OK or MB_ICONERROR);
Result := E_FAIL;
end;
end; 51
Se tudo correr bem, a função ExecutePackInfoApp( ) é convocada para chamar a aplicação
PackInfo.exe, que exibe diversas informações sobre um pacote. Não vamos entrar nas particulari-
dades dessa aplicação agora; no entanto, ela é discutida em detalhes no Capítulo 13 da versão ele-
trônica de Delphi 5 Guia do Desenvolvedor, que se encontra neste CD-ROM.
Registro
Os tratadores de menu de contexto devem ser registrados sob
HKEY_CLASSES_ROOT\<tipo de arquivo>\shellex\ContextMenuHandlers
no Registro do sistema. Seguindo o modelo da extensão de copy hook, a capacidade de registro é adi-
cionada à DLL criando-se um descendente de TComObject especializado. O objeto é mostrado na Lis-
tagem 16.10, juntamente com todo o código-fonte completo da unidade que contém TContextMenu.
A Figura 16.9 mostra o menu local do arquivo BPL com o novo item, e a Figura 16.10 mostra a jane-
la PackInfo.exe do modo como é chamada pelo tratador do menu de contexto.
interface
type
TContextMenu = class(TComObject, IContextMenu, IShellExtInit)
private
FFileName: array[0..MAX_PATH] of char;
FMenuIdx: UINT;
protected
// Métodos IContextMenu
function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast,
uFlags: UINT): HResult; stdcall;
function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
// Método IShellExtInit
function Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
hKeyProgID: HKEY): HResult; reintroduce; stdcall;
end;
TContextMenuFactory = class(TComObjectFactory)
protected
function GetProgID: string; override;
procedure ApproveShellExtension(Register: Boolean; const ClsID: string);
virtual;
public
procedure UpdateRegistry(Register: Boolean); override;
end;
implementation
{ TContextMenu }
{ TContextMenu.IContextMenu }
{ TContextMenu.IShellExtInit }
{ TContextMenuFactory }
55
Listagem 16.10 Continuação
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if not OpenKey(SApproveKey, True) then Exit;
if Register then WriteString(ClsID, Description)
else DeleteValue(ClsID);
finally
Free;
end;
end;
const
CLSID_CopyHook: TGUID = '{7C5E74A0-D5E0-11D0-A9BF-E886A83B9BE5}';
initialization
TContextMenuFactory.Create(ComServer, TContextMenu, CLSID_CopyHook,
'DDG_ContextMenu', 'DDG Context Menu Shell Extension Example',
ciMultiInstance, tmApartment);
end.
56
Tratadores de ícones
Os tratadores de ícones permitem que diferentes ícones sejam usados em várias instâncias do mes-
mo tipo de arquivo. Nesse exemplo, o objeto tratador de ícones TIconHandler fornece diferentes
ícones para diferentes tipos de arquivos Borland Package (BPL). Dependendo de um pacote ser de
runtime, projeto, ambos ou nenhum deles, um ícone diferente será exibido em uma pasta do shell.
Flags de pacote
Antes de obter implementações das interfaces necessárias para essa extensão do shell, reserve um
momento para examinar o método que determina o tipo de um determinado arquivo de pacote. O
método retorna TPackType, que é definida da seguinte maneira:
Esse método funciona chamando o método GetPackageInfo( ) da unidade SysUtils para obter
os flags de pacote. Um ponto interessante a ser observado a respeito da otimização do desempenho
é que a função LoadLibraryEx( ) da API é chamada, não o procedimento LoadPackage( ) do Delphi,
para carregar a biblioteca de pacotes. Internamente, o procedimento LoadPackage( ) chama a API
LoadLibrary( ) para carregar a BPL e em seguida chama InitializePackage( ) para executar o códi-
go de inicialização de cada uma das unidades no pacote. Como tudo o que queremos é obter os flags
de pacote e, como os flags residem em um recurso vinculado à BPL, podemos seguramente carregar
o pacote com LoadLibraryEx( ), usando o flag LOAD_LIBRARY_AS_DATAFILE.
57
Interfaces de tratador de ícones
Como já dissemos, os tratadores de ícones devem oferecer suporte para as interfaces IExtractIcon
(definida em ShlObj) e IPersistFile (definida na unidade ActiveX). Essas interfaces são listadas a se-
guir:
type
IExtractIcon = interface(IUnknown)
[‘{000214EB-0000-0000-C000-000000000046}’]
function GetIconLocation(uFlags: UINT; szIconFile: PAnsiChar; cchMax: UINT;
out piIndex: Integer; out pwFlags: UINT): HResult; stdcall;
function Extract(pszFile: PAnsiChar; nIconIndex: UINT;
out phiconLarge, phiconSmall: HICON; nIconSize: UINT): HResult; stdcall;
end;
IPersistFile = interface(IPersist)
[‘{0000010B-0000-0000-C000-000000000046}’]
function IsDirty: HResult; stdcall;
function Load(pszFileName: POleStr; dwMode: Longint): HResult; stdcall;
function Save(pszFileName: POleStr; fRemember: BOOL): HResult; stdcall;
function SaveCompleted(pszFileName: POleStr): HResult; stdcall;
function GetCurFile(out pszFileName: POleStr): HResult; stdcall;
end;
Os ícones são vinculados à DLL da extensão do shell como um arquivo de recurso e, portanto,
o nome do arquivo atual, retornado por GetModuleFileName( ), é escrito no buffer szIconFile. Além
disso, os ícones são arranjados de um modo que o índice de um tipo de pacote corresponda ao índi-
ce do tipo de pacote na enumeração TPackType e, portanto, o valor de retorno de GetPackageType( )
seja atribuído a piIndex.
Flag Significado
GIL_DONTCACHE Os bits da imagem física desse ícone não devem ser colocados em cache pelo
responsável pela chamada. Essa diferença deve ser levada em consideração,
pois um flag GIL_DONTCACHELOCATION pode ser introduzido em futuras
versões do shell.
GIL_NOTFILENAME A localização não é um par nome de arquivo/índice. Os responsáveis pela
chamada que decidem extrair o ícone do local devem chamar o método
IExtractIcon.Extract( ) desse objeto para obter as imagens de ícone
desejadas.
GIL_PERCLASS Todos os objetos dessa classe têm o mesmo ícone. Esse flag é usado
internamente pelo shell. Geralmente, as implementações de IExtractIcon
não exigem esse flag, pois ele significa que um tratador de ícones não é
necessário para apanhar o ícone de cada objeto. O método recomendado para
implementação de ícones por classe é registrar um ícone padrão para a classe.
GIL_PERINSTANCE Cada objeto dessa classe tem seu próprio ícone. Esse flag é usado internamente
pelo shell para cuidar de casos como setup.exe, onde mais de um objeto com
nomes idênticos podem ser conhecidos ao shell e usam diferentes ícones.
Implementações típicas de IExtractIcon não exigem esse flag.
GIL_SIMULATEDOC O responsável pela chamada deve criar um ícone de documento usando o
ícone especificado.
Registro
Os tratadores de ícones devem ser registrados na chave do Registro
HKEY_CLASSES_ROOT\<tipo de arquivo>\shellex\IconHandler.
Mais uma vez, um descendente de TComObjectFactory é criado para lidar com o registro dessa
extensão do shell. Isso é mostrado na Listagem 16.11, juntamente com o resto do código-fonte do
tratador de ícones.
A Figura 16.11 mostra uma pasta do shell contendo pacotes de diferentes tipos. Observe os di-
ferentes ícones de pacotes. 59
Figura 16.11 O resultado do uso do tratador de ícones.
unit IconMain;
interface
type
TPackType = (ptDesign, ptDesignRun, ptNone, ptRun);
TIconHandlerFactory = class(TComObjectFactory)
protected
function GetProgID: string; override;
procedure ApproveShellExtension(Register: Boolean; const ClsID: string);
virtual;
public
procedure UpdateRegistry(Register: Boolean); override;
end;
implementation
60
Listagem 16.11 Continuação
{ TIconHandler }
{ TIconHandler.IExtractIcon }
piIndex := Ord(ptNone);
end;
end;
{ TIconHandler.IPersist }
{ TIconHandler.IPersistFile }
62 { TIconHandlerFactory }
Listagem 16.11 Continuação
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if not OpenKey(SApproveKey, True) then Exit;
if Register then WriteString(ClsID, Description)
else DeleteValue(ClsID);
finally
Free;
end;
end;
const
CLSID_IconHandler: TGUID = '{ED6D2F60-DA7C-11D0-A9BF-90D146FC32B3}';
initialization
TIconHandlerFactory.Create(ComServer, TIconHandler, CLSID_IconHandler,
'DDG_IconHandler', 'DDG Icon Handler Shell Extension Example',
ciMultiInstance, tmApartment);
end.
63
Tratadores de dica de tela
Introduzidos no shell do Windows 2000, os tratadores de dica de tela (InfoTip) oferecem a capaci-
dade de criar IntoTips pop-up personalizadas (também chamadas ToolTips – dicas de tela – no
Delphi) quando o mouse é colocado sobre o ícone que representa um arquivo no shell. A dica de tela
default apresentada pelo shell contém o nome do arquivo, o tipo do arquivo (determinado com
base na sua extensão) e o tamanho do arquivo. Os tratadores de dica de tela são práticos quando
você deseja exibir mais do que essas informações um tanto limitadas e genéricas para o usuário com
um passar de olhos.
Para desenvolvedores Delphi, uma ótima oportunidade são os arquivos de pacote. Embora to-
dos nós saibamos que os arquivos de pacote são compostos de uma ou mais unidades, é impossível
saber rápido exatamente quais unidades estão contidas em seu interior. Anteriormente neste capí-
tulo, você viu um tratador de menu de contexto que oferece essa informação escolhendo uma op-
ção por um menu local, iniciando uma aplicação externa. Agora você verá como obter essa infor-
mação ainda mais facilmente, sem o uso de um programa externo.
type
IQueryInfo = interface(IUnknown)
[SID_IQueryInfo ]
function GetInfoTip(dwFlags: DWORD; var ppwszTip: PWideChar): HResult;
stdcall;
function GetInfoFlags(out pdwFlags: DWORD): HResult; stdcall;
end;
O método GetInfoTip( ) é chamado pelo shell para apanhar a dica de tela para um determina-
do arquivo. O parâmetro dwFlags atualmente não é utilizado. A string da dica de tela é retornada no
parâmetro ppwszTip.
NOTA
O parâmetro ppwszTip aponta para uma string de caracteres larga. A memória para essa string pre-
cisa ser alocada dentro do tratador de dica de tela, usando o alocador de memória do shell. O shell
é responsável por liberar essa memória.
Implementação
Assim como outras extensões do shell, o tratador de dica de tela é implementado como uma
DLL simples de servidor COM. O objeto COM nele contido implementa os métodos IQue-
ryInfo e IPersistFile. A Listagem 16.12 mostra o conteúdo de InfoMain.pas, a unidade princi-
pal para o projeto DDGInfoTip, que contém a implementação do Delphi para um tratador de dica
de tela.
unit InfoMain;
interface
uses
Windows, ActiveX, Classes, ComObj, ShlObj;
type
TInfoTipHandler = class(TComObject, IQueryInfo, IPersistFile)
private
FFileName: string;
FMalloc: IMalloc;
protected
{ IQUeryInfo }
function GetInfoTip(dwFlags: DWORD; var ppwszTip: PWideChar): HResult; stdcall;
function GetInfoFlags(out pdwFlags: DWORD): HResult; stdcall;
{ IPersist }
function GetClassID(out classID: TCLSID): HResult; stdcall;
{ IPersistFile }
function IsDirty: HResult; stdcall;
function Load(pszFileName: POleStr; dwMode: Longint): HResult; stdcall;
function Save(pszFileName: POleStr; fRemember: BOOL): HResult; stdcall;
function SaveCompleted(pszFileName: POleStr): HResult; stdcall;
function GetCurFile(out pszFileName: POleStr): HResult; stdcall;
public
procedure Initialize; override;
end;
TInfoTipFactory = class(TComObjectFactory)
protected
function GetProgID: string; override;
procedure ApproveShellExtension(Register: Boolean; const ClsID: string);
virtual;
public
procedure UpdateRegistry(Register: Boolean); override;
end;
const
Class_InfoTipHandler: TGUID = '{5E08F28D-A5B1-4996-BDF1-5D32108DB5E5}';
implementation
const
TipBufLen = 1024;
end;
procedure TInfoTipHandler.Initialize;
begin
inherited;
// apanha alocador de memória do shell e o salva
SHGetMalloc(FMalloc);
66
Listagem 16.12 Continuação
end;
{ TInfoTipFactory }
67
Listagem 16.12 Continuação
const
SApproveKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved';
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if not OpenKey(SApproveKey, True) then Exit;
if Register then WriteString(ClsID, Description)
else DeleteValue(ClsID);
finally
Free;
end;
end;
initialization
TInfoTipFactory.Create(ComServer, TInfoTipHandler, Class_InfoTipHandler,
'InfoTipHandler', 'DDG sample InfoTip handler', ciMultiInstance, tmApartment);
end.
Existem alguns pontos interessantes nessa implementação. Observe que o alocador de memó-
ria do shell é apanhado e armazenado no método Initialize( ). O alocador mais tarde é usado
para alocar memória para a string de dica de tela no método GetInfoTip( ). O nome do arquivo em
questão é passado para o tratador no método Load( ). O trabalho é realizado no método GetInfo-
Tip( ), que apanha informações do pacote usando a função GetPackageInfo( ), a respeito da qual
você aprendeu anteriormente neste capítulo. À medida que a função de callback PackageInfoCall-
back( ) é chamada repetidamente de dentro de GetPackageInfo( ), a string de dica de tela é concate-
nada arquivo por arquivo.
Registro
A técnica usada para o registro da DLL do servidor COM é quase idêntica à de outras extensões do
shell neste capítulo, como você pode ver na Listagem 16.12. A principal diferença é a chave sob a
qual os tratadores de dica de tela são registrados; estes sempre são registrados sob
HKEY_CLASSES_ROOT\<extensão de arquivo>\shellex\{00021500-0000-0000-C000-000000000046}, on-
de a <extensão de arquivo> é composta do ponto separador e da extensão propriamente dita.
A Figura 16.12 mostra esse tratador de dica de tela em ação.
68
Resumo
Este capítulo inclui todos os diferentes aspectos de extensão do shell do Windows: ícones da bande-
ja de notificação, AppBars, vínculos do shell e uma série de extensões do shell. Ele é uma continua-
ção do conhecimento que você obteve no capítulo anterior, trabalhando com COM e ActiveX. No
Capítulo 17, você aprenderá mais sobre o desenvolvimento baseado em componentes por meio de
interfaces.
69
Usando a API CAPÍTULO
Open Tools
17
NESTE CAPÍTULO
l Interfaces Open Tools
l Uso da API Open Tools
l Assistentes de formulário
Você já se colocou diante da seguinte questão: “O Delphi é realmente bom, mas por que o IDE não
executa esta pequena tarefa que eu gostaria que ele fizesse?” Se esse é o seu caso, a API Open Tools
está aí para satisfazer seus desejos. A API Open Tools do Delphi oferece a capacidade de integrar suas
próprias ferramentas, que trabalham em conjunto com o IDE do Delphi. Neste capítulo, você apren-
derá as diferentes interfaces que compõem a API Open Tools, como usar as interfaces e também como
aproveitar sua habilidade recém-adquirida para escrever um assistente repleto de recursos.
NOTA
A API Open Tools completa só está disponível com o Delphi Professional e Enterprise. O Delphi Perso-
nal tem a capacidade de usar suplementos criados com a API Open Tools, mas não pode criar suple-
mentos, pois só contém as unidades para criar editores de propriedades e componentes. Você pode
achar o código-fonte para as interfaces Open Tools no subdiretório \Delphi 6\Source\ToolsAPI.
A Tabela 17.1 mostra as unidades que compõem a API Open Tools e as interfaces que elas for-
necem. A Tabela 17.2 lista as unidades obsoletas da API Open Tools, que permanecem apenas por
compatibilidade com os experts escritos em Delphi 4 ou anterior. Como as unidades obsoletas são
anteriores ao tipo interface nativo, elas empregam classes regulares do Delphi com métodos abstra-
tos virtuais, como substituto para interfaces verdadeiras. O uso de interfaces verdadeiras foi substi-
tuído pela API Open Tools nas últimas versões do Delphi e a edição atual da API Open Tools é basea-
da principalmente em interface.
ToolsAPI Contém os elementos da API Open Tool baseada na interface mais recente. O
conteúdo dessa unidade basicamente aposenta as unidades da API Open
Tools anteriores ao Delphi 5, que usam classes abstratas para manipular
menus, notificações, o sistema de arquivos, o editor e suplementos do
assistente. Ela também contém as novas interfaces para manipular o
depurador, os principais mapeamentos do IDE, projetos, grupos de projetos,
pacotes e a lista To Do.
VCSIntf Define a classe TIVCSClient, que permite que o IDE do Delphi se comunique
com o software de controle de versão. 71
Tabela 17.1 Continuação
NOTA
Você pode estar se perguntando onde todo esse material referente a assistentes está documentado
no Delphi. Garantimos que está documentado, mas não é nada fácil de se achar. Cada uma dessas
unidades contém documentação completa para a interface, classes, métodos e procedimentos de-
clarados em seu interior. Como não vamos repetir as mesmas informações, convidamos para que
você dê uma olhada nas unidades, para ter acesso à documentação completa.
type
IOTAWizard = interface(IOTANotifier)
[‘{B75C0CE0-EEA6-11D1-9504-00608CCBF153}’]
{ Strings de IU do expert }
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
{ Inicia o AddIn }
procedure Execute;
end;
Essa interface consiste principalmente em algumas funções GetXXX( ) que são projetadas para
serem modificadas pelas classes descendentes, de modo a fornecer informações específicas para cada
assistente. O método Execute( ) é a finalidade comercial de IOTAWizard. Execute( ) é chamado pelo
IDE quando o usuário seleciona o seu assistente no menu principal ou no menu New Items (itens
novos), e é nesse método que o assistente deve ser criado e chamado.
Se você tiver um olho astuto, deve ter percebido que IOTAWizard descende de outra interface,
chamada IOTANotifier. IOTANotifier é uma interface definida na unidade ToolsAPI, que contém mé-
todos que podem ser chamados pelo IDE a fim de notificar um assistente quanto a várias ocorrên-
cias. Essa interface é definida da seguinte maneira:
type
IOTANotifier = interface(IUnknown)
[‘{F17A7BCF-E07D-11D1-AB0B-00C04FB16FB3}’]
{ Este procedimento é chamado imediatamente depois que o item é salvo
com sucesso. Ele não é chamado para IOTAWizards }
procedure AfterSave;
{ Esta função é chamada imediatamente antes de o item ser salvo. Ela
não é chamada para IOTAWizard }
procedure BeforeSave;
{ O item associado está sendo destruído de modo que todas as referências
devem ser liberadas. As exceções são ignoradas. }
procedure Destroyed;
{ Este item associado foi modificado de alguma forma. Ele não é chamado
para IOTAWizards }
procedure Modified;
end;
Como indicam os comentários no código-fonte, a maioria desses métodos não é chamada para
assistentes IOTAWizard simples. Por causa disso, a ToolsAPI fornece uma classe chamada TNotifier-
Object, que fornece implementações vazias para os métodos IOTANotifier. Você pode escolher des-
cender seus assistentes dessa classe para tirar proveito da conveniência de ter métodos IOTANotifier
implementados para você.
Os assistentes não são muito úteis sem um meio para chamá-los, sendo que uma das formas
mais simples de se fazer isso é através de uma escolha de menu. Se você quiser colocar o assistente no
menu principal do Delphi, só precisa implementar a interface IOTAMenuWizard, que é definida em
toda a sua complexidade em ToolsAPI da seguinte maneira:
type
74 IOTAMenuWizard = interface(IOTAWizard)
[‘{B75C0CE2-EEA6-11D1-9504-00608CCBF153}’]
function GetMenuText: string;
end;
Como você pode ver, essa interface descende de IOTAWizard e só inclui um método adicional
para retornar a string de texto do menu.
Para juntar tudo isso e mostrar para que servem as informações que você aprendeu até agora, a
Listagem 17.1 mostra a unidade DumbWiz.pas, que contém o código-fonte de TDumbWizard.
unit DumbWiz;
interface
uses
ShareMem, SysUtils, Windows, ToolsAPI;
type
TDumbWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
// Métodos de IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// Método de IOTAMenuWizard
function GetMenuText: string;
end;
procedure Register;
implementation
uses Dialogs;
procedure TDumbWizard.Execute;
begin
MessageDlg('This is a dumb wizard.', mtInformation, [mbOk], 0);
end;
procedure Register;
begin
RegisterPackageWizard(TDumbWizard.Create);
end;
end.
NomeEmpresa.NomeAssistente
IOTAWizard.Execute( ) chama o assistente. Como a Listagem 17.1 nos mostra, o método Exe-
cute( ) para TDumbWizard não faz muita coisa. Ainda neste capítulo, no entanto, você verá alguns as-
sistentes que realmente realizam algumas tarefas.
IOTAMenuWizard.GetMenuText( ) retorna o texto que deve aparecer no menu principal. Essa
função é chamada todas as vezes que o usuário abre o menu Help e portanto é possível mudar dina-
micamente o valor do texto do menu à medida que o assistente é executado.
Dê uma olhada na chamada para RegisterPackageWizard( ) dentro do procedimento Regis-
ter( ). Você vai perceber que isso é muito parecido com a sintaxe usada para registrar componen-
tes, editores de componentes e editores de propriedades para inclusão na biblioteca de componentes,
como descrito nos Capítulos 11 e 12. A razão para essa semelhança é que esse tipo de assistente é ar-
mazenado em um pacote que é parte da biblioteca de componentes, juntamente com os componen-
tes e tudo o mais. Você também pode armazenar assistentes em uma DLL independente, como verá
no próximo exemplo.
Esse assistente é instalado como qualquer componente: selecione a opção Components,
Install Component (instalar componente) no menu principal e adicione a unidade a um pacote
novo ou existente. Uma vez instalado, a opção de menu para chamar o assistente aparece no menu
Help, como mostra a Figura 17.1. Você pode ver a fantástica saída desse assistente na Figura 17.2.
NOTA
Se você não está acostumado com os detalhes das DLLs no Windows, dê uma olhada no Capítulo 9,
na versão eletrônica de Delphi 5 Guia do Desenvolvedor, no CD que acompanha este livro.
DICA
Não há uma regra infalível que determine se um assistente deve residir em um pacote na biblioteca
de componentes ou em uma DLL. Do ponto de vista de um usuário, a principal diferença entre as
duas é que os assistentes de biblioteca de componentes só precisam da instalação de um pacote
para serem reconstruídos, enquanto os assistentes de DLL exigem uma entrada no Registro e o
Delphi deve ser fechado e reiniciado para que as mudanças façam efeito. No entanto, como um de-
senvolvedor, você verá que os assistentes de pacotes são um pouco mais fáceis de se lidar por uma
série de razões. Especificamente, as exceções se propagam automaticamente entre o seu assistente
e o IDE, você não precisa usar sharemem.dll para gerenciamento de memória, não tem que fazer
nada especial para inicializar a variável de aplicação da DLL e as mensagens de entrada/saída do
mouse e dicas de tela funcionarão a contento.
Com isso em mente, você deve considerar o uso de um assistente de DLL quando quiser que o assis-
tente seja instalado com um mínimo de trabalho por parte do usuário final.
Para que o Delphi reconheça um assistente de DLL, ele deve ter uma entrada no Registro do
sistema, sob a seguinte chave:
HKEY_CURRENT_USER\Software\Borland\Delphi\6.0\Experts
Interface do assistente
O objetivo do assistente Wizard é fornecer uma interface para adicionar, modificar e excluir entra-
das de assistente de DLL do Registro sem ter que usar a complicada aplicação RegEdit. Primeiro, va-
mos examinar InitWiz.pas, a unidade que contém a classe do assistente (veja a Listagem 17.2).
77
Listagem 17.2 InitWiz.pas – a unidade que contém a classe do assistente de DLL
unit InitWiz;
interface
type
TWizardWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
// Métodos de IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// Método de IOTAMenuWizard
function GetMenuText: string;
end;
var
{ Chave do registro onde os assistentes do Delphi 6 são mantidos.
Versão EXE usa default, enquanto versão DLL apanha chave de
ToolServices.GetBaseRegistryKey. }
SDelphiKey: string = ‘\Software\Borland\Delphi\6.0\Experts’;
implementation
procedure TWizardWizard.Execute;
78 { Chamada quando expert é escolhido a partir do menu principal. }
Listagem 17.2 Continuação
end.
Você deve perceber algumas poucas diferenças entre essa unidade e a que é usada para criar o
assistente burro. Mais importante, uma função de inicialização do tipo TWizardInitProc é exigida
como um ponto de entrada para o IDE na DLL do assistente. Nesse caso, essa função é chamada
InitWizard( ). Essa função executa uma série de tarefas de inicialização de assistente, incluindo as
seguintes:
l Obtenção da interface IOTAServices do parâmetro BorlandIDEServices.
l Salvamento do ponteiro de interface BorlandIDEServices para uso posterior.
l Definição da alça da variável Application da DLL como o valor retornado por IOTAServi-
ces.GetParentHandle( ) GetParentHandle( ) retorna a alça da janela que deve servir como o
pai para todas as janelas de nível superior criadas pelo assistente.
l Passagem da instância recém-criada do assistente para o procedimento RegisterProc( )
para registrar o assistente com o IDE. RegisterProc( ) será chamada uma vez para cada ins-
tância de assistente que a DLL registra com o IDE.
l Opcionalmente, InitWizard( ) também pode atribuir um procedimento do tipo TWizard-
TerminateProc para o parâmetro Terminate servir como um procedimento de saída para o
assistente. Esse procedimento será chamado imediatamente antes de o assistente ser descar-
regado pelo IDE e nele você pode executar qualquer limpeza necessária. Inicialmente, esse
parâmetro é nil e, portanto, se você não precisar de nenhuma limpeza especial, deixe seu
valor como nil.
79
ATENÇÃO
O método de inicialização do assistente deve usar a convenção de chamada stdcall.
ATENÇÃO
Os assistentes de DLL que chamam funções da API Open Tools que possuem parâmetros de string
devem ter a mesma unidade ShareMem em sua cláusula uses; caso contrário, o Delphi emitirá uma
violação de acesso quando a instância do assistente for liberada.
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Registry, AddModU, ComCtrls, Menus;
type
TMainForm = class(TForm)
TopPanel: TPanel;
Label1: TLabel;
BottomPanel: TPanel;
WizList: TListView;
PopupMenu1: TPopupMenu;
Add1: TMenuItem;
Remove1: TMenuItem;
Modify1: TMenuItem;
AddBtn: TButton;
RemoveBtn: TButton;
ModifyBtn: TButton;
CloseBtn: TButton;
procedure RemoveBtnClick(Sender: TObject);
procedure CloseBtnClick(Sender: TObject);
procedure AddBtnClick(Sender: TObject);
80
Listagem 17.3 Continuação
var
MainForm: TMainForm;
{ Chave do registro onde os assistentes do Delphi 6 são mantidos.
Versão EXE usa default, enquanto versão DLL apanha chave de
ToolServices.GetBaseRegistryKey. }
SDelphiKey: string = '\Software\Borland\Delphi\6.0\Experts';
implementation
uses InitWiz;
{$R *.DFM}
var
DelReg: TRegistry;
ExpPath := Item.SubItems[0];
OrigName := ExpName; // salva nome original
end;
{ Chama caixa de diálogo que permite ao usuário incluir ou modificar entrada }
if AddModWiz(Action, ExpName, ExpPath) then
begin
{ se ação é Modify e o nome foi mudado, cuida disso }
if (Action = amaModify) and (OrigName < > ExpName) then
DelReg.RenameValue(OrigName, ExpName);
DelReg.WriteString(ExpName, ExpPath); // escreve novo valor
end;
RefreshReg; // atualiza caixa de listagem
end;
procedure TMainForm.RefreshReg;
{ Atualiza caixa de listagem com conteúdo do Registro }
var
i: integer;
TempList: TStringList;
Item: TListItem;
begin
WizList.Items.Clear;
TempList := TStringList.Create;
try
{ Apanha nomes de expert do Registro }
DelReg.GetValueNames(TempList);
{ Apanha strings de caminho para cada nome de expert }
for i := 0 to TempList.Count – 1 do
begin
Item := WizList.Items.Add;
Item.Caption := TempList[i];
Item.SubItems.Add(DelReg.ReadString(TempList[i]));
end;
finally
TempList.Free;
end;
end;
initialization
82 DelReg := TRegistry.Create; // cria objeto do Registro
Listagem 17.3 Continuação
Essa é a unidade responsável pelo fornecimento da interface com o usuário para adicionar, re-
mover e modificar entradas do assistente de DLL no Registro. Na seção initialization desta unida-
de, é criado um objeto TRegistry, chamado DelReg. A propriedade RootKey de DelReg é definida
como HKEY_CURRENT_USER e abre a chave \Software\Borland\Delphi\6.0\Experts – a chave usada para
monitorar assistentes de DLL – usando seu método OpenKey( ).
Quando o assistente é acionado, um componente TListView chamado ExptList é preenchido
com os itens e os valores da chave de Registro mencionada anteriormente. Isso é feito pela chamada
de DelReg.GetValueNames( ) para recuperar os nomes dos itens em TStringList. Um componente
TListItem é adicionado a ExptList para cada elemento na lista de strings e o método DelReg.Read-
String( ) é usado para ler o valor de cada item, que é colocado na lista SubItems de TListItem.
O trabalho do Registro é feito nos métodos RemoveBtnClick( ) e DoAddMod( ). Remo-
veBtnClick( ) é responsável pela remoção do item de assistente atualmente selecionado do Regis-
tro. Ele primeiro verifica para se certificar de que um item está destacado; em seguida, ele abre uma
caixa de diálogo de confirmação. Finalmente, ele faz o trabalho chamando o método DelReg.Dele-
teValue( ) e passando CurrentItem como parâmetro.
DoAddMod( ) aceita um parâmetro do tipo TAddModAction. Esse tipo é definido da seguinte ma-
neira:
type
TAddModAction = (amaAdd, amaModify);
Como se pode deduzir pelos valores do tipo, essa variável indica se um novo item será adicio-
nado ou um item existente será modificado. Essa função primeiro verifica se há um item atualmente
selecionado ou, se não houver, se o parâmetro Action armazena o valor amaAdd. Depois disso, se
Action for amaModify, o item e o valor existentes para o assistente são copiados nas variáveis locais
ExpName e ExpPath . Posteriormente, esses valores são passados para uma função chamada AddMod-
Expert( ), que é definida na unidade AddModU, mostrada na Listagem 17.4. Essa função chama uma
caixa de diálogo na qual o usuário pode inserir informações novas ou modificadas sobre nome ou
caminho para um assistente (veja a Figura 17.5). Ela retorna True quando o usuário fecha a caixa de
diálogo com o botão OK. Nesse ponto, um item existente é modificado usando DelReg.RenameVa-
lue( ) e um valor novo ou modificado é escrito com DelReg.WriteString( ).
interface
uses 83
Listagem 17.4 Continuação
type
TAddModAction = (amaAdd, amaModify);
TAddModForm = class(TForm)
OkBtn: TButton;
CancelBtn: TButton;
OpenDialog: TOpenDialog;
Panel1: TPanel;
Label1: TLabel;
Label2: TLabel;
PathEd: TEdit;
NameEd: TEdit;
BrowseBtn: TButton;
procedure BrowseBtnClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
{$R *.DFM}
end;
end.
{$ifdef BUILD_EXE}
program WizWiz; // Constrói como EXE
{$else}
library WizWiz; // Constrói como DLL
{$endif}
uses
{$ifndef BUILD_EXE}
ShareMem, // ShareMem exigida pela DLL
InitWiz in ‘InitWiz.pas’, // Material do assistente
{$endif}
ToolsAPI,
Forms,
Main in ‘Main.pas’ {MainForm},
AddModU in ‘AddModU.pas’ {AddModForm};
{$ifdef BUILD_EXE}
{$R *.RES} // obrigatório para EXE
{$else}
exports // obrigatório para DLL
InitWizard name WizardEntryPoint; // ponto de entrada obrigatório
{$endif}
begin
{$ifdef BUILD_EXE} // obrigatório para EXE...
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
{$endif}
end.
85
Figura 17.6 A caixa de diálogo Project Options.
Uma última observação sobre este projeto: observe que a função InitWizard( ) da unidade
InitWiz está sendo exportada na cláusula exports do arquivo de projeto. Você deve exportar essa
função com o nome WizardEntryPoint, que é definido na unidade ToolsAPI.
ATENÇÃO
A Borland não fornece um arquivo ToolsAPI.dcu, o que significa que EXEs ou DLLs contendo uma
referência a ToolsAPI em uma cláusula uses só podem ser construídos com pacotes. Atualmente,
não é possível construir assistentes sem pacotes.
DDG Search
Você se lembra do pequeno porém interessante programa que desenvolveu no Capítulo 5? Nesta
seção, você aprenderá como pode tornar essa aplicação útil em um assistente do Delphi ainda mais
útil, adicionando apenas um pouco de código. Esse assistente é chamado de DDG Search.
Primeiro, a unidade que faz a interface de DDG Search com o IDE, InitWiz.pas, é mostrada na
Listagem 17.6. Você vai perceber que essa unidade é muito semelhante à unidade homônima no
exemplo anterior. Isso foi proposital. Essa unidade é apenas uma cópia da anterior, com algumas
mudanças necessárias envolvendo o nome do assistente e o método Execute( ). Copiar e colar é o
que chamamos de “herança à moda antiga”. Afinal de contas, por que digitar mais do que o estrita-
mente necessário?
Listagem 17.6 InitWiz.pas – a unidade que contém a lógica de assistente para o assistente DDG
Search
unit InitWiz;
interface
uses
Windows, ToolsAPI;
type
TSearchWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
// Métodos de IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
86 // Método de IOTAMenuWizard
Listagem 17.6 Continuação
var
ActionSvc: IOTAActionServices;
implementation
procedure TSearchWizard.Execute;
{ Chamado quando o nome do expert for selecionado no menu Help do IDE. }
{ Esta função chama o expert }
begin
// caso não tenha sido criado, cria e mostra
if MainForm = nil then
begin
MainForm := TMainForm.Create(Application);
ThreadPriWin := TThreadPriWin.Create(Application);
MainForm.Show;
end
else
// se criou, restaura a janela e mostra
with MainForm do
begin
if not Visible then Show;
if WindowState = wsMinimized then WindowState := wsNormal;
SetFocus;
end; 87
Listagem 17.6 Continuação
end;
end.
A função Execute( ) deste assistente mostra uma coisa um pouco diferente do que você viu até
agora: o formulário principal do assistente, MainForm, está sendo exibido de forma não-modal, ao
invés de modal. É claro que isso requer um pouco mais de trabalho, pois você tem que saber quando
um formulário é criado e quando a variável do formulário é inválida. Isso pode ser feito certifican-
do-se de que a variável MainForm esteja definida como nil quando o assistente está inativo. Falare-
mos mais sobre isso posteriormente.
Outro aspecto desse projeto que foi significativamente alterado desde o Capítulo 5 é que o ar-
quivo de projeto agora é chamado de DDGSrch.dpr. Esse arquivo é mostrado na Listagem 17.7.
{$IFDEF BUILD_EXE}
program DDGSrch;
{$ELSE}
library DDGSrch;
{$ENDIF}
uses
{$IFDEF BUILD_EXE}
Forms,
{$ELSE}
ShareMem,
ToolsAPI,
InitWiz in 'InitWiz.pas',
{$ENDIF}
Main in 'MAIN.PAS' {MainForm},
SrchIni in 'SrchIni.pas',
SrchU in 'SrchU.pas',
PriU in 'PriU.pas' {ThreadPriWin},
MemMap in '..\..\Utils\MemMap.pas',
DDGStrUtils in '..\..\Utils\DDGStrUtils.pas';
{$R *.RES}
88
Listagem 17.7 Continuação
{$IFNDEF BUILD_EXE}
exports
{ Ponto de entrada que é chamado pelo IDE do Delphi }
InitWizard name WizardEntryPoint;
{$ENDIF}
begin
{$IFDEF BUILD_EXE}
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
{$ENDIF}
end.
Mais uma vez, você pode ver que esse projeto foi criado para ser compilado como um assisten-
te baseado em EXE ou DLL. Quando compilado como um assistente, ele usa o cabeçalho library
para indicar que é uma DLL e exporta a função InitWiz( ) para ser inicializado pelo IDE.
Apenas algumas mudanças foram feitas na unidade Main nesse projeto. Como já dissemos, a va-
riável MainForm precisa ser definida como nil quando o assistente não está ativo. Como você apren-
deu no Capítulo 2, a variável de instância MainForm automaticamente terá o valor nil na partida da
aplicação. Além disso, no tratador de evento OnClose do formulário, a instância do formulário é li-
berada e a global MainForm é redefinida como nil. Veja o método a seguir:
O retoque final desse assistente é trazer os arquivos para o Code Editor do IDE quando o usuá-
rio der um clique duplo na caixa de listagem do formulário principal. Essa lógica é manipulada por
um novo método FileLBDblClick( ), mostrado a seguir::
unit Main;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Buttons, ExtCtrls, Menus, SrchIni,
SrchU, ComCtrls;
type
TMainForm = class(TForm)
FileLB: TListBox;
PopupMenu1: TPopupMenu;
Font1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
FontDialog1: TFontDialog;
StatusBar: TStatusBar;
AlignPanel: TPanel;
ControlPanel: TPanel;
ParamsGB: TGroupBox;
LFileSpec: TLabel;
LToken: TLabel;
lPathName: TLabel;
EFileSpec: TEdit;
EToken: TEdit;
PathButton: TButton;
OptionsGB: TGroupBox;
cbCaseSensitive: TCheckBox;
cbFileNamesOnly: TCheckBox;
cbRecurse: TCheckBox;
SearchButton: TBitBtn;
CloseButton: TBitBtn;
PrintButton: TBitBtn;
PriorityButton: TBitBtn;
View1: TMenuItem;
EPathName: TEdit;
procedure SearchButtonClick(Sender: TObject);
procedure PathButtonClick(Sender: TObject);
procedure FileLBDrawItem(Control: TWinControl; Index: Integer;
90 Rect: TRect; State: TOwnerDrawState);
Listagem 17.8 Continuação
var
MainForm: TMainForm;
implementation
{$R *.DFM}
end;
end;
end;
end;
with StatusBar do
begin
Panels[0].Width := Width div 3;
Panels[1].Width := Width * 2 div 3;
end;
{ centraliza controles no meio do formulário }
ControlPanel.Left := (AlignPanel.Width div 2) – (ControlPanel.Width div 2);
end;
procedure TMainForm.ReadIni;
{ Lê valores default do Registro }
begin
with SrchIniFile do
begin
EPathName.Text := ReadString('Defaults', 'LastPath', 'C:\');
EFileSpec.Text := ReadString('Defaults', 'LastFileSpec', '*.*');
EToken.Text := ReadString('Defaults', 'LastToken', '');
cbFileNamesOnly.Checked := ReadBool('Defaults', 'FNamesOnly', False);
cbCaseSensitive.Checked := ReadBool('Defaults', 'CaseSens', False);
cbRecurse.Checked := ReadBool('Defaults', 'Recurse', False);
Left := ReadInteger('Position', 'Left', 100);
Top := ReadInteger('Position', 'Top', 50);
Width := ReadInteger('Position', 'Width', 510);
Height := ReadInteger('Position', 'Height', 370);
end;
end;
procedure TMainForm.WriteIni;
{ Grava configurações atuais de volta no Registro }
begin
with SrchIniFile do
begin
WriteString('Defaults', 'LastPath', EPathName.Text);
WriteString('Defaults', 'LastFileSpec', EFileSpec.Text);
WriteString('Defaults', 'LastToken', EToken.Text);
WriteBool('Defaults', 'CaseSens', cbCaseSensitive.Checked);
WriteBool('Defaults', 'FNamesOnly', cbFileNamesOnly.Checked);
WriteBool('Defaults', 'Recurse', cbRecurse.Checked);
WriteInteger('Position', 'Left', Left);
WriteInteger('Position', 'Top', Top);
WriteInteger('Position', 'Width', Width);
WriteInteger('Position', 'Height', Height);
end;
end;
end.
DICA
Observe a seguinte linha da Listagem 17.8:
{ $WARN UNIT_PLATFORM OFF }
Essa diretiva do compilador é usada para silenciar o aviso, durante a compilação, que é gerado por-
que Main.pas usa a unidade FileCtrl, que é uma unidade específica da plataforma Windows. Fi-
leCtrl é marcada como tal por meio da diretiva platform.
Assistentes de formulário
Um outro tipo de assistente suportado pela API Open Tools é o assistente de formulário. Uma vez
instalados, os assistentes de formulário são acessados a partir da caixa de diálogo New Items; eles
geram novos formulários e unidades para o usuário. O Capítulo 16 empregou esse tipo de assistente
para gerar novos formulários AppBar; no entanto, você não conseguiu ver o código que fez o assis-
tente funcionar.
É extremamente simples criar um assistente de formulário, embora você deva implementar
uma boa quantidade de métodos de interface. A criação de um assistente de formulário pode ser di-
vidida em cinco etapas básicas:
1. Crie uma classe descendente de TCustomForm, TDataModule ou qualquer TWinControl, que
será usada como a classe básica do formulário. Geralmente, essa classe residirá em uma
unidade separada do assistente. Nesse caso, TAppBar servirá como a classe básica.
2. Crie um descendente de TNotifierObject que implemente as seguintes interfaces: IOTAWi-
96 zard, IOTARepositoryWizard, IOTAFormWizard, IOTACreator e IOTAModuleCreator.
3. No seu método IOTAWizard.Execute( ), você normalmente chamará IOTAModuleServi-
ces.GetNewModuleAndClassName( ) para obter uma nova unidade e nome de classe para seu
assistente e IOTAModuleServices.CreateModule( ) para instruir o IDE a começar a criação
do novo módulo.
4. Muitas das implementações de método das interfaces mencionadas acima possuem apenas
uma linha. Os não-triviais são os métodos NewFormFile( ) e NewImplFile( ) de IOTAModu-
leCreator, que retornam o código para o formulário e a unidade, respectivamente. O mé-
todo IOTACreator.GetOwner( ) pode ser um pouco complicado, mas o exemplo a seguir
oferece uma boa técnica para adicionar a unidade ao projeto atual (se houver).
5. Complete o procedimento Register( ) do assistente registrando um tratador para a nova
classe de formulário, usando o procedimento RegisterCustomModule( ) na unidade
DsgnIntf e criando o assistente com a chamada ao procedimento RegisterPackageWizard( )
da unidade ToolsAPI.
A Listagem 17.9 mostra o código-fonte de ABWizard.pas, que é o assistente AppBar.
unit ABWizard;
interface
type
TAppBarWizard = class(TNotifierObject, IOTAWizard, IOTARepositoryWizard,
IOTAFormWizard, IOTACreator, IOTAModuleCreator)
private
FUnitIdent: string;
FClassName: string;
FFileName: string;
protected
// Métodos de IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// Métodos de IOTARepositoryWizard / IOTAFormWizard
function GetAuthor: string;
function GetComment: string;
function GetPage: string;
function GetGlyph: HICON;
// Métodos de IOTACreator
function GetCreatorType: string;
function GetExisting: Boolean;
function GetFileSystem: string;
function GetOwner: IOTAModule;
function GetUnnamed: Boolean;
// Métodos de IOTAModuleCreator
function GetAncestorName: string;
function GetImplFileName: string;
function GetIntfFileName: string;
function GetFormName: string;
function GetMainForm: Boolean;
function GetShowForm: Boolean;
function GetShowSource: Boolean; 97
Listagem 17.9 Continuação
implementation
{$R CodeGen.res}
type
TBaseFile = class(TInterfacedObject)
private
FModuleName: string;
FFormName: string;
FAncestorName: string;
public
constructor Create(const ModuleName, FormName, AncestorName: string);
end;
{ TBaseFile }
{ TUnitFile }
{ TFormFile }
{ TAppBarWizard }
{ TAppBarWizard.IOTAWizard }
procedure TAppBarWizard.Execute;
begin
(BorlandIDEServices as IOTAModuleServices).GetNewModuleAndClassName(
‘AppBar’, FUnitIdent, FClassName, FFileName);
(BorlandIDEServices as IOTAModuleServices).CreateModule(Self);
end;
{ TAppBarWizard.IOTARepositoryWizard / TAppBarWizard.IOTAFormWizard }
{ TAppBarWizard.IOTACreator }
Result := True;
end;
{ TAppBarWizard.IOTAModuleCreator }
end.
unit %0:s;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, AppBars;
type
T%1:s = class(%2:s)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
%1:s: T%1:s;
implementation
{$R *.DFM}
end.
RegisterCustomModule(TAppBar, TCustomModule);
102 RegisterPackageWizard(TAppBarWizard.Create);
Resumo
Depois da leitura deste capítulo, você deve ter uma compreensão maior das diversas unidades e in-
terfaces envolvidas na API Open Tools do Delphi. Em particular, você deve saber e entender as
questões envolvidas na criação de assistentes plugados no IDE. Este capítulo completa essa parte do
livro. Na próxima seção, você aprenderá técnicas para criar aplicações de porte empresarial, come-
çando com aplicações baseadas em COM+ e MTS.
103
Desenvolvimento CAPÍTULO
BizSnap: escrevendo
Web Services
baseados em SOAP 20
NESTE CAPÍTULO
l O que são Web Services?
l O que é SOAP?
l Escrevendo um Web Service
l Invocando um Web Service por um cliente
O desenvolvimento de soluções de eBusiness rapidamente é a chave para o sucesso de muitas orga-
nizações. Felizmente, a Borland possibilitou esse desenvolvimento rápido por meio de um novo re-
curso no Delphi 6, chamado BizSnap. BizSnap é uma tecnologia que integra XML e Web Services
(serviços da Web) usando um protocolo SOAP no Delphi 6.
O que é SOAP?
SOAP é um acrônimo que significa Simple Object Access Protocol (protocolo simples de acesso a ob-
jeto). SOAP é um protocolo simplificado, usado para a troca de dados em um ambiente distribuído,
semelhante a partes da arquitetura CORBA e DCOM, mas com menos funcionalidade e overhead
resultante. SOAP troca dados usando documentos XML, por meio de HTTP (ou HTTPS) para suas
comunicações. Uma especificação sobre SOAP está disponível para referência na Internet, no ende-
reço http://www.w3.org/TR/SOAP/.
Web Services também usam uma forma de XML para instruir os usuários a respeito deles, cha-
mada WSDL. WSDL é uma abreviação de Web Services Description Language (linguagem de descri-
ção de Web Services). WSDL é usada por aplicações cliente para identificar o que um Web Service
pode fazer, onde ele pode ser encontrado e como chamá-lo.
A coisa mais maravilhosa sobre o BizSnap é que você não precisa aprender todos os detalhes de
SOAP, XML ou WSDL para criar aplicações de Web Service.
Neste capítulo, vamos mostrar como é simples criar um Web Service, e depois vamos mostrar
como acessar esse serviço a partir de uma aplicação cliente.
unit TempConverterIntf;
interface
type
ITempConverter = Interface(IInvokable)
['{6D239CB5-6E74-445B-B101-F76F5C0F6E42}']
function FahrenheitToCelsius(AFValue: double): double; stdcall;
function CelsiusToFahrenheit(ACValue: double): double; stdcall;
function Purpose: String; stdcall;
end;
implementation
uses InvokeRegistry;
initialization
InvRegistry.RegisterInterface(TypeInfo(ITempConverter));
end.
Essa pequena unidade contém somente uma interface, que define os métodos que pretende-
mos publicar como parte do nosso Web Service. Você notará que nossa interface descende de
IInvokable. IInvokable é uma interface simples, compilada com a opção {M+} do compilador, para
garantir que a RTTI seja compilada em todos os seus descendentes. Isso é necessário para permi-
tir que os serviços e clientes Web traduzam o código e informações simbólicas passadas um para o
outro.
Em nosso exemplo, definimos dois métodos para converter temperaturas e um método Purpo-
se( ), que retorna uma string. Além disso, observe que fornecemos um GUID para essa interface,
para que tenha uma identificação exclusiva (para criar um GUID no seu próprio código, basta pres-
sionar Ctrl+Shift+G no editor).
ATENÇÃO
Observe que cada método definido na interface invocável é definido usando a convenção de cha-
mada stdcall. Essa convenção precisa ser usada ou então a interface invocável não funcionará.
unit TempConverterImpl;
interface
uses InvokeRegistry, TempConverterIntf;
type
implementation
{ TTempConverter }
Result := (5/9)*(AFValue-32);
end;
initialization
InvRegistry.RegisterInvokableClass(TTempConverter);
end.
110
Invocando um Web Service por um cliente
Para invocar o Web Service, você precisa conhecer o URL usado para apanhar o documento WSDL.
Esse é o mesmo URL que usamos anteriormente.
Para demonstrar isso, usamos uma aplicação simples com um único formulário principal (veja
a Figura 20.3).
Essa aplicação é simples: o usuário digita uma temperatura no controle de edição, pressiona o
botão de conversão desejado e o valor convertido é exibido no label Temperature. O código-fonte
para essa aplicação aparece na Listagem 20.4.
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Rio, SoapHTTPClient;
type
TMainForm = class(TForm)
btnFah2Cel: TButton;
btnCel2Fah: TButton;
edtArguement: TEdit;
lblTemperature: TLabel;
lblResultValue: TLabel;
lblResult: TLabel;
HTTPRIO1: THTTPRIO;
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
implementation
uses TempConvImport;
{$R *.dfm}
end.
111
No formulário principal, acrescentamos um componente THTTPRIO. Um THTTPRIO representa um
objeto invocável remotamente e atua como um proxy local para um Web Service, que provavel-
mente reside em uma máquina de algum lugar remoto. Os dois tratadores de evento TButton reali-
zam o código para chamar o objeto remoto a partir do nosso Web Service. Observe que temos que
converter o componente THTTPRIO como ITempConverter para nos referirmos a ele. Depois, pode-
mos invocar sua chamada de método.
Antes que esse código possa ser executado, temos que preparar o componente THTTPRIO, que
exige algumas etapas.
Para importar um Web Service em uma aplicação cliente, você digita o caminho WSDL (o
URL especificado anteriormente) no campo WSDL or XML Schema Location e depois pressiona
o botão Generate para criar a unidade de importação. A unidade de importação para nosso Web
Service aparece na Listagem 20.5 e se parece bastante com nossa unidade de definição de interfa-
ce original.
Unit TempConvImport;
interface
ITempConverter = interface(IInvokable)
['{684379FC-7D4B-4037-8784-B58C63A0280D}']
function FahrenheitToCelsius(const AFValue: Double): Double; stdcall;
function CelsiusToFahrenheit(const ACValue: Double): Double; stdcall;
function Purpose: WideString; stdcall;
end;
implementation
112
Listagem 20.5 Continuação
uses InvokeRegistry;
initialization
InvRegistry.RegisterInterface(TypeInfo(ITempConverter),
å 'urn:TempConverterIntf-ITempConverter', '');
end.
Depois que a unidade tiver sido gerada, retorne ao formulário principal da aplicação cliente e
use (uses) a unidade de importação recém-gerada. Isso fará com que o formulário principal tome
conhecimento da nova interface.
Enquanto você insere esse código, observe que o CodeInsight do Delphi está disponível para o
próprio Web Service. Isso porque o Delphi adaptou o Web Service na sua aplicação como um obje-
to nativo. As implicações aqui são bastante amplas: qualquer Web Service trazido para uma aplica-
ção Delphi, não importando se esse serviço é instalado em um Solaris, Windows, Linux ou mainfra-
me, e independente da linguagem em que o servidor está escrito, será beneficiado com isso. Além do
CodeInsight, a aplicação escrita para usar um Web Service também ganhará uma verificação de tipo
do compilador e outros recursos de depuração, devido à forte integração que existe entre estes. 113
Resumo
Web Services são uma nova e poderosa ferramenta para a computação distribuída, usando padrões
abertos e a infra-estrutura existente para permitir a interoperação dentro e entre diferentes plata-
formas.
Neste capítulo, mostramos como criar um Web Service simples e o cliente para usar esse servi-
ço. Demonstramos as etapas necessárias para se distribuir esse servidor e configurar a propriedade
do componente THTTPRIO do cliente. Neste ponto, você já deverá estar familiarizado o suficiente
com o desenvolvimento de Web Services com maior complexidade. Você pode examinar muito
mais exemplos de Web Services no site da comunidade Borland. Um artigo que recomendamos é
“Managing Sessions with Delphi 6 Web Services” (gerenciando sessões com Web Services do Del-
phi 6). Ele foi escrito por Daniel Polistchuck (Article ID: 27575) e pode ser lido em http://commu-
nity.borland.com/article/0,1410,27575,00.html.
114
Desenvolvimento CAPÍTULO
DataSnap
Por Dan Miser
21
NESTE CAPÍTULO
l Mecânica da criação de uma aplicação
multicamadas
l Benefícios da arquitetura multicamadas
l Arquitetura típica do DataSnap
l Usando o DataSnap para criar uma aplicação
l Mais opções para fortalecer sua aplicação
l Exemplos do mundo real
l Mais recursos de dataset do cliente
l Erros clássicos
l Distribuindo aplicações DataSnap
As aplicações multicamadas estão sendo tão comentadas quanto qualquer outro assunto em progra-
mação de computador hoje em dia. Isso está acontecendo por um bom motivo. As aplicações multi-
camadas guardam muitas vantagens em relação às aplicações cliente/servidor mais tradicionais. O
DataSnap da Borland é um meio de ajudá-lo a criar e oferecer uma aplicação multicamadas usando
Delphi, baseando-se em técnicas e habilidades que você acumulou quando usou o Delphi. Este capí-
tulo o acompanhará por algumas informações gerais sobre o projeto de aplicação multicamadas,
mostrando-lhe como aplicar esses princípios para criar aplicações de DataSnap sólidas.
Cliente
DBMS
116
BDE, ADO e outros.
Cliente Servidor
DBMS
IAppServer IAppServer
MIDAS.DLL MIDAS.DLL
Modelo de briefcase
O modelo de briefcase é baseado na metáfora de uma maleta física (ou briefcase). Você coloca seus
papéis importantes na sua maleta e os transporta de um lugar para outro, desempacotando-os quan-
do for necessário. O Delphi oferece um meio de empacotar todos os seus dados e levá-los consigo
para a rua sem exigir uma conexão real com o servidor de aplicação ou servidor de banco de dados.
Tolerância a falhas
Se a máquina servidora estiver indisponível devido a circunstâncias imprevistas, seria bom mudar
dinamicamente para um servidor de backup sem recompilar suas aplicações cliente ou servidor. O
Delphi já oferece funcionalidade embutida para isso.
Balanceamento de carga
Ao distribuir sua aplicação cliente para mais pessoas, você inevitavelmente começará a saturar a lar-
gura de banda do seu servidor. Existem duas maneiras de tentar balancear o tráfego da rede: balan-
ceamento de carga estático e dinâmico. Para o balanceamento de carga estático, você incluiria outra
máquina servidora e teria metade dos clientes usando o servidor A e a outra metade acessando o ser-
vidor B. No entanto, e se os clientes que usam o servidor A impuserem uma carga muito mais pesada
do que aqueles usam o servidor B? Usando o balanceamento de carga dinâmico, você poderia en-
frentar essa situação dizendo a cada aplicação cliente qual servidor ela deve acessar. Existem muitos
algoritmos diferentes para balanceamento de carga dinâmico, como aleatório, seqüencial, “round
robin” e menor tráfego de rede. Do Delphi 4 em diante, isso é feito por meio de um componente
para implementar o balanceamento de carga seqüencial.
TClientDataset
TDatasetProvider TDataset
TDispatchConnection
Cliente Servidor
Servidor
Agora que você viu como uma aplicação DataSnap típica é montada, vamos mostrar como fazer
com que isso aconteça no Delphi. Vamos começar vendo algumas das opções disponíveis quando se
configura o servidor.
Opções de instanciação
A especificação de uma opção de instanciação afeta a quantidade de cópias do processo servidor
que serão iniciadas. A Figura 21.5 mostra como as opções feitas aqui controlam como o seu servidor
se comporta.
Cliente1 Thread1
Cliente3 Thread3
Threading em apartamento
Observe também que a configuração do objeto DCOM possui efeito direto sobre o modo de
instanciação do objeto. Veja mais informações sobre esse tópico na seção “Distribuindo aplicações
DataSnap”.
Opções de threading
O suporte para threading no Delphi 5 viu uma mudança drástica para melhor. No Delphi 4, a sele-
ção do modelo de threading para um servidor EXE era insignificativa. O flag simplesmente marcava
o Registro para dizer ao COM que uma DLL era capaz de ser executada sob o modelo de threading
selecionado. Com o Delphi 5 e 6, a opção de modelo de threading agora se aplica a servidores EXE,
permitindo que o COM coloque as conexões em thread sem usar qualquer código externo. A se-
guir, veja um resumo das opções de threading disponíveis para um RDM:
l Single – A seleção de Single (único) significa que o servidor só é capaz de cuidar de um pedi-
do de cada vez. Ao usar Single, você não precisa se preocupar com as opções de threading,
pois o servidor roda em um thread e o COM trata dos detalhes de sincronismo das mensa-
gens para você. No entanto, essa é a pior seleção que você pode fazer se pretende ter um sis-
tema multiusuário, pois o cliente B teria que esperar até que o cliente A terminasse o pro-
cessamento antes que o cliente B pudesse sequer começar a trabalhar. Isso obviamente não
é uma situação boa, pois o cliente A poderia estar fazendo um relatório de resumo do fim
do dia ou alguma outra operação semelhante, que exija muito tempo.
l Apartment – A seleção do modelo de threading Apartment (apartamento) oferece o melhor
de todos os mundos quando combinada com a instanciação ciMultiInstance. Nesse cená-
rio, todos os clientes compartilham um processo servidor, por causa de ciMultiInstance,
mas o trabalho feito no servidor a partir de um cliente não impede o trabalho do outro cli-
ente, devido à opção de threading Apartment. Ao usar o threading em apartamento, você
garante que os dados da instância do seu RDM estão seguros, mas precisa proteger o acesso
a variáveis globais usando alguma técnica de sincronismo de thread, como PostMessage( ),
seções críticas, mutexes, semáforos ou a classe wrapper TMultiReadExclusiveWriteSynchro-
nizer do Delphi. Esse é o modelo de threading preferido para os datasets BDE. Observe
que, se você usar esse modelo de threading com os datasets BDE, terá que colocar um com-
ponente TSession no seu RDM e definir a propriedade AutoSessionName como True, para
ajudar o BDE a estar de acordo com os requisitos internos para threading.
l Free – Esse modelo oferece ainda mais flexibilidade no processamento do servidor, permi-
tindo que várias chamadas sejam feitas simultaneamente a partir do cliente para o servidor.
No entanto, junto com esse poder vem a responsabilidade. Você precisa ter cuidado para
proteger todos os dados contra conflitos de thread – dados de instância e variáveis globais.
Esse é o modelo de threading preferido quando se usa ADO.
l Both – Essa opção é efetivamente a mesma que a opção Free, com uma exceção – callbacks
120 são colocados automaticamente em série.
Opções de acesso aos dados
O Delphi 6 Enterprise vem com muitas opções diferentes de acesso aos dados. O BDE continua sen-
do aceito, permitindo que você use componentes TDBDataset, como TTable, TQuery e TStoredProc.
No entanto, o DBExpress oferece uma arquitetura mais flexível para o acesso aos dados. Além do
mais, você também tem a opção de oferecer suporte a ADO e ter acesso direto ao InterBase, por
meio dos novos componentes TDataset.
Serviços de anúncio
O RDM é responsável por comunicar quais serviços estarão disponíveis aos clientes. Se o RDM ti-
ver que tornar um TQuery disponível para uso no cliente, você precisa colocar o TQuery no RDM jun-
to com um TDatasetProvider. O componente TDatasetProvider é então ligado ao TQuery por meio da
propriedade TDatasetProvider.Dataset. Mais tarde, quando um cliente aparecer e quiser usar os da-
dos do TQuery, ele poderá fazer isso vinculando-se ao TDatasetProvider que você acabou de criar.
Você pode controlar quais provedores estão visíveis ao cliente definindo a propriedade TDataset-
Provider.Exported como True ou False.
Se, por outro lado, você não precisar que um dataset inteiro fique exposto a partir do servidor
e só precisar que o cliente faça uma chamada de método para o servidor, também poderá fazer isso.
Enquanto o RDM tem o foco, selecione a opção de menu Edit, Add To Interface (adicionar à inter-
face) e preencha a caixa de diálogo com o protótipo de método padrão (que é simplesmente uma de-
claração combinando com um método que você criará na sua implementação). Em seguida, você
pode especificar a implementação desse método no código, como sempre fez, lembrando-se das im-
plicações do seu modelo de threading.
Cliente
Depois de montar o servidor, você precisa criar um cliente para usar os serviços fornecidos pelo ser-
vidor. Vamos dar uma olhada em algumas das opções disponíveis quando estiver montando seu cli-
ente DataSnap.
Opções de conexão
A arquitetura do Delphi para a conexão do cliente ao servidor começa com TDispatchConnecti-
on. Esse objeto básico é o pai de todos os tipos de conexão listados mais adiante. Quando o tipo
de conexão é irrelevante para determinada seção, TDispatchConnection será usado para indicar
esse fato.
TDCOMConnection oferece segurança básica e autenticação, usando a implementação padrão do
Windows para esses serviços. Esse tipo de conexão é útil especialmente se você estiver usando essa
aplicação em uma instalação de intranet/extranet (ou seja, onde as pessoas usando sua aplicação são
conhecidas a partir do ponto de vista do domínio). Você pode usar a vinculação inicial quando esti-
ver usando DCOM, e pode usar callbacks e ConnectionPoints com facilidade. (Você também pode
usar callbacks quando usar soquetes, mas está limitado a usar a vinculação tardia para fazer isso.) As
desvantagens do uso dessa conexão são as seguintes:
l Configuração difícil em muitos casos
l Não é um tipo de conexão amiga de firewall
l Exige instalação de DCOM95 para máquinas Windows 95
TSocketConnection é a conexão mais fácil de se configurar. Além disso, ela só usa uma porta
para o tráfego do DataSnap, de modo que seus administradores de firewall ficarão mais satisfeitos
se tiverem que fazer com que o DCOM trabalhe através do firewall. Você precisa estar rodando o
ScktSrvr (encontrado no diretório <DELPHI>\BIN) para que isso funcione, de modo que há um arqui-
vo extra para distribuir e executar no servidor. O Delphi 4 exigia que você instalasse o WinSock2
para usar esse tipo de conexão, o que significava outra instalação para clientes Windows 9x. No en- 121
tanto, se você não estiver usando callbacks, pode considerar a definição de TSocketConnection.Sup-
portCallbacks como False. Isso permite que você fique com o WinSock 1 nas máquinas cliente.
Você também pode usar TCORBAConnection se quiser usar CORBA como seu protocolo de trans-
porte. CORBA pode ser considerado como o equivalente de padrão aberto do DCOM, e inclui mui-
tos recursos para audodescoberta, recuperação de falha e balanceamento de carga, realizados auto-
maticamente por sua aplicação. Você desejará examinar a arquitetura CORBA ao migrar suas
aplicações DataSnap, para permitir conexões entre plataforma e entre linguagem.
O componente TWebConnection também está disponível para você. Esse componente da cone-
xão permite que o tráfego seja transportado por HTTP ou HTTPS. Ao usar esse tipo de conexão, al-
gumas limitações são as seguintes:
l Callbacks de qualquer tipo não são aceitas.
l O cliente precisa ter a WININET.DLL instalada.
l A máquina servidora precisa estar executando o MS Internet Information Server (IIS) 4.0
ou o Netscape 3.6 em diante.
No entanto, essas limitações parecem compensar quando você precisa oferecer uma aplicação
pela Internet ou por um firewall que não esteja sob o seu controle.
O Delphi 6 introduziu um novo tipo de conexão: a TSOAPConnection. Essa conexão se compor-
ta de modo semelhante a WebConnection, mas conecta-se a um Web Service DataSnap. Ao contrário
de quando se usa outros componentes de conexão DataSnap, você não pode usar a propriedade
AppServer de TSoapConnection para chamar métodos da interface do servidor de aplicação que não
sejam métodos IAppServer. Em vez disso, para se comunicar com um módulo de dados SOAP na in-
terface da aplicação, use um objeto THTTPRIO separado.
Observe que todos esses transportes assumem uma instalação válida do TCP/IP. A única exce-
ção a isso é se você estiver usando duas máquinas Windows NT para se comunicarem via DCOM.
Nesse caso, você pode especificar qual protocolo o DCOM usará, executando o DCOMCNFG e
movendo o protocolo desejado para o topo da lista na guia Default Protocols (protocolos default).
O DCOM para Windows 9x também tem suporte para TCP/IP.
Conectando os componentes
A partir do diagrama na Figura 21.3, você pode ver como a aplicação DataSnap se comunica entre as
camadas. Esta seção indica as principais propriedades e componentes que dão ao cliente a capacida-
de de se comunicar com o servidor.
Para se comunicar do cliente para o servidor, você precisa usar um dos componentes TDispat-
chConnection listados anteriormente. Cada componente possui propriedades específicas apenas a
esse tipo de conexão, mas todas elas permitem que você especifique onde encontrar o servidor de
aplicação. TDispatchConnection é semelhante ao componente TDatabase quando usado em aplica-
ções cliente/servidor, pois define o link com o sistema externo e serve como canal para outros com-
ponentes quando se comunica com elementos a partir desse sistema.
Quando você tem uma conexão com o servidor, precisa de uma maneira de usar os serviços que
expôs no servidor. Isso pode ser feito incluindo-se um TClientDataset no seu cliente e conectando-o
ao TDispatchConnection por meio da propriedade RemoteServer. Quando essa conexão estiver feita,
você poderá ver uma lista dos provedores exportados no servidor percorrendo a lista na propriedade
ProviderName. Você verá uma lista de provedores exportados que existem no servidor. Desse modo, o
componente TClientDataset é semelhante a um TTable nas aplicações cliente/servidor.
Você também tem a capacidade de chamar métodos personalizados que existem no servidor
usando a propriedade TDispatchConnection.AppServer. Por exemplo, a linha de código a seguir
chamará a função Login no servidor, passando dois parâmetros de string e retornando um valor
booleano:
Configurando o servidor
Primeiro, vamos focalizar a mecânica da criação do servidor de aplicação. Depois que você tiver
criado o servidor, vamos explorar como o cliente é criado.
Esse método é chamado sempre que o servidor é registrado ou perde seu registro. Além das en-
tradas do Registro específicas do COM, que são criadas na chamada UpdateRegistry herdada, você
pode chamar os métodos EnableXXXTransport( ) e DisableXXXTransport( ) para marcar esse objeto
como protegido.
123
NOTA
TSocketConnection só mostrará objetos registrados e protegidos na propriedade ServerName. Se
você não quiser impor segurança alguma, desmarque a opção de menu Connections, Registered
Objects Only (somente objetos registrados) no SCKTSRVR.
Provedores
O servidor de aplicação será responsável por fornecer dados para o cliente, de modo que você preci-
sa encontrar um meio de servir dados a partir do servidor em um formato que seja utilizável no cli-
ente. Felizmente, o DataSnap oferece um componente TDatasetProvider para facilitar essa etapa.
Comece incluindo um TQuery no RDM. Se você estiver usando um RDBMS, inevitavelmente
também precisará da configuração de um componente TDatabase. Por enquanto, você vinculará
TQuery a TDatabase e especificará uma consulta simples na propriedade SQL, como select *from
customer. Por último, inclua um componente TDatasetProvider no RDM e vincule-o a TQuery por
meio da propriedade Dataset. A propriedade Exported no DatasetProvider determina se esse prove-
dor será visível aos clientes. Essa propriedade também oferece a capacidade de controlar com facili-
dade quais provedores são visíveis em runtime.
NOTA
Embora a discussão nesta seção focalize o uso do TDBDataset baseado no BDE, os mesmos princí-
pios se aplicam se você quiser usar qualquer outro descendente de TDataset para o acesso aos seus
dados. Existem várias possibilidades já prontas, como DBExpress, ADO e InterBase Express, e vários
componentes de terceiros estão disponíveis para acessar banco de dados específicos.
Registrando o servidor
Quando o servidor de aplicação estiver montado, ele precisa ser registrado com o COM para que
esteja disponível para as aplicações cliente que se conectarão a ele. As entradas do Registro discuti-
das no Capítulo 15 também são usadas para servidores DataSnap. Você só precisa executar a aplica-
ção servidora e a configuração do Registro será acrescentada. No entanto, antes de registrar o servi-
dor, não se esqueça de salvar o projeto em primeiro lugar. Isso garante que o ProgID estará correto
desse ponto em diante.
Se você preferir não executar a aplicação, poderá passar o parâmetro /regserver na linha de
comandos ao executar a aplicação. Isso só realizará o processo de registro e terminará a aplicação
imediatamente. Para remover as entradas do Registro associadas a essa aplicação, você pode usar o
parâmetro /unregserver.
Criando o cliente
Agora que você tem um servidor de aplicação funcionando, vejamos como realizar algumas tarefas
básicas com o cliente. Vamos discutir como apanhar os dados, como editar os dados, como atualizar
o banco de dados com as mudanças feitas no cliente e como lidar com erros durante o processo de
atualização do banco de dados.
Apanhando dados
No decorrer de uma aplicação de banco de dados, é preciso trazer dados do servidor para o cliente,
a fim de que sejam editados. Trazendo os dados para um cache local, você pode reduzir o tráfego na
rede e diminuir os tempos de transação. Em versões anteriores do Delphi, você usaria atualizações
em cache para realizar essa tarefa. No entanto, as mesmas etapas gerais ainda servem para aplica-
124 ções DataSnap.
O cliente fala com o servidor por meio de um componente TDispatchConnection. Oferecendo a
TDispatchConnection o nome do componente onde o servidor de aplicação reside, essa tarefa é reali-
zada com facilidade. Se você usar TDCOMConnection, poderá especificar o nome de domínio total-
mente qualificado (FQDN; por exemplo, nt.dmiser.com), o endereço IP numérico do componente
(por exemplo, 192.168.0.2) ou o nome do NetBIOS do componente (por exemplo, nt). Entretanto,
devido a um bug no DCOM, você não pode usar o nome localhost ou ainda alguns endereços IP, de
modo confiável em todos os casos. Se você usar TSocketConnection, deve especificar endereços IP
numéricos na propriedade Address ou o FQDN na propriedade Host. Examinaremos as opções para
TWebConnection um pouco mais adiante.
Quando você especificar onde o servidor de aplicação reside, terá que dar a TDispatchConnec-
tion um meio de identificar esse servidor de aplicação. Isso é feito por meio da propriedade Server-
Name. A atribuição da propriedade ServerName preenche a propriedade ServerGUID para você. A pro-
priedade ServerGUID é a parte mais importante. Na realidade, se você quiser distribuir sua aplicação
cliente da maneira mais genérica possível, não se esqueça de excluir a propriedade ServerName e sim-
plesmente usar o ServerGUID.
NOTA
Se você usar TDCOMConnection, a lista ServerName só mostrará a lista de servidores que estão regis-
trados na máquina atual. No entanto, TSocketConnection é inteligente o bastante para exibir a lista
de servidores de aplicação registrados na máquina remota.
NOTA
TClientDataset provou ser útil de mais maneiras do que a intenção original. Ele também serve
como um excelente método para armazenar tabelas na memória, o que não tem nada a ver com
DataSnap especificamente. Além disso, devido ao modo como ele expõe dados por meio das pro-
priedades Data e Delta, ele provou ser útil em diversas implementações de padrão de OOP. A dis-
cussão a respeito dessas técnicas está fora do escopo deste capítulo. No entanto, você encontrará
documentos sobre esses tópicos em http://www.xapware.com ou http://www.xapware.com/ddg.
Desfazendo mudanças
A maioria dos usuários tem usado uma aplicação de processamento de textos que permita a opera-
ção Undo (desfazer). Essa operação apanha sua ação anterior e a retorna ao estado imediatamente
antes de ser iniciada. Usando TClientDataset, você pode chamar cdsCustomer.UndoLastChange( )
para simular esse comportamento. A pilha de undo é ilimitada, permitindo que o usuário continue a
recuar até o início da sessão de edição, se assim desejar. O parâmetro que você passa para esse méto-
do especifica se o cursor está posicionado no registro sendo afetado.
Se o usuário quisesse se livrar de todas as suas atualizações ao mesmo tempo, haveria um modo
mais fácil do que chamar UndoLastChange( ) repetidamente. Basta chamar cdsCustomer.CancelUpda-
tes( ) para cancelar todas as alterações que foram feitas em uma única sessão de edição.
ATENÇÃO
Uma palavra de aviso sobre SavePoint está por ordem: você pode invalidar um SavePoint cha-
mando UndoLastChange( ) após o ponto que atualmente está salvo. Por exemplo, suponha que o
usuário edite dois registros e emita um SavePoint. Nesse ponto, o usuário edita outro registro. No
entanto, ele usa UndoLastChange( ) para reverter as mudanças duas vezes em seguida. Como o
estado do TClientDataset agora é o estado anterior ao SavePoint, o SavePoint está em um esta-
do indefinido.
126
Reconciliando dados
Depois que você tiver acabado de fazer as mudanças na cópia local dos dados em TClientDataset,
precisará sinalizar sua intenção de aplicar essas mudanças de volta ao banco de dados. Isso é feito
chamando cdsCustomer.ApplyUpdates( ). Nesse ponto, DataSnap tirará o Delta de cdsCustomer e o
passará ao servidor de aplicação, onde o DataSnap aplicará essas mudanças ao servidor de banco de
dados usando o mecanismo de reconciliação que você escolheu para esse dataset. Todas as atualiza-
ções são realizadas dentro do contexto de uma transação. Logo, explicaremos como os erros são
tratados durante esse processo.
O parâmetro que você passa a ApplyUpdates( ) especifica o número de erros que o processo de
atualização permitirá antes de considerar a atualização como ruim e, subseqüentemente, rolará to-
das as mudanças que foram feitas. A palavra erros aqui se refere a erros básicos de violação, erros de
integridade referencial ou qualquer outra lógica comercial ou erros de banco de dados. Se você es-
pecificar zero para esse parâmetro, estará dizendo ao DataSnap que não tolerará quaisquer erros.
Portanto, se ocorrer um erro, todas as mudanças que você fez não serão submetidas ao banco de da-
dos. Essa é a configuração que você mais usará, pois combina melhor com as orientações e princí-
pios sólidos de banco de dados.
No entanto, se você quiser, poderá especificar que um certo número de erros poderá ocorrer,
enquanto ainda submete todos os registros que tiverem sucesso. A principal extensão desse conceito
é passar -1 como parâmetro para ApplyUpdates( ). Isso diz ao DataSnap que ele deve submeter cada
registro que puder, independente do número de erros encontrados in-process. Em outras palavras,
a transação sempre será submetida quando esse parâmetro for usado.
Se você quiser ter o máximo de controle sobre o processo de atualização – incluindo a mudan-
ça da SQL que executará para uma inserção, atualização ou exclusão –, poderá fazer isso no evento
TDatasetProvider.BeforeUpdateRecord( ). Por exemplo, quando um usuário quer excluir um regis-
tro, você pode não querer realizar realmente uma operação de exclusão no banco de dados. Em vez
disso, um flag é definido para dizer às aplicações que esse registro não está disponível. Mais tarde,
um administrador pode rever essas exclusões e submeter a operação física da exclusão. O exemplo a
seguir mostra como fazer isso:
Você pode criar tantas consultas quantas quiser, controlando o fluxo e o conteúdo do proces-
so de atualização, com base em diferentes fatores, como UpdateKind e valores no Dataset. Ao inspe-
cionar ou modificar registros do DeltaDS, não se esqueça de usar as propriedades OldValue e NewVa-
lue do TField apropriado. O uso das propriedades TField.AsXXX gerará resultados imprevisíveis.
Além disso, você pode impor regras comerciais aqui ou evitar totalmente a postagem de um re-
gistro para o banco de dados. Qualquer exceção que você levanta aqui passará pelo mecanismo de
tratamento de erro do DataSnap, que explicaremos em seguida.
Depois que a transação terminar, você terá oportunidade para lidar com erros. O erro pára em
eventos no servidor e no cliente, dando-lhe uma chance para tomar uma medida corretiva, registrar
o erro ou fazer qualquer outra coisa que queira com ele.
127
A primeira parada para o erro é o evento DatasetProvider.OnUpdateError. Esse é um ótimo lu-
gar para se lidar com erros que você esteja esperando ou que possa resolver sem maiores interven-
ções do cliente.
O destino final para o erro é de volta ao cliente, onde você pode lidar com o erro permitindo
que o usuário ajude a determinar o que fazer com o registro. Você faz isso atribuindo um tratador ao
evento TClientDataset.OnReconcileError.
Isso é útil especialmente porque o DataSnap é baseado em uma estratégica otimista de blo-
queio de registro. Essa estratégia permite que vários usuários trabalhem no mesmo registro ao mes-
mo tempo. Em geral, isso causa conflitos quando o DataSnap tenta reconciliar os dados de volta ao
banco de dados, pois o registro foi modificado desde o momento em que foi apanhado.
O valor do parâmetro Action determina o que o DataSnap fará com esse registro. Vamos focali-
zar, um pouco mais adiante, alguns outros fatores que afetam as ações que são válidas nesse ponto.
A lista a seguir mostra as ações válidas:
l raSkip – Não atualiza esse registro de banco de dados específico. Deixe o registro inaltera-
do na cache do cliente.
128
l raMerge – Mescla os campos desse registro no registro do banco de dados. Esse registro não
se aplicará aos registros que foram inseridos.
l raCorrect – Atualiza o registro do banco de dados com os valores que você especifica. Ao
selecionar essa ação na caixa de diálogo Reconcile Error, você pode editar os valores na
grade. Você não pode usar esse método se outro usuário alterou o registro do banco de
dados.
l raCancel – Não atualiza o registro do banco de dados. Remova o registro do cache do cliente.
l raRefresh – Atualiza o registro no cache do cliente com o registro atual no banco de dados.
l raAbort – Aborta a operação de atualização inteira.
Nem todas essas opções fazem sentido (e, portanto, não serão exibidas) em todos os casos. Um
requisito para que as ações raMerge e raRefresh estejam disponíveis é que o DataSnap possa identifi-
car o registro por meio da chave primária do banco de dados. Para tanto, defina a propriedade TFi-
eld.ProviderFlags.pfInKey como True no componente TDataset do RDM para todos os campos na
sua chave primária.
Server RDM:
procedure TStateless.DataSetProvider1BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
with Sender as TDataSetProvider do
begin
129
DataSet.Open;
if VarIsEmpty(OwnerData) then
DataSet.First
else
begin
while not DataSet.Eof do
begin
if DataSet.FieldByName(‘au_id’).Value = OwnerData then
break;
end;
end;
end;
end;
Client:
procedure TForm1.ClientDataSet1BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
// KeyValue é uma variável OleVariant privada
if not (Sender as TClientDataSet).Active then
KeyValue := Unassigned;
OwnerData := KeyValue;
end;
NOTA
Observe que o exemplo de código anterior percorre o dataset até que encontre o registro apropria-
do. Isso é feito de modo que os datasets unidirecionais, como o dbExpress, possam usar o mesmo
código sem modificação. Naturalmente, existem muitas maneiras de se encontrar o registro apro-
priado, como a modificação da instrução SQL ou o uso de parâmetros em uma consulta, mas este
exemplo se concentra na mecânica de passagem da chave entre cliente e servidor.
130
Usando o modelo de briefcase
Outra otimização para reduzir o tráfego da rede é usar o suporte para o modelo de briefcase ofereci-
do com o DataSnap. Faça isso atribuindo um nome de arquivo à propriedade TClientDataset.File-
name. Se o arquivo especificado nessa propriedade existir, o TClientDataSet abrirá a cópia local do
arquivo, em vez de ler os dados diretamente do servidor de aplicação. Além de permitir que os usuá-
rios trabalhem com arquivos enquanto estão desconectados da rede, isso é tremendamente útil para
itens que raramente mudam, como tabelas de pesquisa.
DICA
Se você especificar um TClientDataset.Filename que tenha uma extensão .XML, o pacote de da-
dos será armazenado no formato XML, permitindo-lhe usar qualquer uma das inúmeras ferramentas
XML disponíveis para atuar sobre o arquivo de briefcase.
ATENÇÃO
A opção poAutoRefresh não funciona no Delphi 5 e 6. poAutoRefresh só funcionará com uma ver-
são futura do Delphi que inclua um reparo para esse bug. A alternativa, nesse meio tempo, é chamar
Refresh( ) para os seus TClientDatasets ou tomar o controle do processo inteiro da aplicação
de atualizações.
A discussão inteira sobre o processo de reconciliação até aqui tem girado em torno da recon-
cialiação default, baseada em SQL. Isso significa que todos os eventos no TDataset básico não serão
usados durante o processo de reconciliação. A propriedade TDatasetProvider.ResolveToDataset foi
criada para usar esses eventos durante a reconciliação. Por exemplo, se TDatasetProvider.Resolve-
ToDataset for verdadeiro, a maioria dos eventos no TDataset será disparada. Saiba que os eventos
usados são chamados apenas quando da aplicação de atualizações de volta ao servidor. Em outras
palavras, se você tiver um evento TQuery.BeforeInsert definido no servidor, ele só será disparado
no servidor quando você chamar TClientDataSet.ApplyUpdates. Os eventos não se integram aos
eventos correspondentes do TClientDataSet.
Depois você atribui o TQuery.Datasource para o TQuery do detalhe apontar para um componen-
te TDatasource que está ligado ao TDataset mestre. Quando esse relacionamento for estabelecido,
você só precisa exportar o TDatasetProvider que está ligado ao dataset mestre. O DataSnap é inteli-
gente o bastante para entender que o dataset mestre possui datasets de detalhe vinculados a ele e,
portanto, enviará os datasets de detalhe para o cliente como um TDatasetField.
No cliente, você atribui a propriedade TClientDataset.ProviderName do mestre para o prove-
dor do mestre. Depois, você inclui campos persistentes ao TClientDataset. Observe o último campo
no Fields Editor. Ele contém um campo com o mesmo nome do dataset de detalhe no servidor,
sendo declarado como um tipo TDatasetField. Nesse ponto, você tem informações suficientes para
usar o dataset aninhado no código. No entanto, para tornar as coisas realmente fáceis, você pode in-
cluir um TClientDataset de detalhe e atribuir sua propriedade DatasetField ao TDatasetField apro-
priado a partir do mestre. É importante observar aqui que você não configurou quaisquer outras
propriedades no TClientDataset de detalhe, como RemoteServer, ProviderName, MasterSource, Mas-
terFields ou PacketRecords. A única propriedade que você definiu foi a propriedade DatasetField.
Nesse ponto, você também pode vincular controles cientes de dados ao TClientDataset de detalhe.
Depois que você acabar de trabalhar com os dados no dataset aninhado, terá que aplicar as
atualizações de volta ao banco de dados. Isso é feito chamando o método ApplyUpdates( ) do TClient-
Dataset mestre. O DataSnap aplicará todas as mudanças no TClientDataset mestre, incluindo os da-
tasets de detalhe, de volta ao servidor dentro do contexto da transação.
Você verá um exemplo no CD-ROM que acompanha este livro, no diretório para este capítu-
lo, sob \NestCDS.
Vínculo no cliente
Lembre-se de que fizemos alguns avisos anteriormente com relação ao uso de datasets aninhados. A
alternativa ao uso de datasets aninhados é a criação de um relacionamento mestre/detalhe no clien-
te. Para criar um vínculo mestre/detalhe usando esse método, você simplesmente cria um TDataset e
TDatasetProvider para o mestre e o detalhe no servidor.
No cliente, você vincula dois componentes TClientDataset aos datasets que exportou no servi-
dor. Depois, crie o relacionamento mestre/detalhe atribuindo a propriedade TClientDataset.Mas-
terSource do detalhe ao componente TDatasource que aponta para o TClientDataset mestre.
A definição de MasterSource em um TClientDataset define a propriedade PacketRecords como
zero. Quando PacketRecords é igual a zero, isso significa que o DataSnap deve apenas retornar as in-
formações de metadados para esse TClientDataset. No entanto, quando PacketRecords é igual a 133
zero no contexto de um relacionamento mestre/detalhe, o significado muda. O DataSnap agora apa-
nhará os registros para o dataset de detalhe para cada registro mestre. Resumindo, deixe a proprie-
dade PacketRecords definida com o valor default.
Para reconciliar os dados mestre/detalhe no banco de dados em uma transação, você precisa
escrever sua própria lógica de ApplyUpdates. Isso não é tão simples quanto a maioria das tarefas em
Delphi, mas oferece controle flexível sobre o processo de atualização.
A aplicação de atualizações a uma única tabela normalmente é disparada por uma chamada a
TClientDataset.ApplyUpdates. Esse método envia os registros alterados do ClientDataset para seu
provedor na camada intermediária, onde o provedor gravará então as alterações no banco de da-
dos. Tudo isso é feito dentro do escopo de uma transação e é feito sem qualquer intervenção do pro-
gramador. Para fazer a mesma coisa para as tabelas mestre/detalhe, você precisa entender o que o
Delphi está fazendo por você quando é feita a chamada a TClientDataset.ApplyUpdates.
Quaisquer alterações que você faça a um TClientDataset são armazenadas na propriedade Del-
ta. A propriedade Delta contém todas as informações que por fim serão gravadas no banco de da-
dos. O código a seguir ilustra o processo de atualização para a aplicação de propriedades Delta de
volta ao banco de dados. As Listagens 21.2 e 21.3 mostram as seções relevantes do cliente e do servi-
dor para a aplicação de atualizações em uma configuração mestre/detalhe.
procedure TClientDM.ApplyUpdates;
var
MasterVar, DetailVar: OleVariant;
begin
Master.CheckBrowseMode;
Detail_Proj.CheckBrowseMode;
if Master.ChangeCount > 0 then
MasterVar := Master.Delta else
MasterVar := NULL;
if Detail.ChangeCount > 0 then
DetailVar := Detail.Delta else
DetailVar := NULL;
RemoteServer.AppServer.ApplyUpdates(DetailVar, MasterVar);
{ Reconcia os pacotes de dados com erro. Como permitimos 0 erros, somente um
pacote de erro pode conter erros. Se nenhum pacote tiver erros, então
atualizamos os dados. }
if not VarIsNull(DetailVar) then
Detail.Reconcile(DetailVar) else
if not VarIsNull(MasterVar) then
Master.Reconcile(MasterVar) else
begin
Detail.Reconcile(DetailVar);
Master.Reconcile(MasterVar);
Detail.Refresh;
Master.Refresh;
end;
end;
try
if not VarIsNull(MasterVar) then
begin
MasterVar := cdsMaster.Provider.ApplyUpdates(MasterVar, 0, ErrCount);
if ErrCount >0 then
SysUtils.Abort; // Isso causará o Rollback
end;
if not VarIsNull(DetailVar) then
begin
DetailVar := cdsDetail.Provider.ApplyUpdates(DetailVar, 0, ErrCount);
if ErrCount > 0 then
SysUtils.Abort; // Isso causará o Rollback
end;
Database.Commit;
except
Database.Rollback
end;
end;
Embora esse método funcione muito bem, ele realmente não oferece oportunidade para reuti-
lização de código. Essa seria uma boa hora para estender o Delphi e oferecer uma reutilização fácil.
Aqui estão as principais etapas exigidas para resumir o processo de atualização:
1. Coloque os deltas para cada CDS em um array de variantes.
2. Coloque os provedores para cada CDS em um array de variantes.
3. Aplique todos os deltas em uma transação.
4. Reconcilie os pacotes de dados de erro retornados na etapa anterior e atualize os dados.
O resultado dessa abstração é fornecido na unidade utilitária mostrada na Listagem 21.4.
unit CDSUtil;
interface
uses
DbClient, DbTables;
implementation
uses
SysUtils, Provider, Midas, Variants;
type
PArrayData = ^TArrayData;
TArrayData = array[0..1000] of Olevariant; 135
Listagem 21.4 Continuação
{ Chamada do servidor }
procedure CDSApplyUpdates(ADatabase : TDatabase; var vDeltaArray: OleVariant;
const vProviderArray: OleVariant);
var
i : integer;
LowArr, HighArr: integer;
P: PArrayData;
begin
{ Envolve as atualizações em uma transação. Se qualquer etapa resultar
{ em um erro, levanta uma exceção, que causará o Rollback da transação. }
ADatabase.Connected:=true;
ADatabase.StartTransaction;
try
LowArr:=VarArrayLowBound(vDeltaArray,1);
HighArr:=VarArrayHighBound(vDeltaArray,1);
P:=VarArrayLock(vDeltaArray);
try
for i:=LowArr to HighArr do
ApplyDelta(vProviderArray[i], P^[i]);
finally
VarArrayUnlock(vDeltaArray);
end;
ADatabase.Commit;
except
ADatabase.Rollback;
end;
end;
{ Chamadas no cliente }
function RetrieveDeltas(const cdsArray : array of TClientDataset): Variant;
var
i : integer;
LowCDS, HighCDS : integer;
begin
Result:=NULL;
LowCDS:=Low(cdsArray);
HighCDS:=High(cdsArray);
136 for i:=LowCDS to HighCDS do
Listagem 21.4 Continuação
cdsArray[i].CheckBrowseMode;
end.
137
A Listagem 21.5 mostra a modificação do exemplo anterior usando a unidade CDSUtil.
Você pode usar essa unidade em aplicações de duas camadas ou três camadas. Para passar de
um modelo de duas camadas para três, você exportaria uma função no servidor que chame
CDSApplyUpdates em vez de chamar CDSApplyUpdates no cliente. Tudo o mais no cliente permanece
inalterado.
Você encontrará um exemplo neste CD-ROM no diretório para este capítulo, sob \MDCDS.
Associações
A escrita de uma aplicação de banco de dados relacional depende bastante de percorrer os relacio-
namentos entre as tabelas. Normalmente, você achará conveniente representar seus dados altamen-
te normalizados em uma visão mais achatada do que a estrutura de dados básica. No entanto, a atua-
lização dos dados a partir dessas associações exige mais cuidado da sua parte.
Fazendo isso, você está dizendo a ClientDataset para registrar o nome da tabela para você.
Agora, quando você chamar ClientDataset1.ApplyUpdates( ), o DataSnap saberá experimentar e
traduzir o nome de tabela que você especificou, ao contrário de permitir que o DataSnap tente des-
cobrir qual seria o nome da tabela.
Uma técnica alternativa seria usar um componente TUpdateSQL que só atualiza a tabela em
que você está interessado. Isso permite que TQuery.UpdateObject seja usado durante o processo de
reconciliação e combina mais de perto com o processo usado nas aplicações cliente/servidor tradi-
cionais.
NOTA
Nem todos os TDatasets possuem uma propriedade UpdateObject. No entanto, você ainda pode
usar a mesma técnica, devido à modificação feita em TUpdateSQL. Basta definir sua SQL para cada
ação (exclusão, inserção, modificação) e usar um código semelhante a este:
procedure TForm1.DataSetProvider1BeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
UpdateKind: TUpdateKind; var Applied: Boolean);
begin
UpdateSQL1.DataSet := DeltaDS;
UpdateSQL1.SetParams(UpdateKind);
ADOCommand1.CommandText := UpdateSQL1.SQL[UpdateKind].Text;
ADOCommand1.Parameters.Assign(UpdateSQL1.Query[UpdateKind].Params);
ADOCommand1.Execute;
Applied := true;
end;
Você encontrará um exemplo neste CD-ROM, no diretório para este capítulo, sob \Join1.
139
Ao usar atualizações em cache com uma associação de múltiplas tabelas, você precisa configu-
rar um componente TUpdateSQL para cada tabela que será atualizada. Como a propriedade Upda-
teObject só pode ser atribuída a um componente TUpdateSQL, você precisa vincular programatica-
mente todas as propriedades TUpdateSQL.Dataset ao dataset associado em TQuery.OnUpdateRecord e
chamar TUpdateSQL.Apply para associar os parâmetros e executar a instrução SQL básica. Nesse
caso, o dataset em que você está interessado é o dataset Delta. Esse dataset é passado como parâme-
tro para o evento TQuery.OnUpdateRecord.
Tudo o que você precisa fazer é atribuir as propriedades SessionName e DatabaseName para per-
mitir que a atualização ocorra no mesmo contexto das outras transações e ligar a propriedade Data-
set ao Delta que é passado ao evento. O código resultante para o evento TQuery.OnUpdateRecord apa-
rece na Listagem 21.6.
usqlFTEmp.SessionName := JoinQuery.SessionName;
usqlFTEmp.DatabaseName := JoinQuery.DatabaseName;
usqlFTEmp.Dataset := Dataset;
usqlFTEmp.Apply(UpdateKind);
UpdateAction := uaApplied;
end;
Como você cumpriu as regras de atualização de dados dentro da arquitetura DataSnap, o pro-
cesso de atualização inteiro é disparado de forma transparente, como sempre acontece no DataSnap,
com uma chamada a ClientDataset1.ApplyUpdates(0).
Você encontrará um exemplo neste CD-ROM, no diretório para este capítulo, sob \Join2.
DataSnap na Web
Até mesmo com a introdução do Kylix, o Delphi está ligado à plataforma Windows (ou Linux); por-
tanto, quaisquer clientes que você escreva precisam ser executados nesse tipo de máquina. Isso nem
sempre é desejável. Por exemplo, você poderia oferecer acesso fácil aos dados que existem no seu
banco de dados a qualquer um que tenha uma conexão com a Internet. Como você já escreveu um
servidor de aplicação que atua como agente para seus dados – além de abrir regras comerciais para
esses dados –, seria desejável reutilizar o servidor de aplicação em vez de reescrever, em outro ambi-
ente, toda a camada de acesso aos dados e regra comercial.
HTML pura
Esta seção explica como aproveitar seu servidor de aplicação e, ao mesmo tempo, oferecer uma
nova camada de apresentação que usará HTML pura. Esta seção considera que você está acostuma-
do com o material abordado no Capítulo 31 de Delphi 5 Guia do Desenvolvedor, que está no
CD-ROM deste livro. Usando esse método, você está introduzindo outra camada na sua arquitetu-
ra. O WebBroker atua como cliente para o servidor de aplicação e empacota esses dados na HTML
que será exibida no browser. Você também perde alguns dos benefícios do trabalho com o IDE do
Delphi, como o uso de controles cientes dos dados. No entanto, essa é uma opção bastante viável
140 para permitir o acesso aos seus dados em um formato HTML simples.
Depois de criar uma aplicação WebBroker e um WebModule, você simplesmente inclui TDispat-
chConnection e TClientDataset no WebModule. Quando as propriedades estiverem preenchidas, você
poderá usar diversos métodos diferentes para traduzir esses dados em HTML que, por fim, será vis-
ta pelo cliente.
Uma técnica válida seria incluir um TDatasetTableProducer vinculado ao TClientDataset de seu
interesse. A partir daí, o usuário poderá dar um clique em um vínculo e passar para uma página de
edição, onde poderá editar os dados e aplicar as atualizações. Veja as Listagens 21.7 e 21.8 para uma
implementação de exemplo dessa técnica.
unit WebMain;
interface
uses
Windows, Messages, SysUtils, Classes, HTTPApp, DBWeb, Db, DBClient,
MConnect, DSProd, HTTPProd;
type
TWebModule1 = class(TWebModule)
dcJoin: TDCOMConnection;
cdsJoin: TClientDataSet;
dstpJoin: TDataSetTableProducer;
dsppJoin: TDataSetPageProducer; 141
Listagem 21.8 Continuação
ppSuccess: TPageProducer;
ppError: TPageProducer;
procedure WebModuleBeforeDispatch(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
procedure WebModule1waListAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
procedure dstpJoinFormatCell(Sender: TObject; CellRow,
CellColumn: Integer; var BgColor: THTMLBgColor;
var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs,
CellData: String);
procedure WebModule1waEditAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
procedure dsppJoinHTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings;
var ReplaceText: String);
procedure WebModule1waUpdateAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
{ Declarações privadas }
DataFields : TStrings;
public
{ Declarações públicas }
end;
var
WebModule1: TWebModule1;
implementation
{$R *.DFM}
cdsJoin.Open;
if cdsJoin.Locate('EMPNO', EmpNo, [ ]) then
begin
cdsJoin.Edit;
cdsJoin.FieldByName('LastName').AsString:=LastName;
cdsJoin.FieldByName('FirstName').AsString:=FirstName;
cdsJoin.FieldByName('HireDate').AsString:=HireDate;
cdsJoin.FieldByName('Salary').AsString:=Salary;
cdsJoin.FieldByName('Vacation').AsString:=Vacation;
if cdsJoin.ApplyUpdates(0)=0 then
Response.Content:=ppSuccess.Content else
Response.Content:=pPError.Content;
end;
end;
end.
143
Observe que esse método exige que muito código personalizado seja escrito, e o conjunto de
recursos completo do DataSnap não é implementado nesse exemplo – especificamente, a reconci-
liação de erro. Você pode continuar a melhorar esse exemplo para ser mais robusto se usar essa téc-
nica extensivamente.
ATENÇÃO
É imperativo que você considere o conceito de estado ao escrever seu WebModule e servidor de
aplicação. Como o HTTP é um protocolo sem estado, você não pode contar com os valores das pro-
priedades como sendo iguais aos que você deixou quando a chamada terminou.
DICA
O WebBroker é um meio de apanhar seus dados para browsers Web. Usando o WebSnap, você
pode estender as capacidades da sua aplicação ainda mais, usando os novos recursos que o Web-
Snap oferece, como scripting e suporte para sessão.
Para executar este exemplo, não se esqueça de compilar e registrar a aplicação de exemplo Jo-
in2. Em seguida, compile a aplicação da Web (uma versão CGI ou ISAPI) e coloque o executável em
um diretório capaz de trabalhar com script para o seu servidor da Web. O código também espera en-
contrar o arquivo join.htm no diretório de scripts; portanto, copie-o também. Depois, basta apontar
seu browser para http://localhost/scripts/WebJoin.exe e ver os resultados desse exemplo.
Você encontrará um exemplo neste CD-ROM, no diretório para este capítulo, sob \WebBrok.
InternetExpress
Com o InternetExpress, você pode melhorar a funcionalidade de uma técnica WebModule pura para
permitir uma experiência mais rica no cliente. Isso é possível devido ao uso de padrões abertos,
como XML e JavaScript, no InternetExpress. Usando o InternetExpress, você pode criar um
front-end apenas com browser para o seu servidor de aplicação DataSnap: nada de controles Acti-
veX para baixar; nada de instalação no cliente e requisitos de configuração; nada além de um brow-
ser Web alcançando um servidor Web.
Para usar o InternetExpress, você deverá ter algum código rodando em um servidor Web.
Para este exemplo, usaremos uma aplicação ISAPI, mas você poderia usar CGI ou ASP. A finali-
dade do agente Web é apanhar pedidos do browser e passá-los para o servidor de aplicação. A
colocação de componentes InternetExpress na aplicação agente da Web torna essa tarefa muito
fácil.
Este exemplo usará um servidor de aplicação DataSnap padrão, que possui clientes, pedidos e
funcionários. Os clientes e os pedidos estão associados em um relacionamento de dataset aninhado
(para obter mais informações sobre datasets aninhados, leia a próxima seção), enquanto o dataset
de funcionários servirá como tabela de pesquisa. Depois que o servidor de aplicação tiver sido cria-
do e registrado, você poderá focalizar a criação da aplicação agente Web, que se comunicará com o
servidor de aplicação.
Crie uma nova aplicação ISAPI selecionando File, New, Web Server Application no Object
Repository. Acrescente um componente TDCOMConnection no WebModule. Isso atuará como víncu-
lo com o servidor de aplicação; portanto, preencha a propriedade ServerName com o ProgID do ser-
vidor de aplicação.
Em seguida, você acrescentará no WebModule um componente TXMLBroker a partir da página
InternetExpress da Component Palette e definirá as propriedades RemoteServer e ProviderName
como o CustomerProvider. O componente TXMLBroker atua de modo semelhante ao TClientData-
set. Ele é responsável por apanhar pacotes de dados do servidor de aplicação e passar esses pacotes
de dados para o browser. A principal diferença entre o pacote de dados em um TXMLBroker e um
144 TClientDataset é que o TXMLBroker traduz os pacotes de dados do DataSnap para XML. Você também
incluirá um TClientDataset ao WebModule e o vinculará ao provedor de funcionários no servidor de
aplicação. Mais adiante, você usará isso como origem de dados de pesquisa.
O componente TXMLBroker é responsável pela comunicação com o servidor de aplicação e tam-
bém pela navegação das páginas HTML. Existem muitas propriedades disponíveis para se persona-
lizar o modo como sua aplicação InternetExpress se comportará. Por exemplo, você pode limitar o
número de registros que serão transmitidos ao cliente ou especificar o número de erros permitidos
durante uma atualização.
Você agora precisa de um meio para mover esses dados para o browser. Usando o componente
TInetXPageProducer, você pode usar a tecnologia WebBroker no Delphi para levar uma página
HTML até o browser. No entanto, o TInetXPageProducer também permite a criação visual da página
Web por meio do Web Page Editor.
Dê um clique duplo no TInetXPageProducer para fazer surgir o Web Page Editor. Esse editor vi-
sual o ajuda a personalizar os elementos presentes em determinada página Web. Uma das coisas
mais interessantes sobre o InternetExpress é que ele é completamente extensível. Você pode criar
seus próprios componentes, que podem ser usados no Web Page Editor seguindo algumas regras
bem definidas. Para obter exemplos de componentes InternetExpress personalizados, consulte o
diretório <DELPHI>\DEMOS\MIDAS\INTERNETEXPRESS\INETXCUSTOM.
ATENÇÃO
TInetXPageProducer possui um diretório chamado IncludePathURL. É essencial definir essa pro-
priedade corretamente ou então sua aplicação InternetExpress não funcionará. Defina esse valor
como o diretório virtual que contém os arquivos JavaScript do InternetExpress. Por exemplo, se você
incluir os arquivos em c:\inetpub\wwwroot\jscript, o valor para essa propriedade será
/jscript/.
Com o Web Page Editor ativo, dê um clique no botão Insert (inserir) para exibir a caixa de diá-
logo Add Web Component (adicionar componente Web) (veja a Figura 21.7). Essa caixa de diálogo
contém uma lista de componentes Web que podem ser acrescentados na página HTML. Essa lista é
baseada no componente pai (a seção no canto superior esquerdo) que está atualmente selecionado.
Por exemplo, inclua um componente Web DataForm ao nó raiz para permitir que os usuários finais
apresentem e editem informações de banco de dados em um layout tipo formulário.
Figura 21.7 A caixa de diálogo Add Web Component trazida a partir do Web Page Editor.
Se, depois disso, você selecionar o nó DataForm no Web Page Editor, poderá dar um clique no
botão Insert (inserir) novamente. Observe que a lista de componentes disponíveis nesse ponto é di-
ferente da lista exibida na etapa anterior. Depois de selecionar o componente FieldGroup, você
verá um aviso no painel de visualização, informando que a propriedade TXMLBroker para o Field-
Group não está atribuída. Atribuindo o XMLBroker no Object Inspector, você notará imediata-
145
mente o layout da HTML no painel de visualização do Web Page Editor. Ao continuar a modificar
propriedades ou incluir componentes, o estado da página HTML será constantemente atualizado
(veja a Figura 21.8).
Figura 21.8 O Web Page Editor após a criação de uma página HTML.
146
Figura 21.9 Internet Explorer acessando a página Web do InternetExpress.
Esse método exige mais código e esforço do que nas versões anteriores do Delphi, onde você
simplesmente atribuiria a propriedade Table1.Provider.Data à propriedade ClientDataset1.Data.
No entanto, essa função ajudará a tornar o código adicional menos observável.
Você também pode usar o componente TClientDataset para apanhar os dados de um TDataset
durante o projeto, selecionando o comando Assign Local Data (atribuir dados locais) no menu de
contexto do componente TClientDataset. Depois, você especifica o cmp TDataset que contém os
dados que você deseja, e os dados são trazidos para o TClientDataset e armazenados na propriedade
Data.
ATENÇÃO
Se você tivesse que salvar o arquivo nesse estado e comparar o tamanho do arquivo DFM para o ta-
manho antes de executar esse comando, notaria um aumento no tamanho do DFM. Isso porque o
Delphi armazenou todos os metadados e registros associados ao TDataset no DFM. O Delphi só
colocará esses dados em stream no DFM se o TClientDataset estiver Active. Você também pode
remover esse espaço executando o comando Clear Data (apagar dados) no menu de contexto de
TClientDataset.
Se você quiser ter a flexibilidade total que uma atribuição de provedor oferece, terá que atribu-
ir a propriedade AppServer. Em runtime, você pode atribuir a propriedade AppServer no código. Isso
pode ser tão simples quanto a seguinte instrução, encontrada em FormCreate:
ClientDataset1.AppServer:=TLocalAppServer.Create(Table1);
148 ClientDataset1.Open;
Você pode atribuir a propriedade AppServer durante o projeto. Se deixar a propriedade Remo-
teServer em branco em um TClientDataset, você poderá atribuir um componente TDatasetProvider
à propriedade TClientDataset.ProviderName.
Uma desvantagem importante no uso da propriedade TClientDataset.ProviderName é que ela
não pode ser atribuída a provedores que residem em outro formulário ou DataModule durante o proje-
to. Isso porque o Delphi 6 introduziu o componente TLocalConnection. TLocalConnection descobrirá e
exporá automaticamente quaisquer TDatasetProviders que encontrar com o mesmo proprietário.
Para usar esse método de atribuição de provedores, atribua a propriedade ClientDataset.RemoteSer-
ver para ser o componente LocalConnection no formulário ou DataModule externo. Depois disso, você
terá a lista de provedores para essa LocalConnection na propriedade ClientDataset.ProviderName.
A diferença principal entre o uso de componentes TDataset e ClientDataset é que, quando
você está usando ClientDataset, está usando a interface IAppServer para agenciar seus pedidos de
dados ao componente TDataset básico. Isso significa que você estará manipulando as propriedades,
os métodos, os eventos e os campos do componente TClientDataset, e não do componente TData-
set. Pense no componente TDataset como se estivesse em uma aplicação separada e, portanto, não
pudesse ser manipulado diretamente por você no código. Coloque todos os componentes do seu
servidor em um DataModule separado. A colocação dos componentes TDatabase, TDataset e TLocal-
Connection em um DataModule separado efetivamente prepara sua aplicação para uma transição mais
fácil, mais adiante, para uma distribuição multicamadas. Outro benefício de se fazer isso é que pode
ajudá-lo a pensar em DataModule como algo em que o cliente não pode tocar com facilidade. Nova-
mente, essa é uma boa preparação para a sua aplicação (e para o seu próprio raciocínio) quando for
a hora de transportar essa aplicação para uma distribuição em multicamadas.
Erros clássicos
O erro mais comum na criação de uma aplicação multicamadas é a introdução de conhecimento
desnecessário da camada de dados na camada de apresentação. Alguma validação é mais adequada
na camada de apresentação, mas é o modo como essa validação é realizada que determina sua ade-
quação em uma aplicação multicamadas.
Por exemplo, se você estiver passando instruções da SQL dinâmica do cliente para o servidor,
isso gera uma dependência, em que a aplicação cliente sempre deve estar sincronizada com a cama-
da de dados. Fazer as coisas dessa maneira introduz mais partes em movimento que precisam ser co-
ordenadas na aplicação multicamadas geral. Se você mudar uma das estruturas das tabelas na cama-
da de dados, terá que atualizar todas as aplicações cliente que enviam SQL dinâmica, de modo que
agora possam enviar a instrução SQL correta. Isso certamente limita o benefício mantido por uma
aplicação de cliente magro desenvolvida de forma adequada.
Outro exemplo de um erro clássico é quando a aplicação cliente tenta controlar o tempo de
vida da transação, em vez de permitir que a camada comercial cuide disso em favor do cliente. Qua-
se sempre, isso é implementado pela exposição de três métodos da instância de TDataBase no servi-
dor – BeginTransaction( ), Commit( ) e Rollback( ) – e pela chamada desses métodos a partir do
cliente. Quando as coisas são feitas dessa forma, o código do cliente se torna muito mais complicado
para se manter e infringe o princípio de que a camada de apresentação deve ser a única camada res-
ponsável pela comunicação com a camada de dados. A camada de apresentação nunca deverá se ba-
sear nessa tática. Em vez disso, você deve enviar suas atualizações para a camada comercial e permi-
tir que essa camada trate da atualização dos dados em uma transação.
149
Questões de licenciamento
O licenciamento tem sido um assunto difícil para muitos pessoas desde que o DataSnap foi introdu-
zido inicialmente no Delphi 3. As inúmeras opções para distribuição dessa tecnologia contribuíram
para essa confusão. Esta seção explica os requisitos gerais de quando você precisa adquirir uma li-
cença do DataSnap. No entanto, o único documento legalmente relacionado ao licenciamento está
em DEPLOY.TXT, localizado no diretório do Delphi 6. Finalmente, para obter a resposta definitiva
para uma situação específica, você precisa entrar em contato com o representante de vendas local
da Borland. Outras orientações e exemplos estão disponíveis em
http://www.borland.com/midas/papers/licensing/
http://www.xapware.com/ddg
As informações desse documento foram preparadas para responder a alguns dos cenários mais
comuns em que o DataSnap é utilizado. As informações sobre preços e opções também estão incluí-
das no documento.
O critério principal para se determinar a necessidade de uma licença do DataSnap para a sua
aplicação é se o pacote de dados DataSnap atravessa um limite de máquina. Se isso acontecer e você
utilizar os componentes DataSnap em ambas as máquinas, então terá que adquirir uma licença. Se
não acontecer (como nos exemplos de uma e duas camadas, apresentados anteriormente), você es-
tará usando a tecnologia DataSnap, mas não será preciso adquirir uma licença para usar o DataSnap
dessa maneira.
Configuração do DCOM
A configuração do DCOM se parece mais com uma arte do que uma ciência. Existem muitos aspec-
tos envolvidos em uma configuração DCOM completa e segura, mas esta seção o ajudará a enten-
der alguns dos fundamentos dessa arte oculta.
Depois de registrar seu servidor de aplicação, seu objeto servidor estará disponível para perso-
nalização no utilitário DCOMCNFG da Microsoft. Esse utilitário está incluído nos sistemas NT au-
tomaticamente, mas é um download separado para máquinas Win9x. Como um lembrete, existem
muitos bugs no DCOMCNFG; o principal é que o DCOMCNFG só pode ser executado em máqui-
nas Wi9x que estejam com o compartilhamento em nível de usuário ativado. Isso, naturalmente,
exige um domínio. Isso nem sempre é possível ou desejável em uma rede ponto a ponto, como em
duas máquinas Windows 9x. Isso tem feito com que muitas pessoas pensem incorretamente que é
necessário o uso de uma máquina NT para se trabalhar com DCOM.
Se você puder executar o DCOMCNFG, poderá selecionar o servidor de aplicação registrado
e dar um clique no botão Properties para revelar informações sobre o seu servidor. A página Iden-
tity (identidade) é um bom lugar para iniciarmos nosso passeio pelo DCOMCNFG. O valor default
para um objeto servidor registrado é Launching User (usuário que inicia). A Microsoft não poderia
ter tomado uma decisão pior para o default, se tentasse.
Quando o DCOM cria o servidor, ele usa o contexto de segurança do usuário especificado na
página Identity. O usuário que inicia gerará um novo processo do objeto servidor para todo e qual-
quer login de usuário distinto. Muitas pessoas vêem o fato de que selecionaram o modo de instancia-
ção ciMultiple e se perguntam por que várias cópias de seu servidor estão sendo criadas. Por exem-
plo, se o usuário A se conecta ao servidor e depois o usuário B se conecta, o DCOM gerará um
processo inteiramente novo para o usuário B. Além disso, você não verá a parte GUI do servidor
para usuários que se conectam sob uma conta diferente da que está atualmente em uso na máquina
servidora. Isso acontece por causa de um conceito do NT conhecido como estações Windows. A
única estação Windows capaz de escrever na tela é a Interactive User (usuário interativo), que é o
150 usuário atualmente conectado na máquina servidora. Além do mais, estações Windows são um re-
curso escasso, e você pode não conseguir executar muitos processos do servidor se usar essa confi-
guração. Resumindo, nunca use a opção Launching User como sua identidade para o seu servidor.
A próxima opção interessante nessa página é Interactive User, que significa que cada cliente
que cria um servidor fará isso sob o contexto do usuário que está conectado ao servidor naquele mo-
mento. Isso também permitirá que você tenha interação visual com o seu servidor de aplicação.
Infelizmente, a maior parte dos administradores de sistemas não permite que um login aberto fique
ocioso em uma máquina NT. Além disso, se o usuário conectado decidir sair, o servidor de aplica-
ção não funcionará mais conforme desejado.
Para esta discussão, isso nos deixa com a última opção na página Identity: This User (este usuá-
rio). Usando essa opção, todos os clientes criarão um servidor de aplicação e usarão as credenciais
de login e o contexto do usuário especificado na página Identity. Isso também significa que a máqui-
na NT não exige que um usuário esteja conectado para usar o servidor de aplicação. A única desvan-
tagem dessa técnica é que não haverá uma tela GUI do servidor quando a opção é utilizada. No en-
tanto, de longe essa é a melhor de todas as opções disponíveis para colocar seu servidor de aplicação
em produção.
Depois que o objeto servidor for configurado corretamente com a identidade certa, você pre-
cisará voltar sua atenção para a guia Security (segurança). Certifique-se de que o usuário que estará
usando esse objeto possui privilégios apropriados. Não se esqueça também de conceder o acesso de
usuário SYSTEM ao servidor; caso contrário, você encontrará erros durante o processo.
Muitas nuances sutis estão espalhadas pelo processo de configuração do DCOM. Para ver o
que há de mais recente sobre problemas de configuração do DCOM, especialmente com relação ao
Windows 9x, Delphi e DataSnap, visite a página DCOM em nosso site Web, em
http://www.DistribuCon.com/dcom95.htm
Arquivos a distribuir
Os requisitos para a distribuição de uma aplicação DataSnap mudaram a cada nova versão do Del-
phi. O Delphi 6 facilita a distribuição, mais do que qualquer outra versão.
Com o Delphi 6, os arquivos mínimos necessários para distribuição da sua aplicação DataSnap
aparecem nas listas a seguir.
Aqui estão as etapas para o servidor (essas etapas consideram um servidor COM; elas diferirão
ligeiramente para outras variedades):
1. Copie o servidor de aplicação para um diretório com privilégios suficientes no NTFS ou
privilégios em nível de compartilhamento definidos corretamente, se estiver em uma má-
quina Win9x.
2. Instale sua camada de acesso a dados para permitir que o servidor de aplicação atue como
cliente para o RDBMS (por exemplo, BDE, MDAC, bibliotecas de banco de dados especí-
ficas no cliente e assim por diante).
3. Copie MIDAS.DLL para o diretório %SYSTEM%. Por default, esse seria C:\Winnt\System32 para
máquinas NT e C:\Windows\System para máquinas 9x.
4. Execute o servidor de aplicação uma vez para registrá-lo no COM.
Aqui estão as etapas para o cliente:
1. Copie o cliente para um diretório, junto com quaisquer outros arquivos de dependência
usados pelo seu cliente (por exemplo, pacotes de runtime, DLLs, controles ActiveX e assim
por diante).
2. Copie MIDAS.DLL para o diretório %SYSTEM%. Observe que o Delphi 6 pode vincular estatica-
mente o MIDAS.DLL à sua aplicação, tornando essa etapa desnecessária. Para fazer isso, basta
incluir a unidade MidasLib em sua cláusula uses e montar sua aplicação novamente. Você
verá um aumento no tamanho do arquivo EXE, devido ao vínculo estático. 151
3. Opcional: se você especificar a propriedade ServerName na sua TDispatchConnection ou se em-
pregar a vinculação inicial no seu cliente, terá que registrar o arquivo da biblioteca de tipos do
servidor (TLB). Isso pode ser feito usando um utilitário como <DELPHI>\BIN\TREGSVR.EXE (ou
programaticamente, se você optar por isso).
Essa chamada diz ao DataSnap que 16 objetos estarão disponíveis no pool, e que o DataSnap pode
liberar quaisquer instâncias de objetos que tiverem sido criadas se não houver atividade por 30 minutos.
Se você nunca quiser liberar os objetos, então poderá passar 0 como parâmetro de tempo limite.
O cliente não é muito diferente disso. Basta usar uma TWebConnection como TDispatchConnec-
tion para o cliente e preencher as propriedades de forma adequada, e o cliente estará se comunican-
do com o servidor de aplicação através do HTTP. A única grande diferença quando se usa TWebCon-
nection é a necessidade de especificar o URL completo para o arquivo httpsrvr.dll, em vez de
simplesmente identificar o componente servidor por nome ou por endereço. A Figura 21.11 mostra
152 uma captura de tela de uma configuração típica, usando TWebConnection.
Figura 21.11 Configuração de TWebConnection durante o projeto.
Outro benefício do uso do HTTP para o seu transporte é que um sistema operacional como o
NT Enterprise permite o uso de clusters de servidores. Isso oferece balanceamento de carga e tole-
rância a falhas automatizados para o seu servidor de aplicação. Para obter mais informações sobre o
clustering, consulte http://www.microsoft.com/ntserver/ntserverenterprise/exec/overview/clus-
tering.
As limitações do uso de TWebConnection são bastante triviais, e compensam qualquer concessão
para que haja mais clientes capazes de atingir seu servidor de aplicação. As limitações são que você
precisa instalar wininet.dll no cliente e nenhuma callback está disponível quando se usa TWebCon-
nection.
Resumo
Este capítulo fornece muitas informações sobre DataSnap. Mesmo assim, ele é apenas uma introdu-
ção ao que pode ser feito com essa tecnologia – algo muito além do escopo de um único capítulo.
Mesmo depois que você explorar todos os detalhes internos do DataSnap, ainda poderá aumentar
seu conhecimento e suas capacidades usando DataSnap com C++Builder e JBuilder. Usando JBuil-
der, você pode atingir o máximo em acesso de múltiplas plataformas a um servidor de aplicação, en-
quanto usa a mesma tecnologia e conceitos aprendidos aqui.
O DataSnap é uma tecnologia em rápida evolução, que traz a promessa de aplicações multica-
madas a cada programador. Quando você experimentar o verdadeiro poder da criação de uma apli-
cação com DataSnap, pode nunca retornar ao desenvolvimento de aplicações de banco de dados
conforme o conhece atualmente.
153
Criando aplicações CAPÍTULO
WebSnap
Por Nick Hodges
23
NESTE CAPÍTULO
l Características do WebSnap
l Criando uma aplicação WebSnap
l Tópicos avançados
O Delphi 6 introduz uma nova estrutura de aplicação Windows, chamada WebSnap, que traz os
pontos fortes do Rapid Application Development (RAD) para o desenvolvimento na Web. Baseado
no WebBroker e no InternetExpress, WebSnap é um grande salto adiante para os desenvolvedores
Delphi que desejam usar sua ferramenta favorita para criar aplicações Web. Ele oferece todos os
apetrechos padrão para as aplicações Web, incluindo gerenciamento de sessão, login de usuário,
acompanhamento de preferências do usuário e scripting. Naturalmente, o Delphi 6 leva o RAD
para o desenvolvimento de servidor Web, tornando fácil e rápida a criação de aplicações Web ro-
bustas, dinâmicas e baseadas em banco de dados.
Características do WebSnap
WebSnap não é uma tecnologia totalmente nova, e não despreza suas aplicações WebBroker e
InternetExpress. WebSnap é compatível com essas duas tecnologias mais antigas, e a integração do
seu código existente a uma nova aplicação WebSnap é um processo relativamente simples. O Web-
Snap oferece várias características, listadas nas próximas seções.
Scripting no servidor
O WebSnap integra de forma transparente o script no servidor às suas aplicações, permitindo-lhe
criar facilmente objetos poderosos que aceitam script, os quais você pode usar para criar e persona-
lizar suas aplicações e a HTML. O componente TAdapter e todos os seus descendentes são compo-
nentes que aceitam script, o que significa que podem ser chamados por seu script no servidor e pro-
duzir HTML e JavaScript no cliente para as suas aplicações.
Componentes TAdapter
Os componentes TAdapter definem uma interface entre uma aplicação e o scripting no servidor. O
script no servidor só tem acesso à sua aplicação por meio de adaptadores, garantindo que o script
não mude inadvertidamente os dados em uma aplicação ou exponha funções que não sejam volta-
das para consumo público. Você pode criar descendentes personalizados de TAdapter, que geren-
ciam o conteúdo para as suas necessidades específicas, e esse conteúdo pode até mesmo ser visível e
configurável durante o projeto. TAdapters podem manter dados e executar ações. Por exemplo, o
TDataSetAdapter pode exibir registros de um dataset, bem como tomar as ações normais em um da-
taset, como rolar, adicionar, atualizar e excluir.
Gerenciamento de sessão
As aplicações WebSnap contêm gerenciamento de sessão interno e automático; agora você pode
acompanhar as ações do usuário entre os diversos pedidos HTTP. Como o HTTP é um protocolo
sem estado, suas aplicações Web precisam registrar os usuários deixando algo no cliente que identi-
fique cada usuário. Normalmente, isso é feito com cookies, referências de URL ou controles de
campo oculto. O WebSnap oferece suporte transparente para sessão, facilitando bastante o acom-
panhamento de usuários. O WebSnap faz isso por meio do seu componente SessionsService. O
componente SessionsService mantém, de forma transparente, um valor de identificação de sessão
para cada usuário, simplificando bastante a tarefa de registrar cada usuário enquanto realiza pedi-
dos individuais. Isso normalmente é um serviço difícil de se controlar, mas o WebSnap cuida de to-
dos os detalhes e disponibiliza a informação de sessão tanto para o script no servidor quanto para o
próprio código da aplicação Web.
Serviços de login
Suas aplicações Web provavelmente terão que implementar alguma segurança, exigindo que os
usuários se registrem em determinada aplicação. O WebSnap automatiza esse processo, oferecendo
um componente adaptador de login especializado. Esse componente contém as funções necessárias
para consultar e autenticar os usuários corretamente, de acordo com o modelo de segurança esco-
lhido pela aplicação. Ele reúne informações de login e, em conjunto com o gerenciamento de sessão
do WebSnap, oferece as credenciais atuais do login para cada pedido. Os componentes de login
também automatizam a validação do login e a expiração do login. Por toda a sua aplicação, os usuá-
rios que tentarem acessar páginas não-autorizadas podem ser levados automaticamente para a pági-
na de login.
Acompanhamento do usuário
A função mais comum que o rastreamento de sessão oferece é a capacidade de acompanhar seus
usuários e suas preferências para a sua aplicação. O WebSnap oferece componentes que permitem
acompanhar informações do usuário com facilidade e exibi-las no seu site. Você pode armazenar as
informações de login do usuário e depois apanhar as informações do usuário com base nisso. Você
pode manter direitos de acesso do usuário e preferências do site, além de outras coisas, como infor-
mações do carrinho de compras.
Gerenciamento de HTML
Normalmente, em uma aplicação Web dinâmica, o acompanhamento e o gerenciamento da HTML
pode ser difícil. O conteúdo HTML pode residir em diversos lugares, como em arquivos e recursos,
ou então pode ser gerado dinamicamente. O WebSnap oferece um meio para que você gerencie esse
processo com seus serviços de localização de arquivo.
156
Serviços de uploading de arquivo
O gerenciamento do uploading de arquivos normalmente requer muito código personalizado. O
WebSnap oferece uma solução adaptadora simples, que gerencia os formulários em múltiplas par-
tes necessários para o upload de arquivos. Você pode oferecer a capacidade de uploading de arqui-
vo na sua aplicação WebSnap de modo rápido e fácil, usando a funcionalidade interna do compo-
nente TAdapter.
Projetando a aplicação
Primeiro, você desejará incluir uma barra de ferramentas WebSnap no IDE; portanto, dê um clique
com o botão direito na área de botões da barra de título do IDE e selecione a barra de ferramentas
Internet (veja a Figura 23.1). Isso acrescenta uma barra de ferramentas à janela principal do IDE, fa-
cilitando a criação de aplicações WebSnap e a inclusão de formulários e módulos Web.
Em seguida, dê um clique no botão com a mão segurando o globo, e você verá a caixa de diálo-
go que aparece na Figura 23.2.
A caixa de diálogo da Figura 23.2 oferece uma série de opções para configurar sua aplicação
WebSnap. A primeira é o tipo de servidor em que sua aplicação será executada. Você tem cinco
opções:
l ISAPI/NSAPI Dynamic Link Library – Essa opção produz um projeto que roda sob IIS (ou
sob servidores Netscape, se o adaptador ISAPI correto for instalado). O projeto produz
uma DLL quando compilado e roda no mesmo espaço de memória do servidor Web. O ser-
vidor Web mais comum para rodar aplicações ISAPI é o Internet Information Server da Mi-
crosoft, embora outros servidores Web possam rodar DLLs ISAPI.
l CGI Standalone executable – Essa opção cria um projeto que produz um executável pelo
console, que lê e escreve nas portas de entrada e saída padrão. Ela se ajusta à especificação
CGI. Quase todos os servidores Web aceitam CGI. 157
l Win-CGI Standalone executable – Essa opção produz um projeto Win-CGI que se comuni-
ca com um servidor Web por meio de arquivos INI baseados em texto. Win-CGI é muito
rara e não é recomendada.
l Apache Shared Module (DLL) – Essa opção produz um projeto que rodará no servidor
Web Apache. Para obter mais informações sobre o Apache, consulte http://www.apa-
che.org.
l Web App Debugger Executable – Se você selecionar essa opção, obterá uma aplicação que
será executada no Web App Debugger do Delphi (veja a Figura 23.3). Sua aplicação Web será
um servidor COM out-of-process, e o Web App Debugger controlará a execução da aplica-
ção. Esse tipo de aplicação Web permitirá usar o poder completo do depurador do Delphi
para depurá-lo. Isso significa que não há mais disputa com os servidores Web, ligando-os e
desligando-os para carregar e descarregar suas aplicações. Em vez disso, a depuração de sua
aplicação será rápida e fácil.
NOTA
O Web App Debugger pode ser acessado por meio do menu Tools no IDE. Para que funcione corre-
tamente, você precisa registrar a aplicação encontrada no diretório <Delphi Dir>\bin, chamada
serverinfo.exe. Tudo o que você precisa fazer para registrá-la é executá-la uma vez, e ela mesmo
se registrará. O Web App Debugger é uma aplicação baseada em COM, que atua como um servi-
dor Web para testar suas aplicações. Quando você cria uma aplicação para o Web App Debugger,
seu novo projeto terá um formulário e um módulo Web. O formulário atua como um marcador de
lugar para o servidor COM, e a execução da aplicação uma vez a registrará. Depois disso, o Web
App Debugger a controlará por meio do browser Web, e servirá à sua aplicação no browser. Como
a aplicação é um executável do Delphi, e não uma extensão do servidor Web, você pode definir um
ponto de interrupção nela e executá-la no IDE do Delphi. Depois, quando você a acessar por meio
do browser, o depurador do Delphi assumirá o comando quando seus pontos de interrupção forem
alcançados, e você poderá depurar a aplicação normalmente. Para acessar sua aplicação por meio
do browser, execute o Web App Debugger e dê um clique no hyperlink intitulado Default URL. Isso
trará uma aplicação Web que lista todas as aplicações registradas com o servidor. Você pode, en-
tão, selecionar sua aplicação e executá-la. A opção View Details (exibir detalhes) permitirá ver mais
informações sobre as diferentes aplicações, removendo-as do Registro quando não forem mais ne-
cessárias. No entanto, tenha cuidado para não excluir a aplicação ServerInfo; caso contrário,
você terá que retornar e registrá-la novamente.
158
Para a aplicação de exemplo que você criará aqui, selecione a opção Web App Debugger. Isso
permitirá depurar a aplicação enquanto você a cria.
A próxima opção no assistente permite selecionar o tipo de módulo que você deseja e os dife-
rentes componentes que serão incluídos. Se você escolher a opção Page Module (módulo de pági-
na), receberá um módulo Web que representa uma página na sua aplicação. Se você escolher a op-
ção Data Module, receberá um módulo de dados que pode ser usado em uma aplicação WebSnap.
Ele pode realizar a mesma função dos módulos de dados nas aplicações tradicionais cliente/servi-
dor. Para essa aplicação, selecione a opção Page Module.
Em seguida, dê um clique no botão Components, e você verá a caixa de diálogo mostrada na
Figura 23.4.
Figura 23.4 A caixa de diálogo Web App Components permite que você selecione os
componentes que serão incluídos em seu novo módulo.
159
l Sessions Service – Esse componente gerencia sessões para os usuários, permitindo-lhe man-
ter o estado para usuários individuais entre os pedidos HTTP. Sessions Service pode arma-
zenar informações sobre usuários e expirar automaticamente suas sessões depois de um
certo período de inatividade. Você pode acrescentar qualquer informação específica da
sessão que desejar na propriedade Session.Values, um array de variantes indexado por
string. Por default, as sessões são gerenciadas com cookies na máquina do usuário, embora
você pudesse criar uma classe para lidar com elas de alguma outra maneira, como com
URLs gordos ou campos ocultos.
l User List Service – Esse componente mantém uma lista de usuários que estão autorizados a
se registrar na aplicação e informações sobre eles.
NOTA
Cada uma dessas opções possui caixas suspensas que permitem escolher o componente que aten-
derá a cada um dos papéis indicados. Você pode criar seus próprios componentes, que atenderão a
esses papéis e os registrarão com o WebSnap. Com isso, eles aparecerão como opções nessa caixa
de diálogo. Por exemplo, você poderia criar um componente de sessão que mantenha informações
de sessão em um URL gordo, em vez de usar cookies.
Para este exemplo, marque todas as caixas de seleção. Depois, para o componente End User
Adapter, desça a caixa de combinação e selecione TEndUserSessionAdapter. Esse componente associa-
rá automaticamente um ID de sessão a um usuário final. Depois, dê um clique em OK.
A próxima opção no assistente é o nome da página. Chame essa página principal de Home e de-
pois dê um clique em Page Options (opções de página). Você verá a caixa de diálogo que aparece na
Figura 23.5.
Figura 23.5 A caixa de diálogo Application Module Page Options permite selecionar
as opções para a página no seu módulo Web.
Essa caixa de diálogo permite personalizar o componente PageProducer do seu módulo Web e
a HTML associada a ele. Ela apresenta diversas opções. A primeira é o tipo de produtor de página.
O WebSnap inclui uma série de PageProducers padrão, que podem produzir e gerenciar HTML de
várias maneiras. Para começar, selecione a opção default, um PageProducer simples. Você também
pode selecionar o tipo de scripting no servidor que deseja utilizar. O Delphi já vem com suporte
para JScript e VBScript (além de XML/XSL, que serão discutidos mais adiante.) Aqui, deixe o valor
default JScript.
Cada módulo possui uma página HTML associada a ele. A próxima opção permite selecionar
que tipo de HTML você deseja. Por default, o Delphi oferece uma página Standard com um menu
de navegação simples, ativado por script. Você pode criar seus próprios modelos HTML, regis-
trá-los com o WebSnap e depois selecioná-los aqui. Veremos como fazer isso mais adiante, neste ca-
pítulo. Por enquanto, mantenha o valor default, Standard.
160
Chame a página de Home (o Title é preenchido automaticamente com o mesmo nome). Certifi-
que-se de que Published (publicado) esteja marcado e mantenha Login Required (exige login) des-
marcado. Uma página publicada aparecerá na lisa de páginas da aplicação e poderá ser referenciada
pelo objeto de scripting Pages. Isso é necessário para a criação de menus baseados em página no
script, usando o objeto de scripting Pages.
Depois que você tiver feito isso, dê um clique em OK e depois novamente em OK no assistente
principal. O assistente, em seguida, criará sua aplicação para você, e o novo módulo Web ficará se-
melhante ao que aparece na Figura 23.6.
Ainda não discutimos sobre o controle TWebAppComponents. Esse controle é a carteira de compen-
sação central para todos os outros componentes. Como muitos dos componentes em uma aplicação
WebSnap trabalham em conjunto, o componente WebAppComponents é aquele que os une e permite que
se comuniquem e façam referência um ao outro. Suas propriedades consistem simplesmente em ou-
tros componentes que atendem aos papéis específicos discutidos anteriormente.
Nesse ponto, você deverá salvar o projeto. Para manter coerência com o restante do capítulo,
chame o módulo Web (unit2) de wmHome, chame o formulário (Unit1) de ServerForm e chame o
projeto em si de DDG6Demo.
Examinando o Code Editor, você deverá ver alguns recursos novos e desconhecidos. Primei-
ro, observe as guias na parte inferior. Cada módulo Web – por representar uma página em uma apli-
cação Web – possui um arquivo HTML associado, que pode conter script no servidor. A segunda
guia na parte inferior mostra essa página (veja a Figura 23.7). Como você selecionou o modelo de
página HTML Standard no assistente, a HTML possui script no servidor, que saudará o usuário se
ele for conectado, e oferecerá um menu de navegação básico montado automaticamente com base
em todas as páginas publicadas da aplicação. À medida que as páginas são acrescentadas nessa apli-
cação de demonstração, esse menu crescerá e permitirá que os usuários naveguem para cada uma
das páginas. O código HTML default aparece na Listagem 23.1.
161
Figura 23.7 A página HTML associada ao módulo Web.
<html>
<head>
<title>
<%= Page.Title %>
</title>
</head>
<body>
<h1><%= Application.Title %></h1>
c++
}
}
if (c>1) Response.Write(s)
%>
</td>
</table>
</body>
</html>
Isso faz com o que o objeto de scripting Application apresente o valor para ApplicationTitle
na HTML.
Em seguida, vá para o Code Editor e selecione a página HTML para o Home Module. Depois
mova o cursor para baixo da tag </table>, próximo do final, e inclua uma descrição resumida da pá-
gina, saudando o usuário. O código neste CD-ROM possui tal entrada, acrescentando o seguinte:
<P>
<FONT SIZE="+1" COLOR="Red">Welcome to the Delphi 6 Developers Guide WebSnap
åDemonstration Application!</FONT>
<P>
This application will demonstrate many of the new features in Delphi 6 and
åWebSnap. Feel free to browse around and look at the code involved. There is
åa lot of power, and thus a lot to learn, in WebSnap, so take your time and
ådon't try to absorb it all at once.
<P>
Esse novo código, naturalmente, também aparece imediatamente no painel HTML Preview.
Em seguida, só para provar que você está realmente criando uma aplicação para o browser,
execute o projeto. A primeira coisa que você verá é um formulário em branco. Esse é o servidor
COM. Você pode fechá-lo e depois iniciar o Web App Debugger no menu Tools. Depois de fazer 163
isso, dê um clique no hyperlink Default URL (ele será chamado DDG6DemoApp.DDG6TestApp), localize a
aplicação na caixa de listagem no seu browser e dê um clique no botão Go. Seu browser deverá mos-
trar sua página conforme ilustrada na Figura 23.8.
Esse código simplesmente percorre o objeto de scripting Pages, montando um menu de nomes
de página. O código cria um link se a página encontrada não for a página atual. Assim, a página atual
não é um link, e todos os outros nomes de página são, não importa qual seja a página atual. Esse é
um menu bastante simples e, naturalmente, você poderia escrever seus próprios menus mais sofisti-
cados para a sua aplicação personalizada.
NOTA
Se você alternar entre as duas páginas, poderá observar que o formulário da aplicação pisca em se-
gundo plano toda vez que um pedido for feito. Isso porque o Web App Debugger está chamando a
aplicação como um objeto COM para cada pedido, executando a aplicação, apanhando a respos-
ta HTTP e fechando a aplicação.
Em seguida, você pode tornar parte da aplicação restrita apenas a usuários que efetuaram o lo-
gin. Primeiro, inclua uma página que exija o login de um usuário para que ele possa vê-la. Dê um cli-
que no botão New WebSnap Page (nova página WebSnap) na barra de ferramentas Internet e chame a
página de “LoggedIn”. Depois, selecione a caixa de seleção Login Required. Dê um clique em OK
para criar uma página Web que só possa ser vista por um usuário que efetuou login. Salve a página
como wmLoggedIn.pas. Depois, inclua algum código HTML na página, permitindo que o usuário saiba
que somente os usuários com login poderão ver a página. A aplicação neste CD-ROM inclui o seguinte:
<P>
<FONT COLOR= ”Green ”><B>Congratulations!</B></FONT>
<BR>
You are successfully logged in!Only logged in users are granted access
å.to this page.All others are sent back to the Login page.
<P>
<P>
<FONT COLOR="Green"><B>Congratulations! </B></FONT> 165
<BR>
You are successfully logged in! Only logged in users are granted access
åto this page. All others are sent back to the Login page.
<P>
A única diferença entre a página LoggedIn e a página Simple é um parâmetro no código que re-
gistra a página com o gerenciador de aplicação. Toda página WebSnap possui uma seção de iniciali-
zação que se parece com isto:
initialization
if WebRequestHandler < > nil then
WebRequestHandler.AddWebModuleFactory(TWebPageModuleFactory.Create(TLoggedIn,
TWebPageInfo.Create([wpPublished, wpLoginRequired], '.html'),
åcrOnDemand, caCache));
Esse código registra a página com o objeto WebRequestHandler, que gerencia todas as páginas e
oferece seu conteúdo HTML quando for preciso. WebRequestHandler sabe como criar, colocar em
cache e destruir instâncias de módulos Web quando for preciso. O código anterior é o código para a
página LoggedIn, e possui o parâmetro wpLoginRequired, dizendo à página que somente os usuários
que efetuaram login podem ter acesso a ela. Por default, o New Page Wizard acrescenta esse valor,
mas o transforma em comentário. Se você quiser que a página fique protegida por senha mais tarde,
poderá simplesmente retirar o símbolo de comentário do parâmetro e recompilar a aplicação.
Login
Você precisa criar uma página que permita o login do usuário. Primeiro, no entanto, existe uma ta-
refa de manutenção a ser feita na página Home.
Primeiro, crie uma nova página e dê-lhe o nome Login. Depois, selecione TAdapterPagePro-
ducer para o tipo de produtor de página. Dessa vez, no entanto, não a publique, removendo a sele-
ção da caixa Publish, e, obviamente, não exija que um usuário efetue login para ver a página de lo-
gin! Desmarcando a opção Publish, a página estará disponível para uso, mas não fará parte do
objeto de scripting Pages, e por isso não aparecerá no menu de navegação. Salve-a como wmLo-
gin. Dessa vez, vá para a página WebSnap da Component Palette e inclua um componente TLo-
ginAdapter no módulo.
O TAdapterPageProducer é um PageProducer especializado, que sabe como exibir e tratar dos
campos e controles HTML apropriados para um TAdapter. No caso da aplicação de demonstração,
esse TAdapterPageProducer irá exibir as caixas de edição Username (nome de usuário) e Password
(senha), que o usuário terá que usar para efetuar o login. Quando você começar a entender melhor o
WebSnap, rapidamente desejará usar TAdapterPageProducers em todas as suas páginas, pois eles faci-
litam bastante a exibição de informações de TAdapter, a execução de ações de TAdapter e a criação de
formulários HTML com base em campos TAdapter.
Como o TLoginFormAdapter possui todos os campos necessários para isso, a criação da página
de login será muito fácil, e feita sem qualquer código – é isso mesmo, sem código! Você poderá
acrescentar usuários, criar uma página de login e impor o login sobre páginas especificadas, tudo
sem uma única linha de código.
Primeiro, para gerenciar os logins, você terá que criar alguns usuários. Vá para o módulo Web
Home e dê um clique duplo no componente WebUserList. Esse componente gerencia usuários e se-
nhas. Você pode facilmente acrescentar usuários e suas senhas. Dê um clique no botão New e inclua
dois usuários diferentes. Acrescente quaisquer senhas que você quiser para cada usuário. Os dois
usuários na aplicação de demonstração no CD-ROM são ddg6 e user. Suas senhas são iguais aos
seus nomes de usuário, como mostra a Figura 23.9.
166
Figura 23.9 O editor de componentes para o componente WebUserList,
com dois usuários acrescentados.
E isso é tudo o que você precisa fazer. Execute a aplicação e deixe-a em execução. Agora, como
você está se baseando nas informações de sessão sobre o usuário, precisa deixar a aplicação na me-
mória para que ela se lembre que você efetuou o login. Execute a aplicação no browser e depois ten-
te navegar até a página que exige o login. Ela deverá levá-lo para a página Login. Digite um nome de
usuário e senha válidos, dê um clique no botão Login e você será levado à página que pedirá seu lo-
gin. A partir daí, qualquer página que você especifique como exigindo um login válido só aparecerá
se você tiver efetuado o login corretamente. Caso contrário, ela o levará para a página de login.
Tudo isso acontece sem a escrita de uma única linha de código em Pascal.
Experimente isto também: efetue o logout, selecionando o link Logout, e depois tente efetuar
o login com um nome de usuário ou senha inválida. Observe que aparecerá uma mensagem de erro.
Isso é o componente AdapterErrorList em ação. Ele coletará automaticamente os erros de login e os
mostrará para você.
167
Quando você estiver autenticado na aplicação e navegando por suas páginas, notará que ela se
lembra de quem você é e apresenta seu nome de login no cabeçalho de cada página. Isso é resultado
do seguinte script no servidor, no arquivo HTML para os módulos Web:
168
Listagem 23.2 Tratador OnExecute
Value := FavoriteMovieField.ActionValue;
if Value.ValueCount > 0 then
begin
Session.Values[sFavoriteMovie] := Value.Values[0];
end;
Value := PasswordHintField.ActionValue;
if Value.ValueCount > 0 then
begin
Session.Values[sPasswordHint] := Value.Values[0];
end;
Value := LikesChocolateField.ActionValue;
if Value < > nil then
begin
if Value.ValueCount > 0 then
begin
Session.Values[sLikesChocolate] := Value.Values[0];
end;
end else
begin
Session.Values[sLikesChocolate] := 'false';
end;;
end;
Esse código apanha os valores dos campos de entrada na sua HTML quando o usuário aciona o
botão Submit e coloca os valores na variável de sessão, para serem recuperados pelos AdapterFields.
Naturalmente, você precisa ser capaz de apanhar esses valores depois que estiverem definidos,
de modo que cada campo do adaptador apanhará seu valor de volta do objeto SessionsService. Para
cada campo no adaptador, torne os tratadores de evento OnGetValue semelhantes ao código da Lis-
tagem 23.3.
...
const
sFavoriteMovie = 'FavoriteMovie';
sPasswordHint = 'PasswordHint';
sLikesChocolate = 'LikesChocolate';
sIniFileName = 'DDG6Demo.ini';
...
end;
Em seguida, você precisa ser capaz de exibir controles que realmente apanham os dados do
usuário. Você fará isso por meio de TAdapterPageProducer, assim como fez com a página Login. Pri-
meiro, dê um clique duplo em TAdapterPageProducer; você voltará ao Web Surface Designer mais
uma vez. Crie um novo AdapterForm e inclua um AdapterFieldGroup, além de um AdapterCommand-
Group. Defina a propriedade Adapter do AdapterFieldGroup como PrefAdaper e defina o DisplayCom-
ponent do AdapterCommandGroup como AdapterFieldGroup. Depois, dê um clique com o botão direito
no AdapterFieldGroup e selecione Add All Fields (adicionar todos os campos) no menu. Para cada
um dos campos resultantes, use o Object Inspector para definir a propriedade FieldName com os va-
lores apropriados. Você também pode mudar as propriedades Caption para valores mais amigáveis
do que o default. Depois selecione o AdapterCommandGroup, dê um clique nele com o botão direito e
selecione Add All Commands (adicionar todos os comandos) no menu. Defina a propriedade Acti-
onName do AdapterActionButton resultante como SubmitAction. Finalmente, defina a propriedade
AdapterActionButton.PageName como PreferencesPage. (Essa é a página para onde a ação irá quando
terminar com o processamento da ação. Você criará essa página em um minuto.)
Se algo não for conectado corretamente no Web Surface Designer, você verá uma mensagem
de erro na guia Browser. A mensagem o instruirá sobre as propriedades que precisam ser definidas
para que tudo seja conectado corretamente e para que a HTML seja exibida de modo adequado.
Depois que tudo isso for feito e a HTML estiver correta, a página estará pronta. Agora, se você
executar a aplicação, verá uma página adicional no menu. Se você efetuar o login, poderá ver os
controles de entrada para inserir seus dados de preferência. Não dê um clique no botão Submit,
simplesmente porque ainda não há um lugar para ir.
Em seguida, crie uma página para exibir as preferências do usuário usando a barra de ferra-
mentas e depois chame-a de PreferencesPage. Publique a página e exija que os usuários efetuem o
login para exibi-la. (Novamente, o assistente fará tudo isso por você, como antes.) Salve a nova uni-
dade como wmPreferences.
Depois, vá para a HTML da página e, na área logo abaixo da tabela que mantém o menu de na-
vegação, inclua o seguinte script:
<P>
<B>Favorite Movie: <%= Modules.PreferenceInput.PrefAdapter.FavoriteMovieField.
åValue %>
<BR>
Password Hint: <%= Modules.PreferenceInput.PrefAdapter.PasswordHintField.
åValue %>
<BR>
<% s = ''
if (Modules.PreferenceInput.PrefAdapter.LikesChocolateField.Value)
s = 'You like Chocolate'
170 else
s = 'You do not like chocolate'
Response.Write(s);
%>
Agora, quando você compilar e executar a aplicação, poderá digitar suas preferências e dar um
clique no botão Submit – a aplicação se lembrará e mostrará suas preferências na página Preferen-
ces. Você também pode acessar esses valores no script para qualquer outra página, uma vez defini-
dos. Os valores são mantidos entre os pedidos HTTP pelo objeto Session e apanhados do compo-
nente Adapter por meio do script.
NOTA
Cada uma das páginas em uma aplicação WebSnap possui um arquivo HTML associado, como
você viu. Como esses arquivos existem fora da aplicação, você pode editá-los, salvar as alterações,
atualizar a página no seu browser e ver os resultados sem recompilar sua aplicação. Isso significa
que você mesmo pode atualizar a página sem ter que parar seu servidor Web. Você também pode
experimentar facilmente seu script no servidor durante o desenvolvimento, sem ter que recompilar
sua aplicação. Mais adiante, neste capítulo, você verá meios alternativos de armazenar e apanhar sua
HTML.
Lock.BeginRead;
try
IniFile := TIniFile.Create(IniFileName);
try
Session.Values[sFavoriteMovie] := IniFile.ReadString(TempName,
åsFavoriteMovie, '');
Session.Values[sPasswordHint] := IniFile.ReadString(TempName,
åsPasswordHint, '');
Session.Values[sLikesChocolate] := IniFile.ReadString(TempName,
åsLikesChocolate, 'false');
finally
IniFile.Free;
end;
finally 171
Listagem 23.4 Continuação
Lock.EndRead;
end;
end;
Esses tratadores de evento armazenam os dados em um arquivo INI, mas não há motivo para
que você não armazene os dados em um banco de dados ou em qualquer outro método de armaze-
namento persistente.
A variável Lock é uma variável global do tipo TMultiReadExclusiveWriteSynchronizer, e é criada
na seção de inicialização da página Home. Como várias sessões poderiam estar lendo e gravando no
arquivo INI, esse componente torna a leitura e escrita no arquivo INI à prova de threads. Inclua a
declaração a seguir na parte de interface da sua unidade wmHome:
var
Lock: TMultiReadExclusiveWriteSynchronizer;
Esse código também utiliza uma função chamada IniFileName, que é declarada da seguinte forma:
const
sIniFileName ='DDG6Demo.ini';
...
172
function IniFileName: string;
begin
Result := ExtractFilePath(GetModuleName(HInstance)) + sIniFileName;
end;
Inclua isso em sua unidade wmHome e você deverá ter uma aplicação Web totalmente funcionan-
do, que efetua login de usuários e mantém suas preferências, até mesmo entre diferentes sessões.
Tratamento de imagens
Praticamente toda aplicação Web apresenta gráficos. Os gráficos podem melhorar a atratividade e a
funcionalidade da sua aplicação. Naturalmente, o WebSnap torna a inclusão de imagens e gráficos
nas suas aplicações tão fácil quanto, bem, tudo o mais o que o WebSnap faz. Como você poderia es-
perar, o WebSnap permitirá usar gráficos e imagens de qualquer origem que você preferir – arqui-
vos, recursos, streams de banco de dados e assim por diante. Se os dados da sua imagem puderem ser
colocados em um stream, eles podem ser usados em uma aplicação WebSnap.
Use a barra de ferramentas Internet para incluir outra página na sua aplicação. Use um TAdap-
terPageProducer, publique a página e exija que os usuários efetuem login para obter acesso a ele. Em
seguida, chame a página de Images e salve a unidade resultante como wmImages. Depois que isso for
feito, vá para o módulo Web Imagens, inclua um TAdapter no módulo e dê-lhe o nome ImageAdap-
ter. Finalmente, dê um clique duplo em ImageAdapter e inclua dois campos do tipo TAdapterImage-
Field. Cada um desses mostrará um modo diferente de exibir imagens.
Primeiro, você pode exibir uma imagem com base em um URL. Destaque o primeiro Adaper-
ImageField e defina a propriedade HREF como um URL totalmente qualificado, que aponta para uma
imagem no seu sistema ou em qualquer lugar na Internet, pelo mesmo motivo. Por exemplo, se você
quiser ver o histórico de um ano do preço das ações da Borland, defina a propriedade HREF como
http://chart.yahoo.com/c/1y/b/borl.gif.
Dê um clique duplo no TAdapterPageProducer, no módulo Web Images, inclua um AdapterForm
e depois inclua um AdapterFieldGroup neste. Defina a propriedade adaptadora desse novo Adapter-
FieldGroup para o ImageAdapter. Depois, dê um clique com o botão direito novamente no Adap-
terFieldGroup e selecione Add All Fields. Em seguida, defina o campo ReadOnly do AdapterImage-
Field como True. Se essa propriedade for True, ela mostrará a imagem na sua página. Se for definida
como False, ela dará uma caixa de edição e um botão para pesquisar um nome de arquivo. Obvia-
mente, para ver as imagens, você definiria essa propriedade como True. Quando você olhar para a
imagem pela primeira vez, notará que a imagem possui uma pequena e incômoda legenda. Normal-
mente, você não desejará isso, de modo que, para livrar-se dela, defina a propriedade Caption como
um único espaço. (Observe que uma legenda em branco não é aceita.) Você deverá então ver o gráfi-
co aparecendo no Web Surface Designer, como mostra a Figura 23.12.
Figura 23.12 O Web Surface Designer com um gráfico vindo de ImageAdapterField. 173
NOTA
Se você quiser que as imagens sejam referenciadas por links relativos, para aparecerem durante o
projeto, terá que incluir o diretório onde elas residem em Search Path (caminho de pesquisa) na pá-
gina Options do Web App Debugger.
Agora você pode exibir imagens com base em um URL. Outras vezes, no entanto, você pode
querer apanhar uma imagem de um stream. O componente AdapterImageField também oferece su-
porte para isso. Selecione o segundo AdapterImageField no seu ImageAdapter e abra o Object Inspec-
tor. Vá para a página de eventos e dê um clique duplo em OnGetImage. Coloque uma imagem JPG no
mesmo diretório da aplicação (a demonstração neste CD-ROM usa athena.jpg) e faça com que seu
tratador de evento fique semelhante a este:
Esse código é bastante simples – Image é uma variável de stream que você cria e preenche com
uma imagem. Naturalmente, a aplicação precisa saber que tipo de imagem está apanhando, de
modo que você pode retornar essa informação no parâmetro MimeType. Um TFileStream é uma solu-
ção simples, mas poderia apanhar a imagem de qualquer origem, como um BlobStream de um banco
de dados, ou criar a imagem em plena execução e retorná-la em um stream da memória. Agora,
quando você executar a aplicação, deverá ver o JPG que escolheu logo abaixo do gráfico de ações.
Exibindo dados
Logicamente, você deseja que sua aplicação faça mais do que as coisas simples que ela faz até aqui.
Você certamente deseja ser capaz de exibir dados a partir de um banco de dados, tanto em formato
tabular quanto registro por registro. Naturalmente, o WebSnap facilita isso, e você pode criar apli-
cações de banco de dados poderosas com somente uma pequena quantidade de código. Usando o
TDatasetAdapter e seus campos e ações internas, você pode facilmente exibir dados, além de fazer
acréscimos, atualizações e exclusões em qualquer banco de dados.
Na realidade, exibir um dataset em um formulário é muito fácil. Inclua uma nova unidade na
sua aplicação de demonstração – mas, dessa vez, torne-a um WebDataModule, usando o terceiro
botão na barra de ferramentas Internet. Esse assistente é simples; portanto, simplesmente aceite as
opções default. Depois inclua um TDatasetAdapter a partir da guia WebSnap na Component Palette
e uma TTable a partir da guia BDE. Aponte a TTable para o banco de dados DBDemos e depois para a
tabela BioLife. Depois defina a propriedade Dataset de DatasetAdapter1 como Table1. Finalmente,
defina Table1.Active como True para abrir a tabela. Chame o WebDataModule de BioLife data e
salve a unidade como wdmBioLife.
NOTA
Sua aplicação está usando uma tabela do Paradox simples, baseada no BDE, mas o componente
TDatasetAdapter exibirá dados a partir de qualquer descendente de TDataset. Observe, também,
que não é realmente uma boa idéia usar uma TTable em uma aplicação Web sem suporte explícito
para sessão. A aplicação de demonstração faz isso simplesmente para facilitar o uso e manter sua
atenção nos recursos do WebSnap, e não nos dados.
174
Depois, como uma mudança de rumo, use a Object Treeview para definir as propriedades dos
componentes. Se a Object Treeview não estiver visível, selecione View, Object Treeview no menu
principal. Selecione o DatasetAdapter, dê um clique com o botão direito no nó Actions e selecione
Add All Actions (adicionar todas as ações). Depois, conecte a TTable ao TAdapterDataset por meio
de sua propriedade Dataset. Selecione o nó Fields e dê um clique com o botão direito, selecionando
Add All Fields. Faça o mesmo para a TTable, incluindo todos os campos do dataset ao WebDataMo-
dule. Depois, como o WebSnap cria servidores sem estado para operações de banco de dados, você
precisa indicar uma chave primária para o dataset, para permitir a navegação solicitada pelo cliente
e a manipulação de dados. O WebSnap fará tudo isso por você automaticamente, depois que você
especificar a chave primária. Faça isso selecionando o campo Species_No Field na Object Treeview
e incluindo o valor pfInKey em sua propriedade ProviderFlags.
Em seguida, inclua uma página normal na aplicação. Torne essa página Login Required,
dê-lhe um TAdapterPageProducer e chame a página de Biolife. Salve a unidade como wmBioLife.
Como você deseja exibir os dados nessa nova página, inclua o nome de unidade wdmBioLife à cláusu-
la uses da sua unidade wmBioLife. Depois, dê o foco ao módulo Web Biolife e dê um clique com o bo-
tão direito no componente AdapterPageProducer. Dê um clique com o botão direito no nó WebPa-
geItems, logo abaixo dele, selecione New Component (novo componente) e selecione um
AdapterForm. Selecione o AdapterForm, dê um clique com o botão direito nele e inclua uma Adapter-
ErrorList. Depois inclua uma AdapterGrid. Defina a propriedade Adapter dos dois componentes
como DatasetAdapter. Dê um clique com o botão direito na AdapterGrid e selecione Add All Co-
lumns (adicionar todas as colunas). Depois, selecione o nó Actions sob o DatasetAdapter, dê um cli-
que com o botão direito nele e selecione Add All Actions. Em seguida, selecione o nó Fields, dê um
clique com o botão direito e inclua todos os campos também. Você agora deverá ter todas as pro-
priedades definidas corretamente para exibir dados.
Vá até o módulo Web BioLife e dê um clique duplo no AdaperPageProducer. Você verá o Web
Surface Designer com dados vivos. Se não, verifique se você abriu a tabela e conectou todas as pro-
priedades Adapter para os componentes dentro do DatasetAdapter. O campo Notes torna a tabela
muito longa. Portanto, selecione a AdapterGrid no canto superior esquerdo e o componente ColNo-
tes no painel superior direito; depois, apague-o. Agora você deverá ter algo semelhante ao que
mostramos na Figura 23.13.
Os gráficos não aparecem durante o projeto, mas sim em runtime. Na realidade, você agora
pode compilar e executar a aplicação, e pode ver todos os dados na página BioLife – tudo sem escre-
ver uma única linha de código.
175
Naturalmente, olhar apenas para os dados não é muito útil. Provavelmente você desejará ma-
nipular os registros individuais. Certamente, isso é fácil de se fazer no WebSnap. Vá para o Web
Surface Designer e selecione a AdapterGrid. Dê um clique com o botão direito nela e inclua uma
AdapterCommandColumn. Depois, dê um clique com o botão direito nisso e selecione os comandos De-
leteRow, EditRow, BrowseRow e NewRow, como mostra a Figura 23.14.
Figura 23.14 Use a caixa de diálogo Add Commands para selecionar as ações que você deseja
fazer sobre as linhas individuais do dataset.
176
Figura 23.16 A página BioLifeEdit com todos os campos e ações adicionados
ao Web Surface Designer.
Agora, para usar essa página a fim de editar um único registro, volte à unidade wmBioLife onde
a grade se encontra e use a Object TreeView e a tecla Shift para selecionar todos os quatro botões na
AdapterCommandColumn. Defina sua propriedade de página como EditBioLife – o nome da página que
exibirá um único registro. Agora, quando você der um clique no botão na grade, a página EditBioLi-
fe será exibida. Se você pedir para navegar pelo registro, os dados serão exibidos como texto sim-
ples. Mas, se você pedir para editar o registro, eles aparecerão em caixas de edição. O campo gráfico
até mesmo permitirá que você inclua um novo gráfico no banco de dados, procurando um novo ar-
quivo. Você pode navegar pelo dataset usando os botões de comando. Novamente, tudo isso foi
conseguido sem a escrita de uma única linha de código – ou script, tanto faz.
NOTA
Você pode querer mexer um pouco na apresentação do campo Notes. Por default, o controle Text-
Area, usado quando a página está no modo de edição, é muito pequeno e não quebra o texto. Você
pode selecionar o componente FldNotes e ajustar as propriedades TextAreaWrap, DisplayRows e
DisplayWidth para obter melhores resultados.
LocateFileServices
O desenvolvimento de aplicações WebSnap para Windows normalmente exige a coordenação de
diferentes recursos. HTML, script no servidor, código Delphi, acesso a banco de dados e gráficos
precisam ser unidos corretamente a uma única aplicação. Normalmente, muitos desses recursos en-
contram-se embutidos e são gerenciados por meio de um arquivo HTML. O WebSnap oferece su-
porte para separar a HTML da implementação de uma página Web dinâmica, significando que você
pode editar os arquivos HTML separadamente do arquivo binário da aplicação Web. No entanto,
por default, essa HTML precisa residir em arquivos no mesmo local do arquivo binário. Isso nem
sempre é conveniente ou possível, e pode haver ocasiões em que você deseja que a HTML resida em
locais longe do seu binário. Ou então você pode querer obter conteúdo HTML de origens que não
sejam arquivos, digamos, de um banco de dados.
O WebSnap oferece a capacidade de obter HTML de qualquer origem que você queira. O
componente LocateFileService permite apanhar HTML de qualquer local de arquivo, arquivos in-
clude ou qualquer descendente de TStream. Ser capaz de acessar a HTML por um TStream significa
que você pode apanhar a HTML de qualquer origem, desde que ela possa ser colocada em um
TStream.
Por exemplo, a HTML pode ser colocada em um stream a partir de um arquivo RES embutido
no arquivo binário na sua aplicação. A aplicação de demonstração pode mostrar como isso é feito.
Naturalmente, você precisará de alguma HTML para embutir. Usando um editor de textos ou o seu
editor de HTML favorito, apanhe o arquivo wmLogin.html como modelo e salve-o no diretório da
sua aplicação de demonstração como embed.html. Depois, inclua algum texto no arquivo para notar
que o arquivo está embutido no arquivo RES. Desse modo, você saberá com certeza que possui o ar-
quivo correto quando ele for exibido.
Depois, naturalmente, você precisa embutir essa HTML na sua aplicação. O Delphi gerencia
isso com facilidade por meio de arquivos RC, compilando-os automaticamente e acrescentando-os
em uma aplicação. Portanto, use o Bloco de Notas ou alguma ferramenta de edição de textos para
criar um arquivo de texto e chame-o de HTML.RC. Salve-o no mesmo diretório da sua aplicação de de-
monstração e acrescente-o ao seu projeto. Depois, inclua este testo ao arquivo RC:
begin
178 end;
Os principais parâmetros aqui são AFileName e AFoundStream. Você os usará para apanhar a
HTML dos recursos embutidos. Faça com que seu tratador de evento se torne semelhante a este:
procedure THome.LocateFileServiceFindStream(ASender: TObject;
AComponent: TComponent; const AFileName: String;
var AFoundStream: TStream; var AOwned, AHandled: Boolean);
begin
// estamos procurando o arquivo embutido
if Pos('EMBEDDED', UpperCase(AFileName)) > 0 then begin
AFoundStream := TResourceStream.Create(hInstance, 'EMBEDDED', 'HTML');
AHandled := True; // não precisa procurar mais
end;
end;
AFileName será o nome não-qualificado do arquivo HTML que o Delphi usaria como default.
Você pode usar esse nome para determinar qual recurso irá pesquisar. AFoundStream será nil quando
passado para o tratador de evento, de modo que ficará por sua conta a criação de um stream usando
a variável. Nesse caso, AFoundStream torna-se um TResourceStream, que apanha a HTML dos recur-
sos no executável. A definição de AHandled como True garante que LocateFileServices não se esfor-
çará mais para localizar o conteúdo HTML.
Execute a aplicação e você verá sua HTML aparecendo quando exibir a página Embedded.
Uploading de arquivo
No passado, uma das tarefas mais desafiadoras para um desenvolvedor de aplicação Web era fazer o
uploading de arquivos do cliente para o servidor. Isso normalmente envolvia recursos bastante ar-
caicos da especificação HTTP e a contagem cuidadosa de cada byte passado. Como você pode ima-
ginar, o WebSnap torna essa tarefa anteriormente difícil em algo fácil. O WebSnap oferece toda a
funcionalidade para o uploading de arquivo dentro de um TAdapter, e sua parte não é muito mais di-
fícil do que colocar um arquivo em um stream.
Como sempre, crie outra página na sua aplicação, que fará o upload de arquivos para o servi-
dor a partir do cliente. Chame a página de Upload e dê-lhe um TAdapterPageProducer. Depois, salve
o arquivo como wmUpload. Em seguida, inclua um TAdapter no formulário. Dê ao TAdapter um novo
AdapterFileField. Esse campo controlará todo o uploading dos arquivos selecionados no cliente.
Além disso, dê ao Adapter uma única ação e chame-a de UploadAction.
Em seguida, dê ao AdapterPageProducer um AdapterForm com uma AdapterErrorList, um Adap-
terFieldGroup e um AdapterCommandGroup. Conecte os dois primeiros a Adapter1 e o AdapterCommand-
Group ao AdapterFieldGroup. Depois, acrescente todos os campos ao AdapterFieldGroup e todas as
ações ao AdapterCommandGroup. Mude a legenda no botão para Upload File. A Figura 23.17 mostra o
que você deverá ver no Surface Designer.
Figura 23.17 O Web Surface Designer para a página Upload, com o botão
Browse (procurar) acrescentado automaticamente.
179
O código pode ser incluído em dois lugares. O primeiro deles é no tratador de evento Adap-
ter1.AdapterFileFieldOnFileUpload. Esse código deverá ser semelhante ao da Listagem 23.5.
Esse código primeiro verifica se você selecionou um arquivo e depois certifica-se de que você
selecionou um arquivo JPEG. Depois de determinar que você fez isso, ele apanha o nome do arqui-
vo, garante que o diretório receptor existe e coloca o arquivo em um TFileStream. O trabalho real
aqui é feito nos bastidores pela classe TUpdateFileList que gerencia todo o tratamento de formulá-
rio esotérico e em várias partes do HTTP, necessário para o uploading de um arquivo do cliente
para o servidor.
O segundo lugar para incluir código é no tratador OnExecute para UploadAction em Adapter1.
Ele é o seguinte:
180 que simplesmente diz ao Adapter para atualizar seus registros e apanhar os arquivos que foram solicitados.
Incluindo modelos personalizados
Uma coisa que você provavelmente notou é que, quando uma nova página é criada com o New Page
Wizard, você tem apenas duas opções para a HTML na sua aplicação – o modelo padrão ou um mo-
delo em branco. O modelo padrão é bom para coisas como a aplicação de demonstração neste capí-
tulo. Porém, quando você começa a desenvolver sites mais sofisticados, desejará ser capaz de incluir
automaticamente seus próprios modelos HTML quando incluir páginas em suas aplicações. O
WebSnap também permite fazer isso.
Você pode incluir novos modelos nas seleções do New Page Wizard criando e registrando um
descendente de TProducerTemplatesList em um pacote durante o projeto. Existe um pacote de de-
monstração que faz isso no diretório <Delphi>\Demos\WebSnap\Producer Template. Você pode exami-
nar esse pacote e incluir seus próprios modelos HTML/script no arquivo RC incluído no pacote.
Observe que, para que esse pacote seja compilado, primeiro é preciso ter compilado o pacote <Delp-
hi>\Demos\WebSnap\Util\TemplateRes.dpk. Depois que você compilar e instalar esses pacotes, terá
ainda mais modelos para escolher no New Page Wizard.
type
Tddg6BaseWebSnapComponent =class(TWebContainedComponent,IWebContent)
protected
{ IWebContent }
function Content(Options: TWebContentOptions; ParentLayout: TLayout):
åstring;
function GetHTML: string; virtual; abstract;
end;
A classe abstrata implementa a função Content somente porque a função GetHTML é declarada
como abstrata. A função Content basicamente verifica se o componente que a contém é um Layout- 181
Group. Se for LayoutGroup, a função Content coloca seu conteúdo dentro do LayoutGroup. Caso con-
trário, Content simplesmente retorna os resultados de GetHTML. Os componentes descendentes, por-
tanto, só precisam implementar a função GetHTML, retornando o código HTML apropriado, e
podem ser registrados para trabalhar dentro de um TAdapterPageProducer.
O código neste CD-ROM implementa dois componentes que permitem incluir conteúdo
HTML em um TAdapterPageProducer, seja como uma string ou como um arquivo. O código para o
componente Tddg6HTMLCode aparece na Listagem 23.7.
Tddg6HTMLCode = class(Tddg6BaseWebSnapComponent)
private
FHTML: TStrings;
procedure SetHTML(const Value: TStrings);
protected
function GetHTML: string; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property HTML: TStrings read FHTML write SetHTML;
end;
destructor Tddg6HTMLCode.Destroy;
begin
FHTML.Free;
inherited;
end;
Essa é uma classe muito simples. Ela simplesmente oferece uma propriedade publicada do tipo
TString, que apanhará qualquer código HTML e depois o colocará no TAdapterPageProducer como
se encontra. A função GetHTML simplesmente retorna a HTML em formato de string. Você pode
criar componentes para retornar qualquer código HTML que queira incluir – imagens, links, arqui-
vos e outro conteúdo. Tudo o que os componentes descendentes precisam fazer é oferecer seu con-
teúdo HTML em um método GetHTML( ) modificado. Observe que existem funções de registro de
suporte na unidade onde os componentes são implementados. Ao criar componentes, certifique-se
de registrá-los na sua unidade, semelhante aos que estão neste CD-ROM. Para usar esses compo-
nentes, basta instalá-los em um pacote durante o projeto e os componentes aparecerão no Web Sur-
face Designer do TAdapterPageProducer (veja a Figura 23.18).
182
Figura 23.18 Componentes TAdapterPageProducer no Web Surface Designer.
Resumo
Essa foi uma visão geral rápida do poder do WebSnap. Este capítulo apenas arranhou a superfície
do que o WebSnap pode fazer. Não deixe de verificar as diversas aplicações de demonstração no di-
retório <Delphi>\Demos\WebSnap. Muitas delas dão mais funcionalidade ao estado padrão dos com-
ponentes WebSnap.
Logicamente, o WebSnap é uma tecnologia poderosa, mas é preciso que você faça algum es-
forço para entendê-la. Entretanto, depois que você passar pela curva de aprendizado inicial, logo
estará criando, com facilidade, sites Web poderosos, dinâmicos e controlados por banco de dados.
183
Desenvolvimento para CAPÍTULO
24
NESTE CAPÍTULO
l Evolução do desenvolvimento – como chegamos
até aqui?
l Dispositivos portáteis sem fio
l Tecnologias de rádio
l Tecnologias de dados sem fio baseadas em
servidor
l Experiência do usuário sem fio
Sem sombra de dúvida, duas tecnologias nos últimos 10 anos tocaram nossas vidas mais do que
quaisquer outras: a Internet e os dispositivos portáteis. Apesar dos altos e baixos dos negócios ba-
seados na Internet, esta – e, em particular, a Web – tem mudado nossas vidas permanentemente.
Ela tem afetado o modo como nos comunicamos, compramos, trabalhamos e brincamos. Mais
que isso, muitos de nós agora sentem arrepios ao pensar que poderia estar em algum lugar sem
nosso confiável celular (ou telefone portátil) ou Personal Digital Assistant (PDA). Devido à sua
importância em nossas vidas, parece natural que essas duas tecnologias agora estejam em um esta-
do de convergência, com os celulares se tornando tentáculos sem fio que saem da Internet com fio
para nos oferecer os serviços e as informações às quais nos tornamos viciados.
Com dispositivos de informação portáteis tornando-se mais uma necessidade do que uma no-
vidade no clima comercial e social de hoje, nós, os desenvolvedores encaramos o desafio de aprovei-
tar esse hardware e infra-estrutura para atender à demanda cada vez maior para colocar dados e
aplicações nos dispositivos portáteis. Com uma gama incrível de dispositivos portáteis, redes e tec-
nologias no mercado, as principais perguntas para os desenvolvedores se tornam: Qual das plata-
formas sem fio você deve focalizar? Qual é o modo mais eficaz de focalizá-las? Que tecnologias pos-
so aproveitar para fazer com que meus dados e aplicações se adaptem a dispositivos portáteis?
Quais são os prós e os contras de todas essas plataformas e tecnologias?
Este capítulo, de forma alguma, tem como objetivo servir como um guia completo, descreven-
do como implementar todas as diversas tecnologias portáteis. Isso exigiria muitos volumes. No en-
tanto, estaríamos com o dever cumprido se você conseguisse duas coisas deste capítulo. Primeiro,
você conseguirá usar este capítulo como uma introdução para entender o papel que muitos dos vá-
rios tipos de hardware, software e tecnologias desempenham na computação móvel, do ponto de
vista de um desenvolvedor. Segundo, você deverá entender como algumas dessas tecnologias portá-
teis podem ser implementadas com o Delphi.
Telefones portáteis
Os telefones portáteis, sem dúvida, são o tipo mais difundido de dispositivo portátil sem fio. Os te-
lefones portáteis ultrapassaram o âmbito dos sistemas de comunicação puramente para vez e entra-
ram para a comunicação de dados. A grande maioria dos novos telefones que chegam ao mercado
186 aceita mensagens de texto usando o Short Message Service (SMS) e navegação tipo Web, usando o
Wireless Application Protocol (WAP). As taxas de dados atuais ainda são baixas, entre 9,6 e 14,4k,
mas novas tecnologias prometem oferecer velocidades de até 2 Mbits dentro de 2 a 3 anos.
Dispositivos PalmOS
Dispositivos rodando o sistema operacional PalmOS, da Palm Computing, têm liderado o mercado
no espaço do PDA há vários anos. Alguns dispositivos PalmOS possuem capacidade de sem fio em-
butida (como a série Palm VII ou o Smartphone da Kyocera), e a capacidade sem fio pode ser acres-
centada a outros, por meio de um modem sem fio (como aqueles fabricados pela Novatel) ou por
um conector para telefone portátil, disponível na Palm. Diversas empresas licenciaram o PalmOS,
da Palm, Inc., para inclusão em seus próprios dispositivos, incluindo Handspring, Sony, Kyocera,
Symbol, Nokia, Samsung e TRG. Entre as vantagens do PalmOS está o fato de que eles possuem
uma fatia esmagadora do mercado de PDAs e existe uma forte comunidade de desenvolvimento
com um ativo mercado de terceiros.
Pocket PC
Compaq, HP, Casio e outros fabricantes produzem PDAs baseados no sistema operacional Pocket
PC (anteriormente Windows CE) da Microsoft. Até agora, nenhum desses dispositivos possui capa-
cidade interna para uso sem fio, mas eles oferecem suporte para modems sem fio de modo seme-
lhante aos dispositivos PalmOS. Mais ainda, dispositivos Pocket PC costumam ser um pouco mais
poderosos do que seus correspondentes PalmOS, e alguns têm a capacidade de aceitar PC Cards
(PCMCIA) padrão. Isso tem o potencial de permitir uma faixa de expansão ainda mais extensa para
redes sem fio com maior largura de banda.
RIM BlackBerry
A BlackBerry oferece a funcionalidade tipo PDA no tamanho de um pager. Com um modem sem fio
interno e um teclado, o BlackBerry é especialmente adequado para tarefas de e-mail portátil. No en-
tanto, o BlackBerry também oferece suporte para navegação Web por meio de um browser de tercei-
ros. Descobri que o BlackBerry é uma plataforma incrível para e-mail corporativo, graças à integra-
ção interna com MS Exchange ou Lotus Domino, mas o dispositivo pretende se tornar um utensílio
para Web, por causa do tamanho de sua tela e suas capacidades de navegação.
Tecnologias de rádio
As tecnologias de rádio oferecem a conexão entre os dispositivos portáteis e a Internet ou a rede lo-
cal da empresa.
CDPD
Cellular Digital Packet Data (CDPD) é uma tecnologia que permite a transferência de dados em pa-
cotes através de redes sem fio, oferecendo um aumento na largura de banda e a funcionalidade de 187
estar “sempre ligada”. CDPD é comum com o serviço de PDA sem fio nos Estados Unidos, como
aquele oferecido pela GoAmerica ou OmniSky, com velocidades chegando a 19,2k.
3G
Redes portáteis 3G, ou de terceira geração, são criadas desde o princípio para lidar com diversos ti-
pos diferentes de streams de mídia e ostentam uma largura de banda estimada como algo na faixa de
384k-2M. Os candidatos mais prováveis para serem os suportes do padrão 3G são tecnologias co-
nhecidas como EDGE e UMTS. No entanto, embora exista a tecnologia e várias operadoras possuam
espectro suficiente para implementar redes 3G, parece que nenhuma deseja ser a primeira a fazer o
investimento multibilionário em upgrades de rede a fim de seguir adiante com o 3G.
GPRS
General Packet Radio Service (GPRS) é considerado como o caminho de migração do 2G para o
3G, e normalmente é considerado como 2.5G. GPRS permite que o tráfego baseado em pacote
através da infra-estrutura 2G existente, apenas com upgrades relativamente pequenos. O through-
put observado nas redes GPRS provavelmente estará na faixa de 20 a 30k.
Bluetooth
Dispositivos incorporando a tecnologia de rádio Bluetooth só agora estão começando a aparecer no
mercado. Bluetooth é uma importante tecnologia emergente, pois permite o uso de rede ocasional
em onda curta entre diferentes tipos de dispositivos. Como os módulos de rádio Bluetooth são mui-
to pequenos, consomem pouca energia e são relativamente baratos, eles estarão embutidos em to-
dos os tipos de dispositivos portáteis, incluindo telefones, PDAs, laptops e assim por diante. A mai-
oria dos rádios Bluetooth terá um alcance de cerca de 10 metros e desfrutará de aproximadamente
700k de largura de banda. As aplicações em potencial para o Bluetooth incluem o sincronismo de
dados entre PDA e componente, quando estiverem nas proximidades um do outro, ou o forneci-
mento de conectividade com Internet para um laptop, por meio de um telefone portátil no bolso de
uma pessoa. Um novo termo, Personal Area Network (PAN), é usado para descrever essa noção de
pequena rede sem fio, onde todos os nossos dispositivos portáteis pessoais se comunicam regular-
mente uns com os outros.
É mais correto pensar no Bluetooth como um substituto para os cabos serial, USB ou IEEE
1394 do que como uma tecnologia de rede tipo Ethernet. A versão atual do Bluetooth oferece su-
porte apenas para um dispositivo mestre controlando um máximo de sete dispositivos escravos si-
multâneos.
802.11
Embora a tecnologia Bluetooth seja projetada como uma tecnologia de rede pessoal de curto alcance,
o 802.11 foi criado visando o uso em redes locais. A geração atual dessa tecnologia, 802.11b ou WiFi,
oferece até 11Mb de largura de banda, com uma versão de 45MB conhecida como 802.11a no hori-
zonte. 802.11 possui um alcance de cerca de 30 metros, com alcances maiores sendo possíveis com
antenas especiais. Os requisitos de energia do 802.11 são maiores do que os do Bluetooth, e os dispo-
sitivos possuem maior tamanho; o dispositivo de rádio pode caber dentro de um PC Card padrão, o
que é ótimo para laptops, mas não é conveniente para telefones ou para a maioria dos PDAs.
Uma observação importante que se deve ter em mente é que o Bluetooth e o 802.11 comparti-
lham o mesmo espectro de 2,4 GHz, de modo que é possível que os dois interfiram um com o outro
quando ocupam o mesmo espaço. Embora não seja provável que eles congelem completamente um
ao outro, visto que ambos usam tecnologia de espectro amplo para pular de freqüências várias vezes
por segundo, é provável que o desempenho sofra em uma ou ambas as conexões, devido à interfe-
188 rência mútua.
Tecnologias de dados sem fio baseadas em servidor
As tecnologias de dados sem fio aproveitam a tecnologia de rádio para oferecer dados e serviços a
dispositivos portáteis. Essas tecnologias envolvem servidores gerando conteúdo, que é enviado sem
fio para os clientes e interpretado pelo software embutido no cliente.
SMS
A tecnologia Short Message Service (SMS) é usada para enviar mensagens de texto curtas (geralmente
de 100 a 160 caracteres no máximo) para telefones portáteis. Fora o tamanho limitado para a mensa-
gem, a tecnologia SMS é limitada devido a questões de interoperabilidade entre as operadoras de rede
e protocolos SMS variados, empregados pelos operadoras. No entanto, SMS está se tornando bastan-
te popular – principalmente na Europa – devido à sua facilidade de uso e grande disponibilidade.
Como cada operadora pode empregar pequenas variações ao tema SMS, as técnicas para se desen-
volver aplicações que oferecem suporte ao SMS podem variar, dependendo da operadora que você es-
teja visando. Embora o GSM tenha vantagem em relação a outras redes de telefone móvel porque o su-
porte para SMS está embutido no padrão GSM, do ponto de vista de um desenvolvimento de aplicação,
ainda pode ser algo desafiador o envio de mensagens SMS de um servidor conectado à Internet para um
cliente portátil. Isso porque você precisa trabalhar com servidores SMS no lado da operadora, o que po-
deria envolver um suporte para padrões variados e até mesmo taxas de licenciamento.
Recomendamos um dentre dois caminhos para incorporar o suporte a SMS nos servidores. A
primeira opção é simplesmente usar e-mail; a maioria das operadoras oferece suporte para o envio
de uma mensagem SMS, enviando uma mensagem de e-mail para um endereço de e-mail específico,
que contém o número do telefone do destinatário. Embora essa seja uma técnica relativamente sim-
ples, do ponto de vista técnico, a desvantagem é que o suporte não é universal e acrescenta outra ca-
mada de falha em potencial. A segunda opção é comprar qualquer uma das muitas ferramentas de
terceiros que cuidam do envio de mensagens SMS por diversos tipos de redes. Essa é a técnica prefe-
rida, embora envolva alguns custos iniciais e/ou taxas de licenciamento.
WAP
Wireless Application Protocol (WAP) foi estabelecido como meio padrão para acessar informações
da Internet por meio de um dispositivo portátil. A aceitação geral do WAP no mercado tem sido di-
vidida. Por um lado, WAP tem sido bem recebido por operadores de rede e fabricantes de telefone,
pois foi projetado desde o início para trabalhar com qualquer serviço sem fio e padrão de rede em
praticamente qualquer dispositivo. No entanto, a experiência com WAP da parte do usuário não
tem sido totalmente positiva, devido a limitações no tamanho da tela, em suas capacidades de entra-
da de dados e na largura de banda sem fio. Além do mais, como os sites WAP geram receita limitada
com base no uso, não existe um incentivo comercial forte para o desenvolvimento de sites WAP de
alta qualidade. O conteúdo para sistemas WAP foi desenvolvido em uma linguagem baseada em
XML, conhecida como Wireless Markup Language (WML) – linguagem de marcação sem fio.
A arquitetura típica da aplicação WAP é ilustrada na Figura 24.1.
Dispositivo
WAP
Gateway Host de
WAP destino
URL URL
WML WML
(compilada) HTML
Filtro
HTML
<?xml version=”1.0”?>
<!DOCTYPE wml PUBLIC “-//WAPFORUM//DTD WML 1.1//EN”
“http://www.WAPforum.org/DTD/wml_1.1.xml”>
<wml>
<card>
<do type=“accept”>
<go href=“#hello ”/>
</do>
<p>Punch the Button</p>
</card>
190 <card id=“hello”>
<p>Hello from WAP!</p>
</card>
</wml>
Se você souber apenas um pouco sobre HTML e XML, provavelmente já desvendou esse códi-
go com relativa facilidade. O prólogo do documento, composto das três primeiras linhas, é XML
padrão e descreve a versão da XML desse documento e o local da DTD usada para descrever as tags
e os atributos nele contidos. Depois disso, o código passa a criar um baralho com duas cartas, uma
com um botão OK e uma com uma saudação.
A sintaxe da WML também oferece suporte a coisas como eventos, timers, conjuntos de cam-
pos, listas e imagens (embora nem todos os dispositivos aceitem imagens). Algumas das versões mais
recentes dos browsers WAP aceitam ainda uma linguagem de scripting chamada WMLScript. Não
podemos abordar toda a linguagem WML aqui, mas, se você estiver interessado, poderá ver os deta-
lhes da especificação WML em http://www.WAPforum.org.
Se você quiser experimentar o desenvolvimento de algum conteúdo WML, o modo mais fácil
de começar é obter um simulador. Você pode obter um simulador com o desenvolvedor do micro-
browser, em http://www.openwave.com, ou dois outros simuladores populares que vêm diretamente
dos líderes de comunicações portáteis Nokia e Ericsson; visite http://forum.nokia.com ou
http://www.ericsson.com/developerszone. É sempre bom fazer com que as coisas funcionem nos si-
muladores primeiro, antes de passar para o hardware real, pois os simuladores oferecem um tempo
de escrita-execução-depuração muito mais rápido. Antes da liberação final, também é uma boa
idéia testar seu produto final no máximo de dispositivos possível, pois as características exclusivas
de cada dispositivo podem fazer com que seu baralho se comporte ou apareça de forma diferente do
que você tinha em mente.
Segurança do WAP
A especificação WAP exige que uma pilha de criptografia sem fio, conhecida como Wireless Trans-
port Layer Security (WTLS), seja usada para conexões seguras. Como a SSL exige muitos recursos
para ser usada com a geração atual de dispositivos portáteis, a WTLS foi criada para oferecer servi-
ços de criptografia e autenticação entre o dispositivo e o gateway WAP. O gateway, por sua vez, já é
capaz de se comunicar com os hosts da Internet por meio do protocolo SSL padrão. Apesar do fato
de que tanto WTLS quanto SSL sejam bastante seguros por si sós, existe a possibilidade de brechas
de segurança no gateway WAP, no ponto onde o stream de dados WTLS é decodificado e recodifi-
cado com SSL. A arquitetura WTLS é ilustrada na Figura 24.2.
Telefone
portátil Gateway
WAP
Servidor Web
Comunicação
criptografada
com WTLS Comunicação
criptografada
com SSL
Conversão
WTSL/SSL
A Listagem 24.1 mostra o código-fonte para a unidade principal dessa aplicação, incluindo o
tratador de evento OnAction para a ação default do módulo Web.
unit Main;
interface
uses
SysUtils, Classes, HTTPApp;
type
TWebModule1 = class(TWebModule)
procedure WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
{ declarações privadas }
public
{ declarações públicas }
end;
var
WebModule1: TWebModule1;
implementation
{$R *.DFM}
const
SWMLContent = 'text/vnd.wap.wml';
SWBMPContent = 'image/vnd.wap.wbmp';
SWMLDeck =
'<?xml version="1.0"?>'#13#10 +
'<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"'#13#10 +
'"http://www.WAPforum.org/DTD/wml_1.1.xml">'#13#10 +
'<wml>'#13#10 +
' <card>'#13#10 +
' <do type="accept">'#13#10 +
192 ' <go href="#hello"/>'#13#10 +
Listagem 24.1 Continuação
' </do>'#13#10 +
' <p>Punch the Button</p>'#13#10 +
' </card>'#13#10 +
' <card id="hello">'#13#10 +
' <p>Hello from WAP!</p>'#13#10 +
' </card>'#13#10 +
'</wml>'#13#10;
end.
NOTA
Lembre-se de definir o ContentType do objeto Response como a string contendo o tipo MIME da va-
riedade de conteúdo que você está retornando. Isso define a informação de tipo de conteúdo no ca-
beçalho HTTP. Se você retornar o tipo de conteúdo incorreto, seu conteúdo provavelmente será mal
interpretado no dispositivo de destino. Alguns tipos de conteúdo WAP dignos de nota são
l text/vnd.wap.wml para o código WML
l text/vnd.wap.wmlscript para o código de script WML
l image/vnd.wap.wbmp para imagens Wireless Bitmap
Wireless Bitmaps
Embora o WAP ainda não tenha suporte para os elegantes gráficos JPEG e GIF, comuns na Web, a
maioria dos dispositivos WAP aceita imagens monocromáticas na forma de Wireless bitmaps
(wbmp). A Listagem 24.2 acrescenta uma nova ação ao módulo da Web, para dar suporte à gera-
ção de um wbmp. Essa ação gera um gráfico de aparência oficial, porém bastante aleatória, para a
exibição no dispositivo de destino. Embora não entremos em detalhes sobre o formato binário
dos arquivos wbmp neste texto, você pode ver que não é preciso muita coisa para gerar wbmps
manualmente em suas aplicações WAP. A Figura 24.5 mostra como ficaria um WBMP em uma
tela de telefone.
NOTA
Nem todos os browsers, dispositivos e simuladores WAP oferecem suporte para imagens wbmp.
Não se esqueça de testar antes de assumir que existe suporte.
unit Main;
interface
uses
SysUtils, Classes, HTTPApp;
type
TWebModule1 = class(TWebModule)
procedure WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
procedure WebModule1GraphActionAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
procedure CreateWirelessBitmap(MemStrm: TMemoryStream);
procedure HandleException(e: Exception; Response: TWebResponse);
end;
var
WebModule1: TWebModule1;
implementation
{$R *.DFM}
const
SWMLContent = 'text/vnd.wap.wml';
SWBMPContent = 'image/vnd.wap.wbmp';
194 SWMLDeck =
Listagem 24.2 Continuação
'<?xml version="1.0"?>'#13#10 +
'<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"'#13#10 +
'"http://www.WAPforum.org/DTD/wml_1.1.xml">'#13#10 +
'<wml>'#13#10 +
' <card>'#13#10 +
' <do type="accept">'#13#10 +
' <go href="#hello"/>'#13#10 +
' </do>'#13#10 +
' <p>Punch the Button</p>'#13#10 +
' </card>'#13#10 +
' <card id="hello">'#13#10 +
' <p>Hello from WAP!</p>'#13#10 +
' </card>'#13#10 +
'</wml>'#13#10;
SWMLError =
'<?xml version="1.0"?>'#13#10 +
'<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"'#13#10 +
'"http://www.wapforum.org/DTD/wml_1.1.xml">'#13#10 +
'<wml>'#13#10 +
' <card id="error" title="SimpWAP">'#13#10 +
' <p>Error: %s'#13#10 +
' <do type="prev" label="Back">'#13#10 +
' <prev/>'#13#10 +
' </do>'#13#10 +
' </p>'#13#10 +
' </card>'#13#10 +
'</wml>'#13#10;
CreateWirelessBitmap(MemStream);
MemStream.Position := 0;
with Response do
begin
ContentType := SWBMPContent;
ContentStream := MemStream;
SendResponse;
end;
finally
MemStream.Free;
end;
except
on e: Exception do
HandleException(e, Response);
end;
end;
B := 0;
end;
end;
end;
end;
initialization
Randomize;
end.
I-mode
I-mode é uma tecnologia própria para conteúdo da Internet em telefones portáteis, desenvolvida
pela NTT DoCoMo, gigante de telecomunicações do Japão. I-mode tem muito sucesso no Japão,
com mais de 20 milhões de assinantes e crescendo. De várias maneiras, i-mode é tudo o que o WAP
não é: aceita gráficos ricos, com 256 cores, utiliza telas de telefone coloridas e uma conexão TCP/IP
“sempre ligada”. Além do mais, a DoCoMo desenvolveu um modelo de compartilhamento de re-
ceitas que permite ao sites i-mode obterem uma fatia da torta financeira com base no uso. No entan-
to, o i-mode está dificultado pela aspecto da tecnologia própria, e a disponibilidade fora do Japão
ainda é escassa; os serviços do i-mode estarão sendo distribuídos nos Estados Unidos, Reino Unido
e Europa continental a partir deste ano.
De um ponto de vista do desenvolvedor, o direcionamento de telefones i-mode não é muito
mais difícil do que a criação de conteúdo para a Web, pois o conteúdo i-mode é desenvolvido usan-
do-se um subconjunto da HTML, conhecido como Compact HTML (cHTML). As tags cHTML
aceitas e as regras estão disponíveis na DoCoMo, em http://www.nttdocomo.com/i/tagindex.html.
Observe que, além de usar apenas as tags aceitas pela cHTML, os sites i-mode também precisam ga-
rantir que será usado o conjunto de caracteres S-JIS, a imagens são no formato GIF e as páginas não
possuem qualquer conteúdo de script ou Java.
PQA
Palm Query Applications (PQA) são essencialmente páginas HTML normais, sem acréscimos como
scripting e imagens projetadas para exibição na tela de um dispositivo PalmOS sem fio. Os dispositi-
vos PalmOS sem fio, como os Palm VIIx ou aqueles equipados com modems Novatel, atualmente
estão limitados à América do Norte. Assim como o i-mode, PQAs são desenvolvidas usando um sub- 197
conjunto da HTML, exceto que o Palm incluiu algumas extensões próprias. Os desenvolvedores in-
teressados em criar PQAs deverão baixar o Web Clipping Developer’s Guide do Palm, em
http://www.palmos.com/dev/tech/docs/.
Em geral, o ramo PQA da HTML inclui tudo o que existe na HTML 3.2, com a exceção dos
applets (miniaplicativos), JavaScript, tabelas aninhadas, mapas de imagem e as tags VSPACE, SUB, SUP,
LINK e ISINDEX. PQAs também incluem vários acréscimos interessantes à HTML. Os mais interes-
santes são a meta tag palmcomputingplatform e as tags %zipcode e %deviceid. Quando um documento
HTML contém a meta tag palmcomputingplatform, isso serve como um indicador para o proxy
Palm.net da Palm Computing (que atua como intermediário entre um servidor Web e o dispositivo
Palm, semelhante a um gateway WAP) de que o documento está otimizado para exibição em um
portátil PalmOS e não exige análise para que seu conteúdo inválido seja removido. Quando a tag
%zipcode aparece em um pedido postado pelo cliente, o proxy Palm.net substitui a tag pelo código
postal de onde o dispositivo está localizado (com base nas informações da torre de rádio). A tag %de-
viceid, semelhantemente, envia o ID exclusivo do dispositivo PalmOS para o servidor. Isso é práti-
co principalmente porque a HTML PQA não oferece suporte para cookies, mas um meio semelhan-
te de gerenciamento de estado pode ser preparado com o uso da tag %deviceid.
Com o Web clipping, o Palm utiliza uma técnica diferente da maioria dos outros players nesse
espaço. Em vez de usar uma entidade tipo browser para navegar até um site e apanhar seu conteúdo,
as PQAs existem localmente no dispositivo PalmOS. PQAs são criadas passando-se um arquivo
.HTML padrão por um compilador especial, que vincula a HTML com arquivos gráficos referen-
ciados e outras dependências. Os usuários instalam PQAs como aplicações PRC normais do
PalmOS. As PQAs ganham eficiência incluindo partes da aplicação local e só indo para a rede para
as páginas de “resultados”.
Cliente PQA
A primeira etapa para o desenvolvimento de uma aplicação PQA é criar a parte que residirá fisica-
mente no dispositivo cliente. Isso é feito criando-se um documento HTML e compilando-o com a
ferramenta PQA Builder da Palm Computing. Um exemplo de um documento HTML para uma
PQA aparece na Listagem 24.3.
<html>
<head>
<title>DDG PQA Test</title>
<meta name="palmcomputingplatform" content="true">
</head>
<body>
<p>This is a sample PQA for DDG</p>
<img src="image.gif">
<form method="post" action="http://128.64.162.164/scripts/pqatest.dll">
<input type="hidden" value="%zipcode" name="zip">
<input type="hidden" value="%deviceid" name="id">
<input type="submit">
</form>
</body>
Você pode ver que esse documento HTML simples contém uma referência a uma imagem, al-
gum texto, um formulário com um botão de envio e campos ocultos usados para passar o código
postal e o ID do dispositivo para o servidor. A Figura 24.6 mostra esse documento sendo compilado
no PQA Builder.
198
Figura 24.6 Compilando com o PQA Builder.
Uma vez compilado, é gerado um arquivo com uma extensão .pqa. Esse arquivo contém o do-
cumento HTML, além de quaisquer imagens referenciadas. Esse arquivo pode ser instalado no dis-
positivo PalmOS da mesma forma que qualquer outra aplicação PalmOS.
Servidor PQA
A parte do servidor, como WAP, é uma aplicação WebSnap que cuida dos pedidos de página de
clientes e retorna páginas. No entanto, ao contrário do WAP com sua WML, PQAs se comunicam
usando a variante da HTML descrita anteriormente. A Listagem 24.4 mostra a unidade principal de
uma aplicação WebBroker, criada para realizar o papel de servidor para o cliente PQA descrito an-
teriormente.
unit Main;
interface
uses
SysUtils, Classes, HTTPApp;
type
TWebModule1 = class(TWebModule)
procedure WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
WebModule1: TWebModule1;
implementation
{$R *.DFM}
const
SPQAResp =
'<html><head><meta name="palmcomputingplatform" content="true"></head>'+
#13#10 +
'<body>Hello from a Delphi server<br>Your zipcode is: %s<br>'#13#10 + 199
Listagem 24.4 Continuação
end.
A importância do espaço
Ao criar aplicações portáteis, você sempre deverá permanecer sensível à quantidade disponível de
espaço na tela. Seja criando uma aplicação baseada em WAP, vinculada a um microbrowser, ou uma
interface com o usuário personalizada em J2ME, os desenvolvedores de aplicação precisam equili-
brar entre os problemas de comunicar uma quantidade suficiente de informações em cada tela e
manter uma interface com o usuário legível e navegável.
201
M-Commerce
Assim como o e-commerce passou a se referir a transações comerciais realizadas pela Web,
m-commerce refere-se a transações comercias feitas por meio de dispositivos móveis. Os desenvol-
vedores de sites de comércio por dispositivo móvel precisam entender que m-commerce é funda-
mentalmente diferente de e-commerce. Evidentemente, não é viável que os clientes de dispositivos
móveis naveguem pelos itens. No caso de um telefone portátil, por exemplo, é muito demorada a
entrada de toques de tecla – imagens ou não existem ou são de má qualidade, e não há espaço sufici-
ente na tela para a descrição dos itens.
Em vez disso, os sistemas de m-commerce precisam ser projetados com a noção de que os usuá-
rios sabem o que querem; basta facilitar o modo como eles dão seu dinheiro. Lembre-se: se o usuário
quiser comprar uma televisão ou um livro, não há motivo para que ele não possa simplesmente es-
perar até chegar em sua casa ou escritório e fazer a compra em um computador “normal”. O fato de
que o usuário sequer deseja se engajar no m-commerce significa que existe algum sentido na urgên-
cia de se fazer a compra, e os comerciantes de m-commerce bem-sucedidos serão aqueles que reco-
nhecerem e tirarem proveito desse fato.
Por exemplo, um país da Europa oriental permite que os motoristas paguem a tarifa do estacio-
namento com seus telefones celulares. Isso pode ser uma aplicação relativamente simples na super-
fície, mas a proposição de valor para ambas as partes é muito atraente. O motorista não precisa se
preocupar em ter moedas suficientes para colocar nos medidores, e o operador do aparelho não
precisa juntar as moedas de dezenas ou centenas ou milhares de medidores, a fim de trocá-las no
banco. O policial monitorando os medidores pode usar um dispositivo móvel ligado ao mesmo sis-
tema para saber instantaneamente quanto tempo resta para determinada vaga.
Resumo
O mundo da computação para dispositivo sem fio cresceu muito nos últimos anos, e pode ser muito
difícil acompanhar todas as tendências emergentes. Nossa esperança é que, nesse ponto, você já es-
teja armado com informações suficientes para tomar algumas decisões estratégicas e seguir adiante
com um projeto de mobilidade. Além disso, você viu que o Delphi é uma ferramenta bastante capaz
quando se trata da criação da próxima geração de aplicações para dispositivos sem fio.
202