Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Vamos combinar tudo o que aprendemos sobre a entrada do usuário para criar um programa de desenho simples. Aqui está uma captura de tela do programa:
O usuário pode desenhar elipses em várias cores diferentes e selecionar, mover ou excluir elipses. Para manter a interface do usuário simples, o programa não permite que o usuário selecione as cores de elipse. Em vez disso, o programa percorre automaticamente uma lista predefinida de cores. O programa não suporta outras formas além de elipses. Obviamente, este programa não ganhará nenhum prêmio para software gráfico. No entanto, continua a ser um exemplo útil para aprender. Você pode baixar o código-fonte completo do Simple Drawing Sample. Esta seção abordará apenas alguns destaques.
As elipses são representadas no programa por uma estrutura que contém os dados de elipse (D2D1_ELLIPSE) e a cor (D2D1_COLOR_F). A estrutura também define dois métodos: um método para desenhar a elipse e um método para executar o teste de acertos.
struct MyEllipse
{
D2D1_ELLIPSE ellipse;
D2D1_COLOR_F color;
void Draw(ID2D1RenderTarget *pRT, ID2D1SolidColorBrush *pBrush)
{
pBrush->SetColor(color);
pRT->FillEllipse(ellipse, pBrush);
pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black));
pRT->DrawEllipse(ellipse, pBrush, 1.0f);
}
BOOL HitTest(float x, float y)
{
const float a = ellipse.radiusX;
const float b = ellipse.radiusY;
const float x1 = x - ellipse.point.x;
const float y1 = y - ellipse.point.y;
const float d = ((x1 * x1) / (a * a)) + ((y1 * y1) / (b * b));
return d <= 1.0f;
}
};
O programa usa o mesmo pincel de cor sólida para desenhar o preenchimento e contorno para cada elipse, alterando a cor conforme necessário. No Direct2D, alterar a cor de um pincel de cor sólida é uma operação eficiente. Assim, o objeto de pincel de cor sólida suporta um métodoSetColor.
As reticências são armazenadas em uma lista de STL contêiner:
list<shared_ptr<MyEllipse>> ellipses;
Observação
shared_ptr é uma classe de ponteiro inteligente que foi adicionada ao C++ no TR1 e formalizada no C++0x. O Visual Studio 2010 adiciona suporte para shared_ptr e outros recursos do C++0x. Para obter mais informações, consulte o artigo da MSDN Magazine Explorando novos recursos C++ e MFC no Visual Studio 2010.
O programa tem três modos:
- Modo de desenho. O usuário pode desenhar novas elipses.
- Modo de seleção. O usuário pode selecionar uma elipse.
- Modo de arrastar. O usuário pode arrastar uma elipse selecionada.
O usuário pode alternar entre o modo de desenho e o modo de seleção usando os mesmos atalhos de teclado descritos em Tabelas Aceleradoras. No modo de seleção, o programa muda para o modo de arrastar se o usuário clicar em uma elipse. Ele volta para o modo de seleção quando o usuário libera o botão do mouse. A seleção atual é armazenada como um iterador na lista de elipses. O método auxiliar MainWindow::Selection retorna um ponteiro para a elipse selecionada ou o valor nullptr se não houver seleção.
list<shared_ptr<MyEllipse>>::iterator selection;
shared_ptr<MyEllipse> Selection()
{
if (selection == ellipses.end())
{
return nullptr;
}
else
{
return (*selection);
}
}
void ClearSelection() { selection = ellipses.end(); }
A tabela a seguir resume os efeitos da entrada do mouse em cada um dos três modos.
| Entrada do mouse | Modo de Desenho | Modo de seleção | Modo de arrastar |
|---|---|---|---|
| Botão esquerdo para baixo | Defina a captura do mouse e comece a desenhar uma nova elipse. | Solte a seleção atual e execute um teste de acerto. Se uma elipse for atingida, capture o cursor, selecione a elipse e alterne para o modo de arrastar. | Nenhuma ação. |
| Movimento do rato | Se o botão esquerdo estiver para baixo, redimensione a elipse. | Nenhuma ação. | Mova a elipse selecionada. |
| Botão esquerdo para cima | Pare de desenhar a elipse. | Nenhuma ação. | Mude para o modo de seleção. |
O método a seguir na classe MainWindow manipula mensagens WM_LBUTTONDOWN.
void MainWindow::OnLButtonDown(int pixelX, int pixelY, DWORD flags)
{
const float dipX = DPIScale::PixelsToDipsX(pixelX);
const float dipY = DPIScale::PixelsToDipsY(pixelY);
if (mode == DrawMode)
{
POINT pt = { pixelX, pixelY };
if (DragDetect(m_hwnd, pt))
{
SetCapture(m_hwnd);
// Start a new ellipse.
InsertEllipse(dipX, dipY);
}
}
else
{
ClearSelection();
if (HitTest(dipX, dipY))
{
SetCapture(m_hwnd);
ptMouse = Selection()->ellipse.point;
ptMouse.x -= dipX;
ptMouse.y -= dipY;
SetMode(DragMode);
}
}
InvalidateRect(m_hwnd, NULL, FALSE);
}
As coordenadas do mouse são passadas para esse método em pixels e, em seguida, convertidas em DIPs. É importante não confundir estas duas unidades. Por exemplo, a funçãoDragDetect usa pixels, mas o desenho e o teste de acerto usam DIPs. A regra geral é que as funções relacionadas à entrada do Windows ou do mouse usam pixels, enquanto Direct2D e DirectWrite usam DIPs. Sempre teste seu programa em uma configuração de DPI alto e lembre-se de marcar seu programa como compatível com DPI. Para obter mais informações, consulte DPI e Device-Independent Pixels.
Aqui está o código que lida com WM_MOUSEMOVE mensagens.
void MainWindow::OnMouseMove(int pixelX, int pixelY, DWORD flags)
{
const float dipX = DPIScale::PixelsToDipsX(pixelX);
const float dipY = DPIScale::PixelsToDipsY(pixelY);
if ((flags & MK_LBUTTON) && Selection())
{
if (mode == DrawMode)
{
// Resize the ellipse.
const float width = (dipX - ptMouse.x) / 2;
const float height = (dipY - ptMouse.y) / 2;
const float x1 = ptMouse.x + width;
const float y1 = ptMouse.y + height;
Selection()->ellipse = D2D1::Ellipse(D2D1::Point2F(x1, y1), width, height);
}
else if (mode == DragMode)
{
// Move the ellipse.
Selection()->ellipse.point.x = dipX + ptMouse.x;
Selection()->ellipse.point.y = dipY + ptMouse.y;
}
InvalidateRect(m_hwnd, NULL, FALSE);
}
}
A lógica para redimensionar uma elipse foi descrita anteriormente, na seção Exemplo: Desenhando círculos. Observe também a chamada para InvalidateRect. Isso garante que a janela seja repintada. O código a seguir manipula mensagens WM_LBUTTONUP.
void MainWindow::OnLButtonUp()
{
if ((mode == DrawMode) && Selection())
{
ClearSelection();
InvalidateRect(m_hwnd, NULL, FALSE);
}
else if (mode == DragMode)
{
SetMode(SelectMode);
}
ReleaseCapture();
}
Como você pode ver, todos os manipuladores de mensagens para entrada do mouse têm código de ramificação, dependendo do modo atual. Esse é um design aceitável para este programa bastante simples. No entanto, pode rapidamente tornar-se demasiado complexo se forem adicionados novos modos. Para um programa maior, uma arquitetura MVC (model-view-controller) pode ser um design melhor. Nesse tipo de arquitetura, o controlador, que lida com a entrada do usuário, é separado do modelo , que gerencia os dados do aplicativo.
Quando o programa muda de modo, o cursor muda para dar feedback ao usuário.
void MainWindow::SetMode(Mode m)
{
mode = m;
// Update the cursor
LPWSTR cursor;
switch (mode)
{
case DrawMode:
cursor = IDC_CROSS;
break;
case SelectMode:
cursor = IDC_HAND;
break;
case DragMode:
cursor = IDC_SIZEALL;
break;
}
hCursor = LoadCursor(NULL, cursor);
SetCursor(hCursor);
}
E, finalmente, lembre-se de definir o cursor quando a janela receber uma mensagem WM_SETCURSOR:
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(hCursor);
return TRUE;
}
break;
Resumo
Neste módulo, você aprendeu a lidar com a entrada de mouse e teclado; como definir atalhos de teclado; e como atualizar a imagem do cursor para refletir o estado atual do programa.