Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Nu ska vi kombinera allt som vi har lärt oss om användarindata för att skapa ett enkelt ritningsprogram. Här är en skärmdump av programmet:
Användaren kan rita ellipser i flera olika färger och välja, flytta eller ta bort ellipser. För att hålla användargränssnittet enkelt låter programmet inte användaren välja ellipsfärgerna. I stället går programmet automatiskt igenom en fördefinierad lista med färger. Programmet stöder inte andra former än ellipser. Uppenbarligen kommer detta program inte att vinna några priser för grafikprogramvara. Det är dock fortfarande ett användbart exempel att lära sig av. Du kan ladda ned den fullständiga källkoden från Simple Drawing Sample. Det här avsnittet beskriver bara några höjdpunkter.
Ellipser representeras i programmet av en struktur som innehåller ellipsdata (D2D1_ELLIPSE) och färgen (D2D1_COLOR_F). Strukturen definierar också två metoder: en metod för att rita ellipsen och en metod för att utföra träfftestning.
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;
}
};
Programmet använder samma enfärgsborste för att rita fyllningen och konturen för varje ellips och ändra färgen efter behov. I Direct2D är det en effektiv åtgärd att ändra färgen på en helfärgsborste. Därför stöder penselobjektet i färg en SetColor--metod.
Ellipserna lagras i en STL-lista container:
list<shared_ptr<MyEllipse>> ellipses;
Not
shared_ptr är en smartpekarklass som lades till i C++ i TR1 och formaliserades i C++0x. Visual Studio 2010 lägger till stöd för shared_ptr och andra C++0x-funktioner. Mer information finns i artikeln om MSDN Magazine Exploring New C++ and MFC Features in Visual Studio 2010.
Programmet har tre lägen:
- Dragningsläge. Användaren kan rita nya ellipser.
- Markeringsläge. Användaren kan välja en ellips.
- Dra läge. Användaren kan dra en markerad ellips.
Användaren kan växla mellan dragläge och markeringsläge med hjälp av samma kortkommandon som beskrivs i acceleratortabeller. Från markeringsläget växlar programmet till dragläge om användaren klickar på en ellips. Den växlar tillbaka till markeringsläget när användaren släpper musknappen. Den aktuella markeringen lagras som en iterator i listan över ellipser. Hjälpmetoden MainWindow::Selection returnerar en pekare till den markerade ellipsen, eller värdet nullptr om det inte finns någon markering.
list<shared_ptr<MyEllipse>>::iterator selection;
shared_ptr<MyEllipse> Selection()
{
if (selection == ellipses.end())
{
return nullptr;
}
else
{
return (*selection);
}
}
void ClearSelection() { selection = ellipses.end(); }
I följande tabell sammanfattas effekterna av musindata i vart och ett av de tre lägena.
| Musinmatning | Dragningsläge | Markeringsläge | Dra läge |
|---|---|---|---|
| Vänster knapp nedåt | Ange musfångst och börja rita en ny ellips. | Släpp den aktuella markeringen och utför ett träfftest. Om en ellips träffas samlar du in markören, väljer ellipsen och växlar till dragläge. | Ingen åtgärd. |
| Musflytt | Om den vänstra knappen är nere ändrar du storlek på ellipsen. | Ingen åtgärd. | Flytta den markerade ellipsen. |
| Vänster knapp uppåt | Sluta rita ellipsen. | Ingen åtgärd. | Växla till markeringsläge. |
Följande metod i klassen MainWindow hanterar WM_LBUTTONDOWN meddelanden.
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);
}
Muskoordinater skickas till den här metoden i bildpunkter och konverteras sedan till DIP:er. Det är viktigt att inte blanda ihop dessa två enheter. Till exempel använder funktionen DragDetect pixlar, men ritning och träfftestning använder DIP:er. Den allmänna regeln är att funktioner som är relaterade till windows- eller musindata använder pixlar, medan Direct2D och DirectWrite använder DIP:er. Testa alltid programmet med en hög DPI-inställning och kom ihåg att markera programmet som DPI-medvetet. Mer information finns i DPI och Device-Independent Pixels.
Här är koden som hanterar WM_MOUSEMOVE meddelanden.
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);
}
}
Logiken för att ändra storlek på en ellips beskrevs tidigare i avsnittet Exempel: Ritningscirklar. Observera även anropet till InvalidateRect. Detta säkerställer att fönstret är ommålat. Följande kod hanterar WM_LBUTTONUP meddelanden.
void MainWindow::OnLButtonUp()
{
if ((mode == DrawMode) && Selection())
{
ClearSelection();
InvalidateRect(m_hwnd, NULL, FALSE);
}
else if (mode == DragMode)
{
SetMode(SelectMode);
}
ReleaseCapture();
}
Som du ser har alla meddelandehanterare för musindata för förgreningskod, beroende på aktuellt läge. Det är en acceptabel design för detta ganska enkla program. Det kan dock snabbt bli för komplext om nya lägen läggs till. För ett större program kan en MVC-arkitektur (model-view-controller) vara en bättre design. I den här typen av arkitektur separeras -kontrollanten, som hanterar användarindata, från modell, som hanterar programdata.
När programmet växlar lägen ändras markören för att ge feedback till användaren.
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);
}
Kom slutligen ihåg att ange markören när fönstret tar emot ett WM_SETCURSOR meddelande:
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(hCursor);
return TRUE;
}
break;
Sammanfattning
I den här modulen har du lärt dig hur du hanterar mus- och tangentbordsindata. hur du definierar kortkommandon; och hur du uppdaterar markörbilden så att den återspeglar programmets aktuella tillstånd.