Compartilhar via


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 em seu aplicativo.

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 de á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 praticamente 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 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 de 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. Enumerar o objeto de dados e escolher o primeiro que você pode consumir.
  • Inclua quantos formatos você puder dar suporte. 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 formatos CFSTR_FILECONTENTS/CFSTR_FILEDESCRIPTOR . Essa abordagem permite que o destino crie um arquivo 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. 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.
  • Remover destinos que desejam raciocinar sobre os itens que estão sendo arrastados usando o modelo de programação de item de shell pode converter um IDataObject em um IShellItemArray usando SHCreateShellItemArrayFromDataObject.
  • Use cursores de comentários padrão.
  • Suporte para arrastar para a esquerda e para a direita.
  • Use o próprio objeto de dados de um objeto inserido. Essa abordagem permite que seu aplicativo recupere 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 inserido do servidor A é arrastado do servidor/contêiner B e descartado no contêiner C. C deve criar um objeto inserido do servidor A, não um objeto inserido do servidor B que contém um objeto inserido do servidor A.
  • Lembre-se de que o Shell pode usar movimentações otimizadas ou operações de exclusão 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 de CF_HDROP, não são predefinidos. Cada formato que você deseja usar deve ser registrado chamando RegisterClipboardFormat.
  • Implemente e registre um destino de remoção 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 suponha que um formato específico esteja presente. O objeto de dados deve enumerar os formatos em ordem de qualidade, começando com o melhor. Portanto, para obter o melhor formato disponível, os aplicativos normalmente enumeram os formatos disponíveis e usam o primeiro formato na enumeração à qual podem dar suporte.
  • Suporte para arrastar à direita. 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 formatos de CFSTR_FILEDESCRIPTOR/CFSTR_FILECONTENTS . Embora os arquivos do sistema de arquivos tenham o formato CF_HDROP , arquivos de provedores como extensões de namespace geralmente usam CFSTR_FILECONTENTS/CFSTR_FILEDESCRIPTOR. Os exemplos incluem pastas Windows CE, pastas FTP (Protocolo de Transferência de Arquivo), 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 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. Em seguida, 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 copia-o 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:

  1. Chame OleGetClipboard para recuperar a interface IDataObject do objeto de dados.
  2. Chame IDataObject::EnumFormatEtc para solicitar um objeto enumerador.
  3. Use a interface IEnumFORMATETC do objeto enumerador para enumerar os formatos contidos pelo objeto de dados.

Observação

As duas etapas finais neste procedimento estão incluídas para integridade. Normalmente, eles 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 lidar.

 

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 às transferências de arrastar e soltar.

A maneira mais simples de recuperar nomes de arquivo de um objeto de dados é o formato CF_HDROP :

  1. 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.

  2. 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 terminada em nulo duplo que contém os caminhos de arquivo totalmente qualificados dos arquivos copiados.

  3. 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 de arquivo nessa lista é usado na próxima etapa para identificar um caminho específico.

  4. 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.

  5. Processe os caminhos de arquivo conforme necessário e cole-os em seu aplicativo.

  6. 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 arquivo (s) e cola-o 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:

  1. Chame RegisterClipboardFormat para registrar os formatos necessários da área de transferência do Shell.
  2. Chame RegisterDragDrop para registrar uma janela de destino e a interface IDropTarget do aplicativo.

Depois que o usuário inicia a operação selecionando um ou mais arquivos e começando a arrastá-los:

  1. O Windows Explorer cria um objeto de dados e carrega os formatos com suporte nele.
  2. O Windows Explorer chama DoDragDrop para iniciar o loop de arrastar.
  3. Quando a imagem de arrastar atinge a janela de destino, o sistema notifica você chamando IDropTarget::D ragEnter.
  4. 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 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 contenham formatos usados para transferir arquivos, como CF_HDROP.
  5. Quando o usuário descarta os dados, o sistema chama IDropTarget::D rop.
  6. 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 inserido, o objeto de dados conterá um formato CF_EMBEDDEDOBJECT. Use técnicas OLE padrão para extrair os dados. Arquivos de recorte sempre contêm um formato de 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 do Shell versão 4.71, um aplicativo indicava que ele estava transferindo um tipo de arquivo de atalho definindo FD_LINKUI no membro dwFlags da estrutura FILEESCRIPTOR . Para versões posteriores do Shell, a maneira preferencial 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 marcar um sinalizador.

Se o processo de extração de dados for longo, convém fazer a operação de forma assíncrona em um thread em segundo plano. O thread primário pode continuar 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 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 o IStream é uma maneira mais simples de representar dados do que o 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 CFSTR_FILECONTENTS de um arquivo específico.

Para extrair um formato de CFSTR_FILECONTENTS :

  1. Extraia o formato CFSTR_FILEDESCRIPTOR como um valor TYMED_HGLOBAL .
  2. O membro hGlobal da estrutura STGMEDIUM retornada aponta para um objeto de memória global. Bloqueie esse objeto passando o valor hGlobal para GlobalLock.
  3. Converta o ponteiro retornado pelo GlobalLock em um ponteiro FILEGROUPDESCRIPTOR . Ele apontará para uma estrutura FILEGROUPDESCRIPTOR seguida por uma ou mais estruturas FILEESCRIPTOR . Cada estrutura FILEESCRIPTOR contém uma descrição de um arquivo contido em um dos formatos de CFSTR_FILECONTENTS que acompanham.
  4. Examine as estruturas FILEESCRIPTOR para determinar qual corresponde ao arquivo que você deseja extrair. O índice baseado em zero dessa estrutura FILEESCRIPTOR é usado para identificar o formato de CFSTR_FILECONTENTS do arquivo. Como o tamanho de um bloco de memória global não é preciso por bytes, use os membros nFileSizeLow e nFileSizeHigh da estrutura para determinar quantos bytes representam o arquivo no objeto de memória global.
  5. 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. Em seguida, o objeto de dados pode escolher seu mecanismo de transferência de dados preferencial.
  6. A estrutura STGMEDIUMretornada por IDataObject::GetData conterá um ponteiro para os dados do arquivo. Examine o membro tymed da estrutura para determinar o mecanismo de transferência de dados.
  7. 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 serã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.
  8. 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 origem 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 é necessário que a origem exclua o arquivo original.

Observação

O Shell normalmente usa uma movimentação otimizada 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.

 

As movimentações otimizadas são tratadas da seguinte maneira:

  1. A origem chama DoDragDrop com o parâmetro dwEffect definido como DROPEFFECT_MOVE para indicar que os objetos de origem podem ser movidos.

  2. O destino recebe o valor DROPEFFECT_MOVE por meio de um de seus métodos IDropTarget , indicando que uma movimentação é permitida.

  3. O destino copia o objeto (movimento não otimizado) ou move o objeto (movimento otimizado).

  4. Em seguida, o destino informa à fonte se ele 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 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 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 ocorreu uma movimentação otimizada.

    Se o destino fez uma movimentação não otimizada, os dados deverão ser excluídos pela origem. Para informar a origem de que uma movimentação não otimizada foi executada:

      • O destino define o valor pdwEffect que recebeu por meio do 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 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 uma movimentação não otimizada ocorreu.
  5. 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 delete-on-paste

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, ele desaparece imediatamente da exibição. Isso pode não ser eficiente e pode levar a problemas de usabilidade se o usuário se preocupar com o que aconteceu com os dados. Uma abordagem alternativa é usar uma operação delete-on-paste.

Com uma operação delete-on-paste, 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 da tela de fundo. Depois que o aplicativo de destino colar os dados, ele notificará a fonte sobre o resultado da operação. Se o destino tiver realizado uma movimentação otimizada, a origem poderá simplesmente atualizar sua exibição. Se o destino tiver realizado uma movimentação normal, a origem também deverá 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 delete-on-paste com objetos Shell normalmente usam uma movimentação otimizada 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 na colagem.

 

O requisito essencial para delete-on-paste é que o destino deve relatar o resultado da operação para a origem. No entanto, as técnicas padrão da Área de Transferência não podem ser usadas para implementar delete-on-paste porque elas 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. Em seguida, o objeto de dados pode se comunicar com a origem por meio de uma interface privada.

O procedimento básico para uma operação delete-on-paste é o seguinte:

  1. A origem marca a exibição de tela dos dados selecionados.
  2. A fonte cria um objeto de dados. Indica uma operação de corte adicionando o formato CFSTR_PREFERREDDROPEFFECT com um valor de dados de DROPEFFECT_MOVE.
  3. A origem coloca o objeto de dados na Área de Transferência usando OleSetClipboard.
  4. O destino recupera o objeto de dados da Área de Transferência usando OleGetClipboard.
  5. O destino extrai os dados CFSTR_PREFERREDDROPEFFECT . Se ele estiver definido como apenas DROPEFFECT_MOVE, o destino poderá fazer uma movimentação otimizada ou simplesmente copiar os dados.
  6. 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.
  7. Quando a colagem for concluída, o destino chamará o método IDataObject::SetData com o formato CFSTR_PASTESUCCEEDED definido como DROPEFFECT_MOVE.
  8. Quando o método IDataObject::SetData da origem é chamado com o formato CFSTR_PASTESUCCEEDED definido como DROPEFFECT_MOVE, ele deve marcar para ver se ele 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 origem poderá simplesmente remover os dados de sua exibição. Se a transferência falhar, a origem atualizará a exibição para sua aparência original.

Transferir 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 computador 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 como eles são armazenados, os objetos de pasta e arquivo contidos por 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 área de transferência.

Portanto, há 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 quiserem. Algumas pastas virtuais podem 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 formatos de CFSTR_FILECONTENTS CFSTR_FILEDESCRIPTOR/. 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. Transferir um ponteiro de interface requer quase nenhuma 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 o IStorage. O destino extrai o ponteiro do objeto de dados e usa os métodos de interface para extrair os dados.

Para obter mais discussões sobre como lidar com 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 de NameSpace

Ao implementar uma extensão de namespace, você normalmente desejará dar suporte a recursos de arrastar e soltar. Siga as recomendações para remover fontes e destinos discutidos em Diretrizes Gerais. Em particular, uma extensão de namespace deve:

  • Seja capaz de lidar com os formatos de CFSTR_FILECONTENTS/CFSTR_FILEDESCRIPTOR . Esses dois formatos normalmente são usados para transferir objetos de e para extensões de namespace.
  • Seja capaz de lidar com movimentos otimizados. O Shell espera que os objetos Shell sejam movidos com uma movimentação otimizada.
  • Seja capaz de lidar com uma operação delete-on-paste . O Shell usa delete-on-paste quando os objetos são movidos do Shell com uma operação de recortar/colar.
  • Seja 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 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 remoção, 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 descarta um arquivo na Lixeira. Seu aplicativo ou extensão de namespace exclui o arquivo original.

A Lixeira é uma pasta virtual que é usada como um repositório para arquivos que não são mais necessários. Desde 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 Shell para a Lixeira funciona muito como qualquer outra pasta. No entanto, quando um usuário descarta 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 suspensa 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 CFSTR_TARGETCLSID definido como o CLSID (identificador de classe) da Lixeira (CLSID_RecycleBin). Para lidar corretamente com o caso da Lixeira, o objeto de dados deve ser capaz de reconhecer esse formato e comunicar as informações à origem 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 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 extração, 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 de CF_TEXT . Para que o Shell crie um arquivo de recorte contendo dados, a interface IDataObject do aplicativo deve dar suporte ao formato de área de transferência CF_EMBEDSOURCE. Para criar um arquivo que contenha um link, IDataObject deve dar suporte ao formato CF_LINKSOURCE.

Também há 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 recorte com os dados. Se o usuário transferir a extração 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 extração, ele representa os dados como um objeto de inserção. Quando o recorte é transferido para outro contêiner, ele é transferido como um objeto de inserção, mesmo que esteja sendo retornado ao documento original. Seu aplicativo é responsável por determinar o formato de dados contido na extração e colocar os dados de volta em seu formato nativo, se necessário.

Para estabelecer o formato do objeto inserido, 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 extração, 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 recorte em formatos diferentes:

  • Para permitir que os recortes sejam transferidos para contêineres não OLE, que não podem aceitar formatos de objeto inserido.
  • Para permitir que pacotes de aplicativos se comuniquem com um formato privado.
  • Para facilitar a manipulação de viagens de ida e volta.

Os aplicativos podem adicionar formatos à extração armazenando-os em cache no registro. Há dois tipos de formatos armazenados em cache:

  • Formatos de cache de prioridade. Para esses formatos, os dados são copiados em sua totalidade para a extração do objeto de dados.
  • Formatos renderizados com atraso. Para esses formatos, o objeto de dados não é copiado para o recorte. Em vez disso, a renderização é atrasada até que um destino solicite os dados. A renderização de atraso é discutida mais detalhadamente na próxima seção.

Para adicionar um cache de prioridade ou um formato renderizado com atraso, crie uma subchave DataFormat sob a chave CLSID do aplicativo que é a fonte 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 de Área de Transferência padrão.

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 de prioridade e um formato privado "Meu Formato" como um formato renderizado com atraso. O aplicativo MyProg2 tem o formato de CF_BITMAP (#8") como um formato de cache de prioridade.

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 renderizar os 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, juntamente 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 para o destino do objeto ativo.

Observação

Como a renderização atrasada é um pouco arriscada, ela deve ser usada com cuidado. Ele não funcionará se o servidor não estiver disponível ou em aplicativos que não estão habilitados para OLE.

 

Arrastando e soltando objetos 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:

  1. A origem de soltar chama DoDragDrop e bloqueia seu thread primário até que a função retorne. O bloqueio do thread primário normalmente bloqueia o processamento da interface do usuário.
  2. 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.
  3. 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 de dados síncrona funciona muito bem. No entanto, extrair de forma síncrona grandes quantidades de dados pode causar atrasos longos 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 fornece 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 são livres para continuar.

Usando IASyncOperation/IDataObjectAsyncCapability

Observação

A interface foi originalmente denominada 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 de soltar e o destino de soltar negociem se os dados podem ser extraídos de forma assíncrona. O procedimento a seguir descreve como a origem da remoção usa a interface :

  1. Crie um objeto de dados que exponha IAsyncOperation/IDataObjectAsyncCapability.
  2. Chame SetAsyncMode com fDoOpAsync definido como VARIANT_TRUE para indicar que há suporte para uma operação assíncrona.
  3. Depois que DoDragDrop retornar, chame InOperation:
    • Se InOperation falhar ou retornar VARIANT_FALSE, ocorrerá uma transferência de dados síncrona normal e o processo de extração de dados será concluído. A origem deve fazer qualquer limpeza necessária e continuar.
    • Se InOperation retornar VARIANT_TRUE, os dados serão extraídos de forma assíncrona. As operações de limpeza devem ser tratadas por EndOperation.
  4. Libere o objeto de dados.
  5. 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 soltar usa a interface IAsyncOperation/IDataObjectAsyncCapability para extrair dados de forma assíncrona:

  1. Quando o sistema chamar IDropTarget::D rop, chame IDataObject::QueryInterface e solicite uma interface IAsyncOperation/IDataObjectAsyncCapability (IID_IAsyncOperation/IID_IDataObjectAsyncCapability) do objeto de dados.
  2. Chame GetAsyncMode. Se o método retornar VARIANT_TRUE, o objeto de dados oferecerá suporte à extração de dados assíncrona.
  3. Crie um thread separado para manipular a extração de dados e chamar StartOperation.
  4. Retorne a chamada IDropTarget::D rop , como faria para uma operação normal de transferência de dados. O 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.
  5. Extraia os dados no thread em segundo plano. O thread primário do destino é desbloqueado e livre para continuar.
  6. 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.
  7. Notifique o objeto de dados de que a extração foi concluída chamando EndOperation.