Este artigo foi traduzido por máquina.

Windows com C++

DirectComposition: Um API de Modo Retido para controlar tudo

Kenny Kerr

Baixar o código de exemplo

Kenny KerrGráficos APIs tendem a cair em um dos dois campos diferentes. Existem as APIs de modo imediato, com exemplos bem conhecidos, incluindo o Direct2D e Direct3D. Depois, há as APIs de modo retido como Windows Presentation Foundation (WPF) ou qualquer XAML ou declarativa API. Navegadores modernos oferecem uma clara distinção entre os modos de dois gráficos, com Scalable Vector Graphics, fornecendo uma API de modo retido e o elemento Canvas, fornecendo uma API de modo imediato.

Modo retido assume que a API gráfica irá reter alguma representação da cena, como um gráfico ou uma árvore de objetos, que podem então ser manipulados ao longo do tempo. Isto é conveniente e simplifica o desenvolvimento de aplicações interativas. Em contraste, uma API de modo imediato não inclui um grafo de cena interna e em vez disso depende a aplicação ao construir a cena usando uma seqüência de comandos de desenho. Isso traz benefícios tremendo desempenho. Uma API de modo imediato como Direct2D vai normalmente de buffer dados de vértice de várias geometrias, coalescem um grande número de comandos de desenho e muito mais. Isto é particularmente benéfico com um pipeline de processamento de texto, como glifos primeiro precisam ser escrito para uma textura e incluídas na amostra para baixo antes de clear-tipo de filtragem é aplicada e o texto faz o seu caminho para o destino de processamento. Esta é uma das razões por que muitos outros gráficos APIs e cada vez mais muitos aplicativos de terceiros, agora dependem de Direct2D e DirectWrite para processamento de texto.

A escolha entre o modo imediato e mantidos-modo tradicionalmente desceu para um trade-off entre desempenho e produtividade. Os desenvolvedores poderiam escolher a API de modo imediato Direct2D para desempenho absoluto ou a API de modo retido do WPF para produtividade ou conveniência. DirectComposition muda esta equação, permitindo que os desenvolvedores misturar os dois muito mais naturalmente. Ele borra a linha entre APIs de modo imediato e modo retido porque ele fornece um modo retido para gráficos, mas sem impor qualquer sobrecarga de memória ou desempenho. Ele consegue essa façanha centrando-se na composição de bitmap em vez de tentar competir com outros gráficos APIs. DirectComposition simplesmente fornece a árvore visual e a infra-estrutura de composição tal que bitmaps processado com outras tecnologias pode ser facilmente manipulada e compor juntos. E ao contrário do WPF, DirectComposition é parte integrante da infra-estrutura de gráficos do sistema operacional e evita todos os problemas de desempenho e espaço aéreo que tradicionalmente têm atormentado aplicativos WPF.

Se você leu minhas duas colunas anteriores sobre DirectComposition (msdn.microsoft.com/magazine/dn745861 e msdn.microsoft.com/magazine/dn786854), você já deve ter um sentido de que o mecanismo de composição é capaz. Agora eu quero tornar isso muito mais explícito, mostrando-lhe como você pode usar DirectComposition para manipular elementos visuais desenhados com Direct2D, de uma forma que é muito atraente para os desenvolvedores acostumados com as APIs de modo retido. Eu vou mostrar como criar uma simples janela que apresenta círculos como "objetos" que podem ser criados e movimentados, com suporte completo para o teste de visitas e altera a ordem Z. Você pode ver o que isto pode parecer com o exemplo em Figura 1.

Arrastando círculos em torno
Figura 1 arrastando círculos em torno

Embora os círculos em Figura 1 são desenhados com Direct2D, a aplicativo desenha um círculo apenas uma vez para uma superfície de composição. Esta superfície de composição então é compartilhado entre os visuais de composição em uma árvore visual vinculada à janela. Cada visual define um deslocamento relativo para a janela na qual seu conteúdo — a superfície de composição — é posicionado e, finalmente, processada pelo mecanismo de composição. O usuário pode criar novos círculos e movê-los com um mouse, caneta ou o dedo. Cada vez que um círculo está seleccionado, move-se para o topo da ordem Z então ele aparece acima de quaisquer outros círculos na janela. Enquanto certamente não preciso de uma API de modo retido para alcançar um efeito tão simples, ela serve como um bom exemplo de como a API DirectComposition funciona juntamente com o Direct2D para conseguir alguns efeitos visuais poderosos. O objetivo é construir um aplicativo interativo cujo manipulador WM_PAINT não é responsável por manter os pixels da janela atualizados.

Vou começar com uma nova classe de SampleWindow que deriva do modelo de classe de janela que eu apresentei na minha coluna anterior. O modelo de classe de janela apenas simplifica a mensagem despachando em C++:

struct SampleWindow : Window<SampleWindow>
{
};

Como com qualquer aplicativo Windows moderno, preciso lidar com a dinâmica DPI dimensionamento então vou acrescentar dois membros de ponto flutuante para acompanhar o DPI dimensionamento factores para o eixo X e Y:

float m_dpiX = 0.0f;
float m_dpiY = 0.0f;

Você pode inicializar estas sob demanda, como eu ilustrei na minha coluna anterior, ou dentro de seu manipulador de mensagens WM_CREATE. De qualquer forma, você precisa chamar a função MonitorFromWindow para determinar o monitor que tem a maior área de interseção com a nova janela. Então você simplesmente chamar a função GetDpiForMonitor para recuperar seus valores eficazes de DPI. Já ilustrado-isto várias vezes em colunas anteriores e cursos, então não reitero isso aqui.

Vou usar um objeto de geometria de elipse Direct2D para descrever o círculo a ser desenhada para mais tarde poder usar esse mesmo objeto de geometria para teste de acertos. Enquanto é mais eficiente para desenhar uma estrutura de D2D1_ELLIPSE do que um objeto de geometria, o objeto geometry fornece teste de clique e desenho será mantido. Eu vou manter o controle de ambos a fábrica Direct2D e a geometria da elipse:

ComPtr<ID2D1Factory2> m_factory;
ComPtr<ID2D1EllipseGeometry> m_geometry;

Na minha coluna anterior mostrei a você como criar um objeto de dispositivo Direct2D diretamente com a função de D2D1CreateDevice, em vez de por meio de uma fábrica de Direct2D. Esta é certamente uma forma aceitável de continuar, mas há um senão. Recursos de fábrica Direct2D, enquanto eles são independentes de dispositivo e não precisam ser recriados quando ocorre perda do dispositivo, podem ser usados somente com dispositivos de Direct2D, criados pela mesma fábrica Direct2D. Porque eu quero criar a geometria da elipse na frente, eu preciso de um objeto de fábrica Direct2D para criá-lo. Eu poderia, talvez, esperar até que eu criei o Direct2D dispositivo com a função de D2D1CreateDevice e então recuperar a fábrica subjacente com o método e em seguida, usar esse objeto de fábrica para criar a geometria, mas que parece um pouco artificial. Em vez disso, vou criar uma fábrica de Direct2D e usá-lo para criar a geometria da elipse e o objeto de dispositivo conforme necessário. Figura 2 ilustra como criar os objetos de fábrica e geometria do Direct2D.

Figura 2 Criando a fábrica Direct2D e objetos de geometria

void CreateFactoryAndGeometry()
{
  D2D1_FACTORY_OPTIONS options = {};
  #ifdef _DEBUG
  options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
  #endif
  HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
                       options,
                       m_factory.GetAddressOf()));
  D2D1_ELLIPSE const ellipse = Ellipse(Point2F(50.0f, 50.0f),
                                       49.0f,
                                       49.0f);
  HR(m_factory->CreateEllipseGeometry(ellipse,
                                      m_geometry.GetAddressOf()));
}

O método de CreateFactoryAndGeometry então pode ser chamado pelo construtor do SampleWindow para preparar estes dispositivo -­recursos independentes. Como você pode ver, a elipse é definida por um pixels de ponto 50 centro ao longo do eixo X e Y, bem como um raio de 49 pixels para o radius de X e Y, esta elipse, se tornando um círculo. Eu vou estar a criar uma superfície de 100 x 100 composição. Eu escolhi um raio de 49 pixels porque o traço padrão desenhado pelo Direct2D localiza-se entre o perímetro e caso contrário poderia ser recortado.

Em seguida estão os recursos específicos do dispositivo. Preciso de um dispositivo de apoio Direct3D, um dispositivo de composição para confirmar as alterações para a árvore visual, um alvo de composição para manter a árvore visual vivo, um visual de raiz que representará o pai de todos os visuais do círculo e uma superfície de composição compartilhado:

ComPtr<ID3D11Device> m_device3D;
ComPtr<IDCompositionDesktopDevice> m_device;
ComPtr<IDCompositionTarget> m_target;
ComPtr<IDCompositionVisual2> m_rootVisual;
ComPtr<IDCompositionSurface> m_surface;

Eu apresentei esses vários objetos em meus artigos anteriores do DirectX e, em particular, nas minhas duas colunas anteriores na DirectComposition. Também discutido e ilustrado como o você deve lidar com perda e criação do dispositivo para que eu não vou repetir aqui. Chamarei apenas o método de CreateDevice2D que precisa ser atualizado para usar a fábrica Direct2D criada anteriormente:

ComPtr<ID2D1Device> CreateDevice2D()
{
  ComPtr<IDXGIDevice3> deviceX;
  HR(m_device3D.As(&deviceX));
  ComPtr<ID2D1Device> device2D;
  HR(m_factory->CreateDevice(deviceX.Get(), 
    device2D.GetAddressOf()));
  return device2D;
}

Agora vou criar a superfície compartilhada. Preciso de ter cuidado em usar ReleaseAndGetAddressOf método do modelo de classe de ComPtr para garantir que a superfície pode ser recriada com segurança após a perda do dispositivo ou devido a alterações no dimensionamento DPI. Também preciso ter cuidado para preservar o sistema de coordenadas lógico que meu aplicativo está usando enquanto traduzia as dimensões em pixels físicos para a API de DirectComposition:

HR(m_device->CreateSurface(
  static_cast<unsigned>(LogicalToPhysical(100, m_dpiX)),
  static_cast<unsigned>(LogicalToPhysical(100, m_dpiY)),
  DXGI_FORMAT_B8G8R8A8_UNORM,
  DXGI_ALPHA_MODE_PREMULTIPLIED,
  m_surface.ReleaseAndGetAddressOf()));

Eu pode então chamar o método de BeginDraw a superfície composição para receber um contexto de dispositivo de Direct2D com os quais comandos de desenho de buffer:

HR(m_surface->BeginDraw(
  nullptr,
  __uuidof(dc),
  reinterpret_cast<void **>(dc.GetAddressOf()),
  &offset));

E então eu preciso dizer Direct2D como escalar quaisquer comandos de desenho:

dc->SetDpi(m_dpiX,
           m_dpiY);

E eu preciso para transformar a saída para o deslocamento fornecido pelo DirectComposition:

dc->SetTransform(Matrix3x2F::Translation(PhysicalToLogical(offset.x, m_dpiX),
                                         PhysicalToLogical(offset.y, m_dpiY)));

PhysicalToLogical é uma função auxiliar que eu uso rotineiramente para DPI dimensionamento ao combinar APIs que têm diferentes níveis de suporte para dimensionamento DPI (ou nenhum). Você pode ver a função PhysicalToLogical e função correspondente de LogicalToPhysical na Figura 3.

Figura 3-convertendo entre Pixels lógicos e físicos

template <typename T>
static float PhysicalToLogical(T const pixel,
                               float const dpi)
{
  return pixel * 96.0f / dpi;
}
template <typename T>
static float LogicalToPhysical(T const pixel,
                               float const dpi)
{
  return pixel * dpi / 96.0f;
}

Agora eu posso simplesmente desenhar um círculo azul com um pincel de cor sólida criado com este propósito:

ComPtr<ID2D1SolidColorBrush> brush;
D2D1_COLOR_F const color = ColorF(0.0f, 0.5f, 1.0f, 0.8f);
HR(dc->CreateSolidColorBrush(color,
                             brush.GetAddressOf()));

Em seguida, deve limpar o processamento de destino antes de encher a geometria da elipse e, em seguida, acariciando ou desenho sua estrutura de tópicos com o pincel modificado:

dc->Clear();
dc->FillGeometry(m_geometry.Get(),
                 brush.Get());
brush->SetColor(ColorF(1.0f, 1.0f, 1.0f));
dc->DrawGeometry(m_geometry.Get(),
                 brush.Get());

Finalmente, eu deve chamar o método EndDraw para indicar que a superfície está pronta para a composição:

HR(m_surface->EndDraw());

Agora é hora de criar círculos. Em minhas colunas anteriores criei apenas uma única raiz visual, mas esse aplicativo precisa criar visuais sob demanda, então só concluirei que em um método auxiliar conveniente:

ComPtr<IDCompositionVisual2> CreateVisual()
{
  ComPtr<IDCompositionVisual2> visual;
  HR(m_device->CreateVisual(visual.GetAddressOf()));
  return visual;
}

Um dos aspectos interessantes da API DirectComposition é que é efetivamente uma interface somente para o mecanismo de composição. Enquanto retem uma árvore visual para sua janela, ele não fornece qualquer getters que você pode usar para interrogar o árvore visual. Qualquer informação, como de um visual posição ou ordem Z, deve ser mantida diretamente pelo aplicativo. Isto evita sobrecarga de memória desnecessária e também evita potenciais condições de corrida entre visão do aplicativo do mundo e transacional estado do mecanismo de composição. Então eu vou em frente e criar uma estrutura de círculo para manter o controle de posição de cada círculo:

struct Circle
{
  ComPtr<IDCompositionVisual2> Visual;
  float LogicalX = 0.0f;
  float LogicalY = 0.0f;
};

A composição visual representa efetivamente definidores do círculo enquanto o LogicalX e LogicalY campos são os getters. Eu posso definir a posição do visual com a interface IDCompositionVisual2 e posso reter e depois recuperar a sua posição com os outros campos. Isto é necessário para o teste de visitas e para restaurar os círculos após a perda do dispositivo. Para evitar estas ficando fora de sincronização, simplesmente fornecerei um método auxiliar para atualizar o visual objeto baseado a posição lógica. A API de DirectComposition não tem idéia de como o conteúdo pode ser posicionado e dimensionado, então eu preciso fazer os cálculos necessários de DPI:

void UpdateVisualOffset(float const dpiX,
                        float const dpiY)
{
  HR(Visual->SetOffsetX(LogicalToPhysical(LogicalX, dpiX)));
  HR(Visual->SetOffsetY(LogicalToPhysical(LogicalY, dpiY)));
}

Vou adicionar outro método auxiliar para realmente definir o deslocamento lógico do círculo. Este baseia-se no UpdateVisualOffset para garantir que a estrutura do círculo e o objeto visual são sincronizados:

void SetLogicalOffset(float const logicalX,
                      float const logicalY,
                      float const dpiX,
                      float const dpiY)
{
  LogicalX = logicalX;
  LogicalY = logicalY;
  UpdateVisualOffset(dpiX,
                       dpiY);
}

Finalmente, como os círculos são adicionados ao aplicativo, vou precisar um simples Construtor para inicializar a estrutura, apropriando-se de uma referência de IDCompositionVisual2:

Circle(ComPtr<IDCompositionVisual2> && visual,
       float const logicalX,
       float const logicalY,
       float const dpiX,
       float const dpiY) :
  Visual(move(visual))
{
  SetLogicalOffset(logicalX,
                   logicalY,
                   dpiX,
                   dpiY);
}

Eu pode agora acompanhar de círculos do aplicativo com um contêiner padrão lista:

list<Circle> m_circles;

Enquanto estou aqui vou acrescentar também um membro para acompanhar qualquer círculo selecionado:

Circle * m_selected = nullptr;
float m_mouseX = 0.0f;
float m_mouseY = 0.0f;

O deslocamento do mouse também vai ajudar a produzir o movimento natural. Eu vou começar a limpeza do caminho antes de eu olhar para a interação do mouse real que acabará por criar os círculos e permitam-me que movê-los. O CreateDeviceResources método precisa recriar todos os objetos visuais, deve ocorrer a perda do dispositivo, baseado em qualquer círculos criados anteriormente. Não faria se os círculos desaparecerem. Tão bem depois de criar ou recriar a visual de raiz e a superfície compartilhada, eu vou iterar sobre esta lista, criar novos objetos visuais e reposicioná-los para coincidir com o estado existente. Figura 4 ilustra como isso tudo vem junto usando o que eu já estabeleci.

Figura 4 Criando a pilha do dispositivo e a árvore Visual

void CreateDeviceResources()
{
  ASSERT(!IsDeviceCreated());
  CreateDevice3D();
  ComPtr<ID2D1Device> const device2D = CreateDevice2D();
  HR(DCompositionCreateDevice2(
      device2D.Get(),
      __uuidof(m_device),
      reinterpret_cast<void **>(m_device.ReleaseAndGetAddressOf())));
  HR(m_device->CreateTargetForHwnd(m_window,
                                   true,
                                   m_target.ReleaseAndGetAddressOf()));
  m_rootVisual = CreateVisual();
  HR(m_target->SetRoot(m_rootVisual.Get()));
  CreateDeviceScaleResources();
  for (Circle & circle : m_circles)
  {
    circle.Visual = CreateVisual();
    HR(circle.Visual->SetContent(m_surface.Get()));
    HR(m_rootVisual->AddVisual(circle.Visual.Get(), false, nullptr));
    circle.UpdateVisualOffset(m_dpiX, m_dpiY);
  }
  HR(m_device->Commit());
}

A outra parte do serviço de limpeza tem a ver com dimensionamento DPI. A superfície de composição que contém pixels do círculo como processado pelo Direct2D deve ser recriada a escala, e os elementos visuais próprios também devem ser reposicionados para seus deslocamentos são proporcionais um ao outro e para a janela proprietária. O manipulador de mensagem WM_DPICHANGED primeiro recria a superfície de composição — com a ajuda do método CreateDeviceScaleResources — e, em seguida, atualiza o conteúdo e a posição para cada um dos círculos:

if (!IsDeviceCreated()) return;
CreateDeviceScaleResources();
for (Circle & circle : m_circles)
{
  HR(circle.Visual->SetContent(m_surface.Get()));
  circle.UpdateVisualOffset(m_dpiX, m_dpiY);
}
HR(m_device->Commit());

Agora eu vou lidar com a interação de ponteiro. Eu vou deixar o usuário criar novos círculos se o botão esquerdo do mouse é clicado enquanto é pressionada a tecla Control. O manipulador de mensagem WM_LBUTTONDOWN parecida com esta:

if (wparam & MK_CONTROL)
{
  // Create new circle
}
else
{
  // Look for existing circle
}
HR(m_device->Commit());

Assumindo que um novo círculo precisa ser criado, vou começar criando um novo visual e definindo o conteúdo compartilhado antes de adicioná-lo como um filho da raiz visual:

ComPtr<IDCompositionVisual2> visual = CreateVisual();
HR(visual->SetContent(m_surface.Get()));
HR(m_rootVisual->AddVisual(visual.Get(), false, nullptr));

O novo visual é adicionado na frente de qualquer Visual existente. Isso é o AddVisual parâmetro do método segundo no trabalho. Se eu tinha esta definido para true então o novo visual teria sido colocado na parte de trás tem irmãos existentes. Em seguida, preciso adicionar uma estrutura de círculo à lista para sustentar mais tarde visitas teste, perda do dispositivo e dimensionamento DPI:

m_circles.emplace_front(move(visual),
       PhysicalToLogical(LOWORD(lparam), m_dpiX) - 50.0f,
       PhysicalToLogical(HIWORD(lparam), m_dpiY) - 50.0f,
       m_dpiX,
       m_dpiY);

Eu sou cuidadoso para colocar o círculo recém-criado na frente da lista, então naturalmente pode apoiar visitas de teste na mesma ordem que a árvore visual implica. Também inicialmente posicionar o visual então está centrado sobre a posição do mouse. Finalmente, supondo que o usuário não imediatamente solte o mouse, também captura o mouse e manter o controle de qual círculo será potencialmente movido ao redor:

SetCapture(m_window);
m_selected = &m_circles.front();
m_mouseX = 50.0f;
m_mouseY = 50.0f;

O deslocamento do mouse permite-me suavemente arrastar qualquer círculo independentemente de onde o círculo que o ponteiro do mouse desce inicialmente. À procura de um círculo existente é um pouco mais envolvida. Aqui, novamente, eu preciso aplicar manualmente o reconhecimento de DPI. Felizmente, Direct2D isso torna uma brisa. Primeiro, eu preciso iterar sobre os círculos na Z-ordem natural. Felizmente, já coloquei novos círculos na frente da lista é uma simples questão de iteração do começo ao fim:

for (auto circle = begin(m_circles); circle != end(m_circles); ++circle)
{
}

Eu não estou usando um intervalo com base em declaração porque vai ser mais conveniente que na verdade os iteradores útil neste caso. Agora, onde estão os círculos? Bem, cada círculo mantém controle sobre sua posição lógica em relação ao canto superior esquerdo da janela. LPARAM a mensagem mouse contém também a posição do ponteiro físico em relação ao canto superior esquerdo da janela. Mas não é suficiente para traduzi-los para um sistema de coordenadas comum porque preciso de teste para a forma não é um simples retângulo. A forma é definida por um objeto de geometria e Direct2D fornece o método FillContainsPoint para implementar o teste de visitas. O truque é que o objeto geometry fornece apenas a forma do círculo e não sua posição. Para o teste de visitas para trabalhar de forma eficaz, preciso primeiro traduzir a posição do mouse, que é em relação ao objeto de geometria. É muito fácil:

D2D1_POINT_2F const point =
  Point2F(LOWORD(lparam) - LogicalToPhysical(circle->LogicalX, m_dpiX),
          HIWORD(lparam) - LogicalToPhysical(circle->LogicalY, m_dpiY));

Mas eu ainda não estou pronta para chamar o método FillContainsPoint. A outra questão é que o objeto de geometria não sabe nada sobre o destino de processamento. Quando eu usei o objeto geometry para desenhar o círculo, foi o destino de processamento que escalado a geometria para coincidir com os valores DPI do alvo. Então eu preciso de uma maneira de escalar a geometria antes de realizar o teste de visitas para ele vai refletir o tamanho do círculo, correspondente ao que o usuário vê na tela. Mais uma vez, Direct2D vem para o resgate. FillContainsPoint aceita uma matriz de 3 x 2 opcional para transformar a geometria antes de testar se o ponto especificado está contido dentro da forma. Eu simplesmente pode definir uma transformação de escala, dada os valores DPI da janela:

D2D1_MATRIX_3X2_F const transform = Matrix3x2F::Scale(m_dpiX / 96.0f,
                                                      m_dpiY / 96.0f);

O método de FillContainsPoint então me dirá se o ponto está contido dentro do círculo:

BOOL contains = false;
HR(m_geometry->FillContainsPoint(point,
                                 transform,
                                 &contains));
if (contains)
{
  // Reorder and select circle
  break;
}

Se o ponto está contido dentro do círculo, eu preciso reordenar os elementos visuais composição tal que o visual do círculo selecionado está no topo da ordem Z. Pode fazê-lo, removendo o filho visual e adicioná-lo para a frente de qualquer visuais existentes:

HR(m_rootVisual->RemoveVisual(circle->Visual.Get()));
HR(m_rootVisual->AddVisual(circle->Visual.Get(), false, nullptr));

Eu também preciso manter minha lista actualizada, movendo o círculo para a frente da lista:

m_circles.splice(begin(m_circles), m_circles, circle);

Então presumo que o usuário deseja arrastar o círculo em torno de:

SetCapture(m_window);
m_selected = &*circle;
m_mouseX = PhysicalToLogical(point.x, m_dpiX);
m_mouseY = PhysicalToLogical(point.y, m_dpiY);

Aqui, sou cuidadoso para calcular o deslocamento da posição do mouse em relação ao círculo selecionado. Desta forma, o círculo não visualmente "encaixe" para o centro do ponteiro do mouse como é arrastada, proporcionando movimento sem emenda. Respondendo à mensagem WM_MOUSEMOVE permite qualquer círculo selecionado continuar este movimento enquanto um círculo é selecionado:

if (!m_selected) return;
m_selected->SetLogicalOffset(
  PhysicalToLogical(GET_X_LPARAM(lparam), m_dpiX) - m_mouseX,
  PhysicalToLogical(GET_Y_LPARAM(lparam), m_dpiY) - m_mouseY,
  m_dpiX,
  m_dpiY);
HR(m_device->Commit());

Método de SetLogicalOffset a estrutura círculo atualiza a posição lógica, mantida pelo círculo, bem como a posição física da composição visual. Eu também sou cuidadoso para usar as macros GET_X_LPARAM e GET_Y_LPARAM para rachar o LPARAM, ao invés de costume LOWORD e HIWORD macros. Enquanto a posição relatada pela mensagem WM_MOUSEMOVE é relativo para o canto superior esquerdo da janela, isso irá incluir coordenadas negativas se o mouse é capturado e o círculo é arrastado acima ou à esquerda da janela. Como de costume, as alterações para a árvore visual devem ser confirmadas por eles ser realizado. Qualquer movimento chega ao fim no manipulador de mensagem WM_LBUTTONUP liberando o mouse e redefinir o ponteiro de m_selected:

ReleaseCapture();
m_selected = nullptr;

Por fim, concluirei com a melhor parte. A prova mais convincente que é indicativo de gráfico de modo retido é quando você considera o manipulador de mensagem WM_PAINT no Figura 5.

Figura 5 modo retido WM_PAINT mensagem manipulador

void PaintHandler()
{
  try
  {
    if (IsDeviceCreated())
    {
      HR(m_device3D->GetDeviceRemovedReason());
    }
    else
    {
      CreateDeviceResources();
    }
    VERIFY(ValidateRect(m_window, nullptr));
  }
  catch (ComException const & e)
  {
    ReleaseDeviceResources();
  }
}

O método CreateDeviceResources cria a pilha do aparelho na frente. Contanto que nada dê errado, nenhum trabalho adicional é feito pelo manipulador de mensagem WM_PAINT, além de para validar a janela. Se for detectada a perda do dispositivo, os vários blocos de catch irão liberar o dispositivo e invalidar a janela conforme necessário. A próxima mensagem WM_PAINT chegar novamente irá recriar os recursos do dispositivo. Na minha próxima coluna vou te mostrar como você pode produzir efeitos visuais que não são diretamente impulsionados pela entrada do usuário. Como o mecanismo de composição executa mais do processamento sem envolver o aplicativo, é possível que a perda de dispositivo podem ocorrer sem o aplicativo mesmo sabê-lo. É por isso que o GetDeviceRemoved­razão método é chamado. Se o mecanismo de composição detecta perda de dispositivo, ele irá enviar uma mensagem WM_PAINT janela aplicação puramente para que possa verificar a perda do dispositivo, chamando o método de GetDeviceRemovedReason do dispositivo Direct3D. Leve o DirectComposition para um test-drive com o projeto de exemplo que acompanha!


Kenny Kerr é programador de computador, assim como autor da Pluralsight e Microsoft MVP que mora no Canadá. Ele mantém um blog em kennykerr.ca e pode ser seguido no Twitter em twitter.com/kennykerr.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Leonardo Blanco (Leonardo.Blanco@microsoft.com) e James Clarke (James.Clarke@Microsoft.com)