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 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 arquivos da área de transferência para um aplicativo
- Copiando o conteúdo de um arquivo solto em um aplicativo
- Manipulando operações de movimentação otimizadas
- Manipulando operações de exclusão ao colar
- Transferindo dados de e para pastas virtuais
- Soltando arquivos na Lixeira
- Criando e importando arquivos de recorte
- Arrastando e soltando objetos de 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 de arrastar e soltar está em como o objeto de dados chega ao destino. Depois que o destino tiver um ponteiro para a interface IDataObject do objeto de dados, os procedimentos para extrair informações serã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 único cenário de transferência de dados bastante específico. No entanto, as transferências de dados geralmente são mais complexas e podem envolver aspectos de vários cenários. Normalmente, você não sabe, com antecedência, qual cenário realmente precisará lidar. Aqui estão algumas diretrizes gerais a serem lembradas.
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 quantos formatos você puder suportar. Geralmente, você não sabe onde o objeto de dados será descartado. Essa prática aumenta 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/CFSTR_FILEDESCRIPTOR formatos. 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 arrastar 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 dê suporte ao formato CFSTR_SHELLIDLIST usando SHCreateDataObject.
- Os destinos suspensos que desejam raciocinar sobre os itens que estão sendo arrastados usando o modelo de programação de item de shell podem converter um IDataObject em um IShellItemArray usando SHCreateShellItemArrayFromDataObject.
- Use cursores de feedback padrão.
- Suporte para 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 todos os formatos extras que o objeto de dados tem 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 movimentações otimizadas 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 soltura 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 presuma que um formato específico estará presente. O objeto de dados deve enumerar os formatos em ordem de qualidade, começando com o melhor. 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 para arrastar com o botão direito. Você pode personalizar o menu de atalho de 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 CFSTR_FILECONTENTS/CFSTR_FILEDESCRIPTOR formatos. 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. Os exemplos incluem pastas do Windows CE, pastas do protocolo FTP, pastas da Web e pastas CAB. A origem normalmente implementa uma interface IStream para apresentar dados de seu armazenamento como um arquivo.
- Lembre-se de que o Shell pode usar movimentações otimizadas 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 arquivos 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 recortando 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 de â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 no objeto de dados.
Observação
As duas etapas finais deste procedimento são incluídas para fins de integridade. 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 no 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 string 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 nesta 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 criado na etapa 2 não é mais válido e não deve ser usado.
Copiando o conteúdo de um arquivo solto 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 aplicativo.
Depois que o usuário iniciar 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 arrastar.
- Quando a imagem de arrastar atinge a janela de destino, o sistema notifica você 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 no objeto de dados. Se o aplicativo não quiser aceitar nenhum desses formatos, retorne DROPEFFECT_NONE. Para os fins deste cenário, seu aplicativo deve ignorar todos os objetos de dados que não contenham 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 próprio arquivo.
- 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 recorte 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 inserido. 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 da versão 4.71 do Shell, 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 preferida de indicar que os atalhos estão sendo transferidos é usar o formato 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, talvez você queira fazer a operação de forma assíncrona em um thread em segundo plano. Seu thread principal pode prosseguir sem atrasos desnecessários. Para obter uma discussão sobre como lidar com 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 é 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 que seja 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. A transferência de 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 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 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 contido em um dos formatos de CFSTR_FILECONTENTS que o acompanham.
- Examine as estruturas FILEDESCRIPTOR para determinar qual delas 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 em 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 tymed é 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 tymed 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 for 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 Objeto de Dados do Shell.
- Chame GlobalLock para desbloquear o objeto de memória global que você bloqueou na etapa 2.
Manipulando 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 lidar com toda a operação usando a API do Shell. Um exemplo típico é mover arquivos. Depois que o destino tiver o caminho de um arquivo a ser movido, ele poderá 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 lidar com a transferência de dados do Shell corretamente, seu aplicativo deve ser capaz de detectar e lidar com 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 à origem se ela precisa excluir os dados originais.
Uma movimentação otimizada é a operação padrão, com os dados excluídos pelo destino. Para informar à origem que uma movimentação otimizada foi executada:
-
- O destino define o valor pdwEffect recebido 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 CFSTR_PERFORMEDDROPEFFECT definido como DROPEFFECT_NONE. Essa chamada de método é necessária porque alguns destinos de soltar 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 à origem 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 CFSTR_PERFORMEDDROPEFFECT definido como DROPEFFECT_MOVE. Essa chamada de método é necessária porque alguns destinos de soltar podem não definir o parâmetro pdwEffect de DoDragDrop corretamente. O formato CFSTR_PERFORMEDDROPEFFECT é a maneira confiável de indicar que ocorreu um movimento não otimizado.
-
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 removidos imediatamente da exibição. Em vez disso, o aplicativo de origem o marca como selecionado, talvez alterando a fonte ou a cor da tela de fundo. Depois que o aplicativo de destino tiver colado os dados, ele notificará a origem sobre o resultado da operação. Se o alvo executou um movimento otimizado, a fonte pode simplesmente atualizar sua exibição. Se o destino executou um movimento 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 Shell normalmente usam um movimento otimizado para mover os arquivos. Para lidar com a transferência de dados do Shell corretamente, seu aplicativo deve ser capaz de detectar e lidar com 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 ao colar porque não fornecem uma maneira de 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. 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 é concluída, o destino chama 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 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 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 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 todos os dados que ela contém e apresentá-los ao Shell adequadamente. Esse requisito significa que as pastas virtuais normalmente dão suporte a transferências de dados de arrastar e soltar e da área de transferência.
Portanto, existem 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 dar 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 escolherem. 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 do sistema de arquivos é transferido para um aplicativo, o objeto de dados normalmente contém um formato 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 normalmente são transferidos de pastas virtuais com os CFSTR_FILEDESCRIPTOR/formatos CFSTR_FILECONTENTS. O formato CFSTR_FILECONTENTS tem duas vantagens sobre CF_HDROP:
- Nenhum método específico de armazenamento de dados é assumido.
- O formato é mais flexível. Ele dá suporte a 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. A transferência de 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, pois essa interface é um pouco mais flexível 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 lidar com os formatos 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á dar suporte a recursos de arrastar e soltar. Siga as recomendações para fontes e destinos de descarte discutidas nas Diretrizes gerais. Em particular, uma extensão de namespace deve:
- Ser capaz de lidar com os CFSTR_FILEDESCRIPTOR/CFSTR_FILECONTENTS formatos. 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 delete-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 por meio de uma interface IStream ou IStorage. A transferência de dados de ou para uma pasta virtual normalmente é tratada pela transferência de 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 descarte, a extensão de namespace deve extrair os dados do armazenamento e passá-los por essa interface para o destino.
- Como um destino suspenso, uma extensão de namespace deve aceitar dados de uma fonte por meio dessa interface e armazená-los corretamente.
-
Soltando arquivos na Lixeira
Cenário: o usuário solta um arquivo na Lixeira. Sua extensão de aplicativo ou namespace exclui o arquivo original.
A Lixeira é uma pasta virtual usada como repositório de arquivos que não são mais necessários. Desde que a Lixeira não tenha sido esvaziada, o usuário pode recuperar o arquivo posteriormente e devolvê-lo ao sistema de arquivos.
Na maioria das vezes, a transferência de objetos 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 os comentários da pasta indiquem uma operação de cópia. Normalmente, uma fonte de descarte não tem como saber em qual pasta seu objeto de dados foi solto. 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 CFSTR_TARGETCLSID definido como CLSID (identificador de classe) (CLSID_RecycleBin) da Lixeira. Para lidar com 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 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ê poderá criar violações de compartilhamento.
Criando e importando arquivos de recorte
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 recorte, que contém os dados ou um link para os dados. O nome do arquivo é obtido do nome curto registrado para o CLSID do objeto e os dados CF_TEXT . Para que o Shell crie um arquivo de recorte contendo dados, a interface IDataObject do aplicativo deve dar suporte ao formato CF_EMBEDSOURCE Área de Transferência. Para criar um arquivo que contém um link, IDataObject deve dar suporte ao formato CF_LINKSOURCE.
Há também três recursos opcionais que um aplicativo pode implementar para dar suporte a arquivos de recorte:
- 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 sucata com os dados. Se o usuário transferir o recado 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 recorte, ele representa os dados como um objeto de inserção. Quando o refugo é transferido para outro recipiente, ele é transferido como um objeto de incorporação, mesmo que esteja sendo devolvido ao documento original. Seu aplicativo é responsável por determinar o formato de dados contido no recado 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 recorte, ele verifica o registro para obter 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 reversão em formatos diferentes:
- Para permitir que os recados 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 as viagens de ida e volta.
Os aplicativos podem adicionar formatos ao recorte armazenando-os em cache no registro. Há 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 recado do objeto de dados.
- Formatos renderizados com atraso. Para esses formatos, o objeto de dados não é copiado para o sucata. Em vez disso, a renderização é atrasada até que um destino solicite os dados. A renderização de atraso é discutida com mais detalhes na próxima seção.
Para adicionar um cache de prioridade ou um formato renderizado com atraso, crie uma subchave DataFormat na chave CLSID do aplicativo que é a origem dos dados. Nessa subchave, crie uma subchave PriorityCacheFormats ou DelayRenderFormats . Para cada cache de prioridade ou formato renderizado com 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, em que X representa o número de formato de um formato padrão da Área de Transferência.
O exemplo a seguir mostra os 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 com 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 atrasado permite que um aplicativo crie um arquivo de recorte, mas atrase a despesa de renderização dos dados até que eles sejam solicitados por um destino. A interface IDataObject de um recorte 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 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 sejam habilitados para OLE.
Arrastando e soltando objetos de 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 primário até que a função retorne. Bloquear o 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 um período significativo 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 síncrona de dados 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 suspenso 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 de IAsyncOperation, mas isso foi alterado posteriormente para IDataObjectAsyncCapability. Caso contrário, as duas interfaces são idênticas.
A finalidade de IAsyncOperation/IDataObjectAsyncCapability é permitir que a origem e o destino de soltar negociem se os dados podem ser extraídos de forma assíncrona. O procedimento a seguir descreve como a fonte de descarte usa a interface:
- Crie um objeto de dados que exponha IAsyncOperation/IDataObjectAsyncCapability.
- Chame SetAsyncMode com fDoOpAsync definido como VARIANT_TRUE para indicar que há suporte para uma operação assíncrona.
- Depois que DoDragDrop retornar, chame InOperation:
- Se 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 estão sendo extraídos de forma assíncrona. As operações de limpeza devem ser tratadas por EndOperation.
- Libere 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 suspenso 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 IAsyncOperation/IDataObjectAsyncCapability (IID_IAsyncOperation/IID_IDataObjectAsyncCapability) 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 lidar com a extração de dados e chame StartOperation.
- Retorne a chamada IDropTarget::D rop , como faria para uma operação normal de transferência de dados. DoDragDrop retornará e desbloqueará a fonte 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 alvo é desbloqueado e livre para prosseguir.
- 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.