Uživatelský vstup: rozšířený příklad
Pojďme zkombinovat vše, co jsme se naučili o uživatelském vstupu, a vytvořit jednoduchý kreslicí program. Tady je snímek obrazovky programu:
Uživatel může nakreslit tři tečky v několika různých barvách a vybrat, přesunout nebo odstranit tři tečky. Aby bylo uživatelské rozhraní jednoduché, program nedovolí uživateli vybrat barvy tří teček. Místo toho program automaticky prochází předdefinovaným seznamem barev. Program nepodporuje žádné jiné obrazce než tři tečky. Samozřejmě, tento program nebude vyhrát žádné ocenění pro grafický software. Stále je to ale užitečný příklad, ze něhož se můžete poučit. Kompletní zdrojový kód si můžete stáhnout z ukázky jednoduchého výkresu. Tato část se týká jenom některých zvýraznění.
Elipsy jsou v programu reprezentovány strukturou, která obsahuje tři tečky (D2D1_ELLIPSE) a barvu (D2D1_COLOR_F). Struktura také definuje dvě metody: metodu, která má nakreslit tři tečky a metodu pro provedení testování hitů.
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;
}
};
Program pomocí stejného štětce s plnou barvou nakreslete výplň a obrys pro každé tři tečky a podle potřeby změní barvu. V Režimu Direct2D je změna barvy štětce plnou barvou efektivní operací. Objekt štětce s plnou barvou tedy podporuje metodu SetColor.
Tři tečky se ukládají do seznamu seznamu STL:
list<shared_ptr<MyEllipse>> ellipses;
Poznámka
shared_ptr je třída inteligentního ukazatele, která byla přidána do C++ v TR1 a formalizována v jazyce C++0x. Visual Studio 2010 přidává podporu pro shared_ptr a další funkce C++0x. Další informace naleznete v článku MSDN Magazine Zkoumání nových funkcí C++ a MFC v sadě Visual Studio 2010.
Program má tři režimy:
- Režim kreslení Uživatel může nakreslit nové tři tečky.
- Režim výběru Uživatel může vybrat tři tečky.
- Režim přetažení Uživatel může přetáhnout vybrané tři tečky.
Uživatel může přepínat mezi režimem kreslení a režimem výběru pomocí stejných klávesových zkratek popsaných v tabulkách akcelerátorů . V režimu výběru program přepne do režimu přetažení, pokud uživatel klikne na tři tečky. Přepne zpět do režimu výběru, když uživatel uvolní tlačítko myši. Aktuální výběr se uloží jako iterátor do seznamu tří teček. Pomocná metoda MainWindow::Selection
vrátí ukazatel na vybrané tři tečky, nebo hodnotu nullptr pokud neexistuje žádný výběr.
list<shared_ptr<MyEllipse>>::iterator selection;
shared_ptr<MyEllipse> Selection()
{
if (selection == ellipses.end())
{
return nullptr;
}
else
{
return (*selection);
}
}
void ClearSelection() { selection = ellipses.end(); }
Následující tabulka shrnuje účinky vstupu myši v každém ze tří režimů.
Vstup myši | Režim kreslení | Režim výběru | Režim přetažení |
---|---|---|---|
Levé tlačítko dolů | Nastavte zachytávání myší a začněte kreslit nové tři tečky. | Uvolněte aktuální výběr a proveďte test hitu. Pokud se stiskne tři tečky, zachyťte kurzor, vyberte tři tečky a přepněte do režimu přetažení. | Žádná akce. |
Přesunutí myší | Pokud je levé tlačítko dole, změňte velikost tří teček. | Žádná akce. | Přesunutí vybraného symbolu tří teček |
Levé tlačítko nahoru | Zastavte kreslení tří teček. | Žádná akce. | Přepněte do režimu výběru. |
Následující metoda v MainWindow
třídy zpracovává WM_LBUTTONDOWN zprávy.
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);
}
Souřadnice myši se předávají této metodě v pixelech a pak se převedou na ROZDÍLY. Je důležité nezaměňovat tyto dvě jednotky. Například funkce DragDetect používá pixely, ale kreslení a testování hitů používají dips. Obecné pravidlo je, že funkce související s okny nebo vstupem myši používají pixely, zatímco Direct2D a DirectWrite používají dips. Program vždy otestujte v nastavení s vysokým rozlišením DPI a nezapomeňte program označit jako pracující s DPI. Další informace naleznete v tématu DPI a Device-Independent Pixely.
Tady je kód, který zpracovává WM_MOUSEMOVE zprávy.
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);
}
}
Logika pro změnu velikosti tří teček byla popsána dříve, v části Příklad: Kreslení kruhů. Všimněte si také volání InvalidateRect. Tím se zajistí, že se okno znovu nakreslí. Následující kód zpracovává WM_LBUTTONUP zprávy.
void MainWindow::OnLButtonUp()
{
if ((mode == DrawMode) && Selection())
{
ClearSelection();
InvalidateRect(m_hwnd, NULL, FALSE);
}
else if (mode == DragMode)
{
SetMode(SelectMode);
}
ReleaseCapture();
}
Jak vidíte, obslužné rutiny zpráv pro vstup myši mají kód větvení v závislosti na aktuálním režimu. To je přijatelný návrh pro tento poměrně jednoduchý program. Pokud se ale přidají nové režimy, může se rychle stát příliš složitým. Pro větší program může být lepší návrh architektury MVC (model-view-controller). V tomto typu architektury je řadič , který zpracovává uživatelský vstup, oddělený od modelu, který spravuje data aplikací.
Když program přepne režimy, kurzor se změní, aby uživateli poskytl zpětnou vazbu.
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);
}
A nakonec nezapomeňte nastavit kurzor, když okno obdrží WM_SETCURSOR zprávu:
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(hCursor);
return TRUE;
}
break;
Shrnutí
V tomto modulu jste se naučili, jak zvládnout vstup myši a klávesnice; jak definovat klávesové zkratky; a jak aktualizovat obrázek kurzoru tak, aby odrážel aktuální stav programu.