Manipulação de cenários de Transferência de Dados do Shell
O documento Objeto de Dados do Shell discutiu a abordagem geral usada para transferir dados do Shell com o recurso de arrastar e soltar ou a Área de Transferência. No entanto, para implementar a transferência de dados do Shell em seu aplicativo, você também deve entender como aplicar esses princípios e técnicas gerais à variedade de maneiras pelas quais os dados do Shell podem ser transferidos. Este documento apresenta cenários comuns de transferência de dados do Shell e discute como implementar cada um deles em seu aplicativo.
- Diretrizes gerais
- Copiando nomes de arquivo da área de transferência para um aplicativo
- Copiando o conteúdo de um arquivo descartado em um aplicativo
- Lidando com operações de movimentação otimizadas
- Manipulando operações de exclusão ao colar
- Transferindo dados de e para pastas virtuais
- Descartando arquivos na Lixeira
- Criando e importando arquivos de sucata
- Arrastando e soltando objetos do shell de forma assíncrona
Observação
Embora cada um desses cenários discuta uma operação de transferência de dados específica, muitos deles se aplicam a uma variedade de cenários relacionados. Por exemplo, a principal diferença entre a maioria das transferências da Área de Transferência e arrastar e soltar está em como o objeto de dados chega ao destino. Quando o destino tem um ponteiro para a interface IDataObject do objeto de dados, os procedimentos para extrair informações são basicamente os mesmos para ambos os tipos de transferência de dados. No entanto, alguns dos cenários são limitados a um tipo específico de operação. Consulte o cenário individual para obter detalhes.
Diretrizes gerais
Cada uma das seções a seguir discute um cenário de transferência de dados único e bastante específico. No entanto, as transferências de dados geralmente são mais complexas e podem envolver aspectos de vários cenários. Você normalmente não sabe, com antecedência, qual cenário você realmente precisará lidar. Aqui estão algumas diretrizes gerais para ter em mente.
Para fontes de dados:
- Os formatos da Área de Transferência do Shell, com exceção do CF_HDROP, não são predefinidos. Cada formato que você deseja usar deve ser registrado chamando RegisterClipboardFormat.
- Os formatos nos objetos de dados são fornecidos na ordem de preferência da origem. Enumere o objeto de dados e escolha o primeiro que você pode consumir.
- Inclua tantos formatos quanto você puder suportar. Você geralmente não sabe onde o objeto de dados será descartado. Essa prática melhora as chances de que o objeto de dados contenha um formato que o destino de soltar possa aceitar.
- Os arquivos existentes devem ser oferecidos com o formato CF_HDROP.
- Ofereça dados semelhantes a arquivos com CFSTR_FILECONTENTS/formatos CFSTR_FILEDESCRIPTOR. Essa abordagem permite que o destino crie um arquivo a partir de um objeto de dados sem precisar saber nada sobre o armazenamento de dados subjacente. Normalmente, você deve apresentar os dados como uma interface IStream. Esse mecanismo de transferência de dados é mais flexível do que um objeto de memória global e usa muito menos memória.
- As fontes de arraste devem oferecer o formato CFSTR_SHELLIDLIST ao arrastar itens do Shell. Os objetos de dados para itens podem ser adquiridos por meio dos métodos IShellFolder::GetUIObjectOf ou IShellItem::BindToHandler. As fontes de dados podem criar uma implementação de objeto de dados padrão que ofereça suporte ao formato CFSTR_SHELLIDLIST usando SHCreateDataObject.
- Os destinos de soltura que desejam raciocinar sobre os itens que estão sendo arrastados usando o modelo de programação de item do shell podem converter um IDataObject em um IShellItemArray usando SHCreateShellItemArrayFromDataObject.
- Use cursores de feedback padrão.
- Suporte arrastar para a esquerda e para a direita.
- Use o próprio objeto de dados de um objeto incorporado. Essa abordagem permite que seu aplicativo recupere quaisquer formatos extras que o objeto de dados tenha a oferecer e evita a criação de uma camada extra de contenção. Por exemplo, um objeto incorporado do servidor A é arrastado do servidor/contêiner B e solto no contêiner C. C deve criar um objeto incorporado do servidor A, não um objeto incorporado do servidor B contendo um objeto incorporado do servidor A.
- Lembre-se de que o Shell pode usar movimentos otimizados ou operações de exclusão ao colar ao mover arquivos. Seu aplicativo deve ser capaz de reconhecer essas operações e responder adequadamente.
Para destinos de dados:
- Os formatos da Área de Transferência do Shell, com exceção do CF_HDROP, não são predefinidos. Cada formato que você deseja usar deve ser registrado chamando RegisterClipboardFormat.
- Implemente e registre um destino de descarte OLE. Evite usar destinos do Windows 3.1 ou a mensagem WM_DROPFILES, se possível.
- Os formatos contidos por um objeto de dados variam, dependendo de onde o objeto vem. Como você geralmente não sabe com antecedência de onde vem um objeto de dados, não assuma que um formato específico estará presente. O objeto de dados deve enumerar os formatos em ordem de qualidade, começando com os melhores. Assim, para obter o melhor formato disponível, os aplicativos normalmente enumeram os formatos disponíveis e usam o primeiro formato na enumeração que eles podem suportar.
- Suporte a arrastar para a direita. Você pode personalizar o menu de atalho arrastar criando um manipulador de arrastar e soltar.
- Se o aplicativo aceitar arquivos existentes, ele deverá ser capaz de lidar com o formato CF_HDROP.
- Em geral, os aplicativos que aceitam arquivos também devem lidar com os formatos CFSTR_FILECONTENTS /CFSTR_FILEDESCRIPTOR. Embora os arquivos do sistema de arquivos tenham o formato CF_HDROP, os arquivos de provedores como extensões de namespace geralmente usam CFSTR_FILECONTENTS/CFSTR_FILEDESCRIPTOR. Exemplos incluem pastas do Windows CE, pastas FTP (File Transfer Protocol), pastas da Web e pastas CAB. A fonte normalmente implementa uma interface IStream para apresentar dados de seu armazenamento como um arquivo.
- Lembre-se de que o Shell pode usar movimentos otimizados ou operações de exclusão ao colar ao mover arquivos. Seu aplicativo deve ser capaz de reconhecer essas operações e responder adequadamente.
Copiando nomes de arquivo da área de transferência para um aplicativo
Cenário: um usuário seleciona um ou mais arquivos no Windows Explorer e os copia para a Área de Transferência. Seu aplicativo extrai os nomes de arquivo e os cola no documento.
Esse cenário pode ser usado, por exemplo, para permitir que um usuário crie um link HTML cortando e colando o arquivo em seu aplicativo. Seu aplicativo pode extrair o nome do arquivo do objeto de dados e processá-lo para criar uma marca âncora.
Quando um usuário seleciona um arquivo no Windows Explorer e o copia para a Área de Transferência, o Shell cria um objeto de dados. Em seguida, ele chama OleSetClipboard para colocar um ponteiro para a interface IDataObject do objeto de dados na área de transferência.
Quando o usuário seleciona o comando Colar no menu ou na barra de ferramentas do aplicativo:
- Chame OleGetClipboard para recuperar a interface IDataObject do objeto de dados.
- Chame IDataObject::EnumFormatEtc para solicitar um objeto enumerador.
- Use a interface IEnumFORMATETC do objeto enumerador para enumerar os formatos contidos pelo objeto de dados.
Observação
As duas últimas etapas deste procedimento estão incluídas para a completude. Eles normalmente não são necessários para transferências de arquivos simples. Todos os objetos de dados usados para esse tipo de transferência de dados devem conter o formato CF_HDROP , que pode ser usado para determinar os nomes dos arquivos contidos pelo objeto. No entanto, para transferências de dados mais gerais, você deve enumerar os formatos e selecionar o melhor que seu aplicativo pode manipular.
Extraindo os nomes de arquivo do objeto de dados
A próxima etapa é extrair um ou mais nomes de arquivo do objeto de dados e colá-los em seu aplicativo. Observe que o procedimento discutido nesta seção para extrair um nome de arquivo de um objeto de dados se aplica igualmente bem a transferências de arrastar e soltar.
A maneira mais simples de recuperar nomes de arquivo de um objeto de dados é o formato CF_HDROP:
Chame IDataObject::GetData. Defina o membro cfFormat da estrutura FORMATETC como CF_HDROP e o membro tymed como TYMED_HGLOBAL. O membro dwAspect é normalmente definido como DVASPECT_CONTENT. No entanto, se você precisar ter o caminho do arquivo no formato curto (8.3), defina dwAspect como DVASPECT_SHORT.
Quando IDataObject::GetData retorna, o membro hGlobal da estrutura STGMEDIUM aponta para um objeto de memória global que contém os dados.
Crie uma variável HDROP e defina-a como o membro hGlobal da estrutura STGMEDIUM . A variável HDROP agora é um identificador para uma estrutura DROPFILES seguida por uma cadeia de caracteres dupla terminada em nulo contendo os caminhos de arquivo totalmente qualificados dos arquivos copiados.
Determine quantos caminhos de arquivo estão na lista chamando DragQueryFile com o parâmetro iFile definido como 0xFFFFFFFF. A função retorna o número de caminhos de arquivo na lista. O índice baseado em zero do caminho do arquivo nessa lista é usado na próxima etapa para identificar um caminho específico.
Extraia os caminhos de arquivo do objeto de memória global chamando DragQueryFile uma vez para cada arquivo, com iFile definido como o índice do arquivo.
Processe os caminhos de arquivo conforme necessário e cole-os em seu aplicativo.
Chame ReleaseStgMedium e passe o ponteiro para a estrutura STGMEDIUM que você passou para IDataObject::GetData na etapa 1. Depois de liberar a estrutura, o valor HDROP que você criou na etapa 2 não é mais válido e não deve ser usado.
Copiando o conteúdo de um arquivo descartado em um aplicativo
Cenário: um usuário arrasta um ou mais arquivos do Windows Explorer e os solta na janela do aplicativo. Seu aplicativo extrai o conteúdo do(s) arquivo(s) e o cola no aplicativo.
Esse cenário usa arrastar e soltar para transferir os arquivos do Windows Explorer para o aplicativo. Antes da operação, seu aplicativo deve:
- Chame RegisterClipboardFormat para registrar todos os formatos necessários da Área de Transferência do Shell.
- Chame RegisterDragDrop para registrar uma janela de destino e a interface IDropTarget do seu aplicativo.
Depois que o usuário inicia a operação selecionando um ou mais arquivos e começando a arrastá-los:
- O Windows Explorer cria um objeto de dados e carrega os formatos com suporte nele.
- O Windows Explorer chama DoDragDrop para iniciar o loop de arraste.
- Quando a imagem de arrastar atinge a janela de destino, o sistema o notifica chamando IDropTarget::D ragEnter.
- Para determinar o que o objeto de dados contém, chame o método IDataObject::EnumFormatEtc do objeto de dados. Use o objeto enumerador retornado pelo método para enumerar os formatos contidos pelo objeto de dados. Se o seu aplicativo não quiser aceitar nenhum desses formatos, retorne DROPEFFECT_NONE. Para os fins desse cenário, seu aplicativo deve ignorar quaisquer objetos de dados que não contêm formatos usados para transferir arquivos, como CF_HDROP.
- Quando o usuário descarta os dados, o sistema chama IDropTarget::D rop.
- Use a interface IDataObject para extrair o conteúdo dos arquivos.
Há várias maneiras diferentes de extrair o conteúdo de um objeto Shell de um objeto de dados. Em geral, use a seguinte ordem:
- Se o arquivo contiver um formato CF_TEXT , os dados serão texto ANSI. Você pode usar o formato CF_TEXT para extrair os dados, em vez de abrir o arquivo em si.
- Se o arquivo contiver um objeto OLE vinculado ou incorporado, o objeto de dados conterá um formato CF_EMBEDDEDOBJECT. Use técnicas OLE padrão para extrair os dados. Os arquivos de scrap sempre contêm um formato CF_EMBEDDEDOBJECT.
- Se o objeto Shell for do sistema de arquivos, o objeto de dados conterá um formato CF_HDROP com os nomes dos arquivos. Extraia o nome do arquivo de CF_HDROP e chame OleCreateFromFile para criar um novo objeto vinculado ou incorporado. Para obter uma discussão sobre como recuperar um nome de arquivo de um formato CF_HDROP , consulte Copiando nomes de arquivo da área de transferência para um aplicativo.
- Se o objeto de dados contiver um formato CFSTR_FILEDESCRIPTOR , você poderá extrair o conteúdo de um arquivo do formato CFSTR_FILECONTENTS do arquivo. Para obter uma discussão sobre esse procedimento, consulte Usando o formato CFSTR_FILECONTENTS para extrair dados de um arquivo.
- Antes do Shell versão 4.71, um aplicativo indicava que estava transferindo um tipo de arquivo de atalho definindo FD_LINKUI no membro dwFlags da estrutura FILEDESCRIPTOR. Para versões posteriores do Shell, a maneira preferencial de indicar que os atalhos estão sendo transferidos é usar o formato de CFSTR_PREFERREDDROPEFFECT definido como DROPEFFECT_LINK. Essa abordagem é muito mais eficiente do que extrair a estrutura FILEDESCRIPTOR apenas para verificar um sinalizador.
Se o processo de extração de dados for demorado, convém fazer a operação de forma assíncrona em um thread em segundo plano. Seu thread principal pode então prosseguir sem atrasos desnecessários. Para obter uma discussão sobre como manipular a extração de dados assíncrona, consulte Arrastando e soltando objetos do shell de forma assíncrona.
Usando o formato CFSTR_FILECONTENTS para extrair dados de um arquivo
O formato CFSTR_FILECONTENTS fornece uma maneira muito flexível e poderosa de transferir o conteúdo de um arquivo. Nem mesmo é necessário que os dados sejam armazenados como um único arquivo. Tudo o que é necessário para esse formato é que o objeto de dados apresente os dados ao destino como se fosse um arquivo. Por exemplo, os dados reais podem ser uma seção de um documento de texto ou um bloco de dados extraídos de um banco de dados. O destino pode tratar os dados como um arquivo e não precisa saber nada sobre o mecanismo de armazenamento subjacente.
As extensões de namespace normalmente usam CFSTR_FILECONTENTS para transferir dados porque esse formato não pressupõe nenhum mecanismo de armazenamento específico. Uma extensão de namespace pode usar qualquer mecanismo de armazenamento conveniente e usar esse formato para apresentar seus objetos aos aplicativos como se fossem arquivos.
O mecanismo de transferência de dados para CFSTR_FILECONTENTS é normalmente TYMED_ISTREAM. Transferir um ponteiro de interface IStream requer muito menos memória do que carregar os dados em um objeto de memória global, e IStream é uma maneira mais simples de representar dados do que IStorage.
Um formato CFSTR_FILECONTENTS é sempre acompanhado por um formato CFSTR_FILEDESCRIPTOR . Você deve examinar o conteúdo desse formato primeiro. Se mais de um arquivo estiver sendo transferido, o objeto de dados realmente conterá vários formatos de CFSTR_FILECONTENTS , um para cada arquivo. O formato CFSTR_FILEDESCRIPTOR contém o nome e os atributos de cada arquivo e fornece um valor de índice para cada arquivo necessário para extrair o formato de CFSTR_FILECONTENTS de um arquivo específico.
Para extrair um formato CFSTR_FILECONTENTS :
- Extraia o formato CFSTR_FILEDESCRIPTOR como um valor TYMED_HGLOBAL.
- O membro hGlobal da estrutura STGMEDIUM retornada aponta para um objeto de memória global. Bloqueie esse objeto passando o valor hGlobal para GlobalLock.
- Converta o ponteiro retornado por GlobalLock em um ponteiro FILEGROUPDESCRIPTOR. Ele apontará para uma estrutura FILEGROUPDESCRIPTOR seguida por uma ou mais estruturas FILEDESCRIPTOR . Cada estrutura FILEDESCRIPTOR contém uma descrição de um arquivo que está contido por um dos formatos de CFSTR_FILECONTENTS que o acompanham.
- Examine as estruturas FILEDESCRIPTOR para determinar qual corresponde ao arquivo que você deseja extrair. O índice baseado em zero dessa estrutura FILEDESCRIPTOR é usado para identificar o formato CFSTR_FILECONTENTS do arquivo. Como o tamanho de um bloco de memória global não é preciso de bytes, use os membros nFileSizeLow e nFileSizeHigh da estrutura para determinar quantos bytes representam o arquivo no objeto de memória global.
- Chame IDataObject::GetData com o membro cfFormat da estrutura FORMATETC definido como o valor CFSTR_FILECONTENTS e o membro lIndex definido como o índice que você determinou na etapa anterior. O membro vinculado é normalmente definido como TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE. O objeto de dados pode então escolher seu mecanismo de transferência de dados preferido.
- A estrutura STGMEDIUM que IDataObject::GetData retorna conterá um ponteiro para os dados do arquivo. Examine o membro vinculado da estrutura para determinar o mecanismo de transferência de dados.
- Se tymed estiver definido como TYMED_ISTREAM ou TYMED_ISTORAGE, use a interface para extrair os dados. Se tymed estiver definido como TYMED_HGLOBAL, os dados estarão contidos em um objeto de memória global. Para obter uma discussão sobre como extrair dados de um objeto de memória global, consulte a seção Extraindo um objeto de memória global de um objeto de dados do Shell Data Object.
- Chame GlobalLock para desbloquear o objeto de memória global que você bloqueou na etapa 2.
Lidando com operações de movimentação otimizadas
Cenário: um arquivo é movido do sistema de arquivos para uma extensão de namespace usando uma movimentação otimizada.
Em uma operação de movimentação convencional, o destino faz uma cópia dos dados e a origem exclui o original. Esse procedimento pode ser ineficiente porque requer duas cópias dos dados. Com objetos grandes, como bancos de dados, uma operação de movimentação convencional pode nem ser prática.
Com uma movimentação otimizada, o destino usa sua compreensão de como os dados são armazenados para lidar com toda a operação de movimentação. Nunca há uma segunda cópia dos dados e não há necessidade de a fonte excluir os dados originais. Os dados do Shell são adequados para movimentações otimizadas porque o destino pode manipular toda a operação usando a API do Shell. Um exemplo típico é mover arquivos. Uma vez que o destino tem o caminho de um arquivo a ser movido, ele pode usar SHFileOperation para movê-lo. Não há necessidade de a fonte excluir o arquivo original.
Observação
O Shell normalmente usa um movimento otimizado para mover arquivos. Para manipular a transferência de dados do Shell corretamente, seu aplicativo deve ser capaz de detectar e manipular uma movimentação otimizada.
Os movimentos otimizados são tratados da seguinte maneira:
A origem chama DoDragDrop com o parâmetro dwEffect definido como DROPEFFECT_MOVE para indicar que os objetos de origem podem ser movidos.
O destino recebe o valor DROPEFFECT_MOVE por meio de um de seus métodos IDropTarget , indicando que uma movimentação é permitida.
O destino copia o objeto (movimento não otimizado) ou move o objeto (movimento otimizado).
Em seguida, o destino informa à fonte se precisa excluir os dados originais.
Uma movimentação otimizada é a operação padrão, com os dados excluídos pelo destino. Para informar à fonte que uma movimentação otimizada foi executada:
-
- O destino define o valor pdwEffect que recebeu por meio de seu método IDropTarget::D rop como algum valor diferente de DROPEFFECT_MOVE. Normalmente, ele é definido como DROPEFFECT_NONE ou DROPEFFECT_COPY. O valor será retornado à origem por DoDragDrop.
- O destino também chama o método IDataObject::SetData do objeto de dados e passa a ele um identificador de formato de CFSTR_PERFORMEDDROPEFFECT definido como DROPEFFECT_NONE. Essa chamada de método é necessária porque alguns destinos de descarte podem não definir o parâmetro pdwEffect de DoDragDrop corretamente. O formato CFSTR_PERFORMEDDROPEFFECT é a maneira confiável de indicar que uma movimentação otimizada ocorreu.
Se o destino fez uma movimentação não otimizada, os dados devem ser excluídos pela origem. Para informar à fonte que uma movimentação não otimizada foi executada:
-
- O destino define o valor pdwEffect recebido por meio de seu método IDropTarget::D rop como DROPEFFECT_MOVE. O valor será retornado à origem por DoDragDrop.
- O destino também chama o método IDataObject::SetData do objeto de dados e passa a ele um identificador de formato de CFSTR_PERFORMEDDROPEFFECT definido como DROPEFFECT_MOVE. Essa chamada de método é necessária porque alguns destinos de descarte podem não definir o parâmetro pdwEffect de DoDragDrop corretamente. O formato CFSTR_PERFORMEDDROPEFFECT é a maneira confiável de indicar que uma movimentação não otimizada ocorreu.
-
A origem inspeciona os dois valores que podem ser retornados pelo destino. Se ambos estiverem definidos como DROPEFFECT_MOVE, ele concluirá a movimentação não otimizada excluindo os dados originais. Caso contrário, o destino fez uma movimentação otimizada e os dados originais foram excluídos.
Manipulando operações de exclusão ao colar
Cenário: um ou mais arquivos são cortados de uma pasta no Windows Explorer e colados em uma extensão de namespace. O Windows Explorer deixa os arquivos realçados até receber comentários sobre o resultado da operação de colagem.
Tradicionalmente, quando um usuário corta dados, eles desaparecem imediatamente da exibição. Isso pode não ser eficiente e pode levar a problemas de usabilidade se o usuário ficar preocupado com o que aconteceu com os dados. Uma abordagem alternativa é usar uma operação de exclusão ao colar.
Com uma operação de exclusão ao colar, os dados selecionados não são imediatamente removidos da exibição. Em vez disso, o aplicativo de origem o marca como selecionado, talvez alterando a fonte ou a cor do plano de fundo. Depois que o aplicativo de destino colar os dados, ele notifica a origem sobre o resultado da operação. Se o destino executou um movimento otimizado, a origem pode simplesmente atualizar sua exibição. Se o destino executou uma movimentação normal, a origem também deve excluir sua cópia dos dados. Se a colagem falhar, o aplicativo de origem restaurará os dados selecionados para sua aparência original.
Observação
O Shell normalmente usa delete-on-paste quando uma operação de recortar/colar é usada para mover arquivos. As operações de exclusão ao colar com objetos do Shell normalmente usam uma movimentação otimizada para mover os arquivos. Para manipular a transferência de dados do Shell corretamente, seu aplicativo deve ser capaz de detectar e manipular operações de exclusão ao colar.
O requisito essencial para excluir ao colar é que o destino deve relatar o resultado da operação à origem. No entanto, as técnicas padrão da Área de Transferência não podem ser usadas para implementar a exclusão na colagem porque elas não fornecem uma maneira para o destino se comunicar com a origem. Em vez disso, o aplicativo de destino usa o método IDataObject::SetData do objeto de dados para relatar o resultado ao objeto de dados. O objeto de dados pode então se comunicar com a origem por meio de uma interface privada.
O procedimento básico para uma operação de exclusão ao colar é o seguinte:
- A origem marca a exibição na tela dos dados selecionados.
- A origem cria um objeto de dados. Ele indica uma operação de corte adicionando o formato CFSTR_PREFERREDDROPEFFECT com um valor de dados de DROPEFFECT_MOVE.
- A origem coloca o objeto de dados na área de transferência usando OleSetClipboard.
- O destino recupera o objeto de dados da área de transferência usando OleGetClipboard.
- O destino extrai os dados CFSTR_PREFERREDDROPEFFECT . Se estiver definido como apenas DROPEFFECT_MOVE, o destino poderá fazer uma movimentação otimizada ou simplesmente copiar os dados.
- Se o destino não fizer uma movimentação otimizada, ele chamará o método IDataObject::SetData com o formato CFSTR_PERFORMEDDROPEFFECT definido como DROPEFFECT_MOVE.
- Quando a colagem estiver concluída, o destino chamará o método IDataObject::SetData com o formato CFSTR_PASTESUCCEEDED definido como DROPEFFECT_MOVE.
- Quando o método IDataObject::SetData da origem é chamado com o formato CFSTR_PASTESUCCEEDED definido como DROPEFFECT_MOVE, ele deve verificar se também recebeu o formato de CFSTR_PERFORMEDDROPEFFECT definido como DROPEFFECT_MOVE. Se ambos os formatos forem enviados pelo destino, a origem terá que excluir os dados. Se apenas o formato CFSTR_PASTESUCCEEDED for recebido, a fonte pode simplesmente remover os dados de sua exibição. Se a transferência falhar, a origem atualizará a exibição para sua aparência original.
Transferindo dados de e para pastas virtuais
Cenário: um usuário arrasta um objeto de ou o solta em uma pasta virtual.
As pastas virtuais contêm objetos que geralmente não fazem parte do sistema de arquivos. Algumas pastas virtuais, como a Lixeira, podem representar dados armazenados no disco rígido, mas não como objetos comuns do sistema de arquivos. Alguns podem representar dados armazenados que estão em um sistema remoto, como um PC portátil ou um site FTP. Outros, como a pasta Impressoras, contêm objetos que não representam dados armazenados. Embora algumas pastas virtuais façam parte do sistema, os desenvolvedores também podem criar e instalar pastas virtuais personalizadas implementando uma extensão de namespace.
Independentemente do tipo de dados ou de como eles são armazenados, os objetos de pasta e arquivo contidos em uma pasta virtual são apresentados pelo Shell como se fossem arquivos e pastas normais. É responsabilidade da pasta virtual pegar quaisquer dados que ela contenha e apresentá-los ao Shell adequadamente. Esse requisito significa que as pastas virtuais normalmente oferecem suporte a transferências de dados de arrastar e soltar e da Área de Transferência.
Há, portanto, dois grupos de desenvolvedores que precisam se preocupar com a transferência de dados de e para pastas virtuais:
- Desenvolvedores cujos aplicativos precisam aceitar dados transferidos de uma pasta virtual.
- Desenvolvedores cujas extensões de namespace precisam oferecer suporte adequado à transferência de dados.
Aceitando dados de uma pasta virtual
As pastas virtuais podem representar praticamente qualquer tipo de dados e podem armazenar esses dados da maneira que quiserem. Algumas pastas virtuais podem realmente conter arquivos e pastas normais do sistema de arquivos. Outros podem, por exemplo, empacotar todos os seus objetos em um único documento ou banco de dados.
Quando um objeto de sistema de arquivos é transferido para um aplicativo, o objeto de dados normalmente contém um formato de CF_HDROP com o caminho totalmente qualificado do objeto. Seu aplicativo pode extrair essa cadeia de caracteres e usar as funções normais do sistema de arquivos para abrir o arquivo e extrair seus dados. No entanto, como as pastas virtuais normalmente não contêm objetos normais do sistema de arquivos, elas geralmente não usam CF_HDROP.
Em vez de CF_HDROP, os dados são normalmente transferidos de pastas virtuais com os formatos CFSTR_FILEDESCRIPTOR/ CFSTR_FILECONTENTS. O formato CFSTR_FILECONTENTS tem duas vantagens em relação ao CF_HDROP:
- Nenhum método específico de armazenamento de dados é assumido.
- O formato é mais flexível. Ele suporta três mecanismos de transferência de dados: um objeto de memória global, uma interface IStream ou uma interface IStorage.
Objetos de memória global raramente são usados para transferir dados de ou para objetos virtuais porque os dados devem ser copiados para a memória em sua totalidade. Transferir um ponteiro de interface quase não requer memória e é muito mais eficiente. Com arquivos muito grandes, um ponteiro de interface pode ser o único mecanismo prático de transferência de dados. Normalmente, os dados são representados por um ponteiro IStream, porque essa interface é um pouco mais flexível do que IStorage. O destino extrai o ponteiro do objeto de dados e usa os métodos de interface para extrair os dados.
Para obter mais informações sobre como manipular os formatos de CFSTR_FILECONTENTS CFSTR_FILEDESCRIPTOR/, consulte Usando o formato CFSTR_FILECONTENTS para extrair dados de um arquivo.
Transferindo dados de e para uma extensão NameSpace
Ao implementar uma extensão de namespace, você normalmente desejará oferecer suporte a recursos de arrastar e soltar. Siga as recomendações para fontes de queda e metas discutidas nas Diretrizes Gerais. Em particular, uma extensão de namespace deve:
- Ser capaz de lidar com os CFSTR_FILEDESCRIPTOR/formatos CFSTR_FILECONTENTS. Esses dois formatos são normalmente usados para transferir objetos de e para extensões de namespace.
- Ser capaz de lidar com movimentos otimizados. O Shell espera que os objetos do Shell sejam movidos com um movimento otimizado.
- Ser capaz de lidar com uma operação de exclusão ao colar . O Shell usa excluir-on-paste quando os objetos são movidos do Shell com uma operação de recortar/colar.
- Ser capaz de lidar com a transferência de dados através de uma interface IStream ou IStorage. A transferência de dados de ou para uma pasta virtual é normalmente manipulada transferindo um desses dois ponteiros de interface, normalmente um ponteiro IStream . Em seguida, o destino chama os métodos de interface para extrair os dados:
-
- Como uma fonte de recebimento, a extensão de namespace deve extrair os dados do armazenamento e passá-los por essa interface para o destino.
- Como um destino de descarte, uma extensão de namespace deve aceitar dados de uma fonte por meio dessa interface e armazená-los corretamente.
-
Descartando arquivos na Lixeira
Cenário: O usuário solta um arquivo na Lixeira. Seu aplicativo ou extensão de namespace exclui o arquivo original.
A Lixeira é uma pasta virtual usada como repositório para arquivos que não são mais necessários. Contanto que a Lixeira não tenha sido esvaziada, o usuário poderá recuperar o arquivo posteriormente e devolvê-lo ao sistema de arquivos.
Na maioria das vezes, a transferência de objetos do Shell para a Lixeira funciona como qualquer outra pasta. No entanto, quando um usuário solta um arquivo na Lixeira, a origem precisa excluir o original, mesmo que o feedback da pasta indique uma operação de cópia. Normalmente, uma fonte de recebimento não tem como saber em qual pasta seu objeto de dados foi descartado. No entanto, para sistemas Windows 2000 e posteriores, quando um objeto de dados é descartado na Lixeira, o Shell chamará o método IDataObject::SetData do objeto de dados com um formato de CFSTR_TARGETCLSID definido como o identificador de classe (CLSID) (CLSID_RecycleBin) da Lixeira. Para manipular o caso da Lixeira corretamente, seu objeto de dados deve ser capaz de reconhecer esse formato e comunicar as informações à fonte por meio de uma interface privada.
Observação
Quando IDataObject::SetData é chamado com um formato de CFSTR_TARGETCLSID definido como CLSID_RecycleBin, a fonte de dados deve fechar todos os identificadores abertos para os objetos que estão sendo transferidos antes de retornar do método. Caso contrário, você pode criar violações de compartilhamento.
Criando e importando arquivos de sucata
Cenário: um usuário arrasta alguns dados do arquivo de dados de um aplicativo OLE e os solta na área de trabalho ou no Windows Explorer.
O Windows permite que os usuários arrastem um objeto do arquivo de dados de um aplicativo OLE e o soltem na área de trabalho ou em uma pasta do sistema de arquivos. Essa operação cria um arquivo de scrap, que contém os dados ou um link para os dados. O nome do arquivo é obtido do nome abreviado registrado para o CLSID do objeto e os dados CF_TEXT . Para que o Shell crie um arquivo de scrap contendo dados, a interface IDataObject do aplicativo deve oferecer suporte ao formato CF_EMBEDSOURCE Área de Transferência. Para criar um arquivo contendo um link, IDataObject deve oferecer suporte ao formato CF_LINKSOURCE.
Há também três recursos opcionais que um aplicativo pode implementar para oferecer suporte a arquivos de scrap:
- Suporte de ida e volta
- Formatos de dados armazenados em cache
- Renderização atrasada
Suporte de ida e volta
Uma viagem de ida e volta envolve a transferência de um objeto de dados para outro contêiner e, em seguida, de volta para o documento original. Por exemplo, um usuário pode transferir um grupo de células de uma planilha para a área de trabalho, criando um arquivo de scrap com os dados. Se o usuário transferir o scrap de volta para a planilha, os dados precisarão ser integrados ao documento como eram antes da transferência original.
Quando o Shell cria o arquivo de scrap, ele representa os dados como um objeto de incorporação. Quando a sucata é transferida para outro contêiner, ela é transferida como um objeto de incorporação, mesmo que esteja sendo retornada ao documento original. Seu aplicativo é responsável por determinar o formato de dados contidos no scrap e colocar os dados de volta em seu formato nativo, se necessário.
Para estabelecer o formato do objeto incorporado, determine seu CLSID recuperando o formato CF_OBJECTDESCRIPTOR do objeto. Se o CLSID indicar um formato de dados que pertence ao aplicativo, ele deverá transferir os dados nativos em vez de chamar OleCreateFromData.
Formatos de dados armazenados em cache
Quando o Shell cria um arquivo de scrap, ele verifica o registro para a lista de formatos disponíveis. Por padrão, há dois formatos disponíveis: CF_EMBEDSOURCE e CF_LINKSOURCE. No entanto, há vários cenários em que os aplicativos podem precisar ter arquivos de scrap em diferentes formatos:
- Para permitir que os scraps sejam transferidos para contêineres não-OLE, que não podem aceitar formatos de objeto incorporados.
- Para permitir que conjuntos de aplicativos se comuniquem com um formato privado.
- Para facilitar o manuseio de viagens de ida e volta.
Os aplicativos podem adicionar formatos ao scrap armazenando-os em cache no registro. Existem dois tipos de formatos armazenados em cache:
- Formatos de cache prioritários. Para esses formatos, os dados são copiados em sua totalidade para o scrap do objeto de dados.
- Formatos renderizados por atraso. Para esses formatos, o objeto de dados não é copiado para o scrap. Em vez disso, a renderização é atrasada até que um destino solicite os dados. A renderização de atraso é discutida em mais detalhes na próxima seção.
Para adicionar um cache de prioridade ou formato renderizado por atraso, crie uma subchave DataFormat sob a chave CLSID do aplicativo que é a fonte dos dados. Sob essa subchave, crie uma subchave PriorityCacheFormats ou DelayRenderFormats . Para cada cache de prioridade ou formato renderizado por atraso, crie uma subchave numerada começando com zero. Defina o valor dessa chave como uma cadeia de caracteres com o nome registrado do formato ou um valor #X, onde X representa o número de formato de um formato padrão da Área de Transferência.
O exemplo a seguir mostra formatos armazenados em cache para dois aplicativos. O aplicativo MyProg1 tem o formato rich text como um formato de cache prioritário e um formato privado "My Format" como um formato renderizado por atraso. O aplicativo MyProg2 tem o formato CF_BITMAP (#8") como um formato de cache prioritário.
HKEY_CLASSES_ROOT
CLSID
{GUID}
(Default) = MyProg1
DataFormats
PriorityCacheFormats
0
(Default) = Rich Text Format
DelayRenderFormats
0
(Default) = My Format
{GUID}
(Default) = MyProg2
DataFormats
PriorityCacheFormats
0
(Default) = #8
Formatos adicionais podem ser adicionados criando subchaves numeradas adicionais.
Renderização atrasada
Um formato de renderização atrasada permite que um aplicativo crie um arquivo de scrap, mas atrase a despesa de renderização dos dados até que eles sejam solicitados por um destino. A interface IDataObject de um scrap oferecerá os formatos de renderização atrasados para o destino junto com dados nativos e armazenados em cache. Se o destino solicitar um formato de renderização atrasado, o Shell executará o aplicativo e fornecerá os dados ao destino a partir do objeto ativo.
Observação
Como a renderização atrasada é um pouco arriscada, ela deve ser usada com cautela. Ele não funcionará se o servidor não estiver disponível ou em aplicativos que não estejam habilitados para OLE.
Arrastando e soltando objetos do shell de forma assíncrona
Cenário: um usuário transfere um grande bloco de dados da origem para o destino. Para evitar o bloqueio de ambos os aplicativos por um período significativo de tempo, o destino extrai os dados de forma assíncrona.
Normalmente, arrastar e soltar é uma operação síncrona. Em resumo:
- A origem de soltar chama DoDragDrop e bloqueia seu thread principal até que a função retorne. O bloqueio do thread primário normalmente bloqueia o processamento da interface do usuário.
- Depois que o método IDropTarget::D rop do destino é chamado, o destino extrai os dados do objeto de dados em seu thread primário. Esse procedimento normalmente bloqueia o processamento da interface do usuário do destino durante o processo de extração.
- Depois que os dados forem extraídos, o destino retornará a chamada IDropTarget::D rop, o sistema retornará DoDragDrop e ambos os threads poderão continuar.
Em suma, a transferência de dados síncrona pode bloquear os threads primários de ambos os aplicativos por uma quantidade significativa de tempo. Em particular, ambos os threads devem aguardar enquanto o destino extrai os dados. Para pequenas quantidades de dados, o tempo necessário para extrair dados é pequeno e a transferência de dados síncrona funciona muito bem. No entanto, a extração síncrona de grandes quantidades de dados pode causar longos atrasos e interferir na interface do usuário do destino e da origem.
A interface IAsyncOperation/IDataObjectAsyncCapability é uma interface opcional que pode ser implementada por um objeto de dados. Ele dá ao destino de soltar a capacidade de extrair dados do objeto de dados de forma assíncrona em um thread em segundo plano. Depois que a extração de dados é entregue ao thread em segundo plano, os threads primários de ambos os aplicativos ficam livres para continuar.
Usando IASyncOperation/IDataObjectAsyncCapability
Observação
A interface foi originalmente chamada IAsyncOperation, mas isso foi posteriormente alterado para IDataObjectAsyncCapability. Caso contrário, as duas interfaces serão idênticas.
O objetivo de IAsyncOperation/IDataObjectAsyncCapability é permitir que a origem e o destino de recebimento negociem se os dados podem ser extraídos de forma assíncrona. O procedimento a seguir descreve como a origem de recebimento usa a interface:
- Crie um objeto de dados que exponha IAsyncOperation/IDataObjectAsyncCapability.
- Chame SetAsyncMode com fDoOpAsync definido como VARIANT_TRUE para indicar que uma operação assíncrona é suportada.
- Depois que DoDragDrop retornar, chame InOperation:
- Se o InOperation falhar ou retornar VARIANT_FALSE, uma transferência de dados síncrona normal ocorreu e o processo de extração de dados foi concluído. A fonte deve fazer qualquer limpeza necessária e prosseguir.
- Se InOperation retornar VARIANT_TRUE, os dados estarão sendo extraídos de forma assíncrona. As operações de limpeza devem ser tratadas por EndOperation.
- Solte o objeto de dados.
- Quando a transferência de dados assíncrona é concluída, o objeto de dados normalmente notifica a origem por meio de uma interface privada.
O procedimento a seguir descreve como o destino de recebimento usa a interface IAsyncOperation/IDataObjectAsyncCapability para extrair dados de forma assíncrona:
- Quando o sistema chamar IDropTarget::D rop, chame IDataObject::QueryInterface e solicite uma interface IDataObjectAsyncCapability (IID_IAsyncOperation/IID_IDataObjectAsyncCapability) IAsyncOperation/do objeto de dados.
- Chame GetAsyncMode. Se o método retornar VARIANT_TRUE, o objeto de dados dará suporte à extração de dados assíncrona.
- Crie um thread separado para manipular a extração de dados e chamar StartOperation.
- Retorne a chamada IDropTarget::D rop , como faria para uma operação normal de transferência de dados. DoDragDrop retornará e desbloqueará a origem de soltar. Não chame IDataObject::SetData para indicar o resultado de uma operação otimizada de movimentação ou exclusão ao colar. Aguarde até que a operação seja concluída.
- Extraia os dados no thread em segundo plano. O thread principal do destino é desbloqueado e livre para continuar.
- Se a transferência de dados foi uma operação otimizada de movimentação ou exclusão ao colar, chame IDataObject::SetData para indicar o resultado.
- Notifique o objeto de dados de que a extração foi concluída chamando EndOperation.