Kullanıcı girişi: genişletilmiş örnek
Basit bir çizim programı oluşturmak için kullanıcı girişi hakkında öğrendiğimiz her şeyi birleştirelim. Programın ekran görüntüsü aşağıdadır:
Çizim programıekran görüntüsü
Kullanıcı birkaç farklı renkte üç nokta çizebilir ve üç noktayı seçebilir, taşıyabilir veya silebilir. Kullanıcı arabirimini basit tutmak için, program kullanıcının üç nokta renklerini seçmesine izin vermez. Bunun yerine, program otomatik olarak önceden tanımlanmış bir renk listesi arasında döngüler oluşturur. Program üç nokta dışında hiçbir şekli desteklemez. Açıkçası, bu program grafik yazılımı için herhangi bir ödül kazanmayacak. Ancak, yine de öğrenmek için yararlı bir örnektir. Kaynak kodun tamamını Basit Çizim Örneğiindirebilirsiniz. Bu bölümde yalnızca bazı önemli noktalar ele alınacaktır.
Üç nokta, programda üç nokta verilerini (D2D1_ELLIPSE) ve rengi (D2D1_COLOR_F) içeren bir yapıyla temsil edilir. Yapı ayrıca iki yöntem tanımlar: üç noktayı çizmek için bir yöntem ve isabet testi gerçekleştirmek için bir yöntem.
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, her üç nokta için dolguyu ve ana hattı çizmek için aynı düz renkli fırçayı kullanır ve rengi gerektiği gibi değiştirir. Direct2D'de düz renkli fırçanın rengini değiştirmek verimli bir işlemdir. Bu nedenle, düz renkli fırça nesnesi SetColor yöntemini destekler.
Üç nokta bir STL listesinde kapsayıcıda depolanır:
list<shared_ptr<MyEllipse>> ellipses;
Not
shared_ptr, TR1'de C++'a eklenen ve C++0x ile resmileştirilen bir akıllı işaretçi sınıfıdır. Visual Studio 2010, shared_ptr ve diğer C++0x özellikleri için destek ekler. Daha fazla bilgi için Visual Studio 2010'da Yeni C++ ve MFC Özelliklerini Keşfetme MSDN Dergisi makalesine bakın.
Programın üç modu vardır:
- Çizim modu. Kullanıcı yeni üç nokta çizebilir.
- Seçim modu. Kullanıcı bir üç nokta seçebilir.
- Sürükleme modu. Kullanıcı seçili bir üç noktayı sürükleyebilirsiniz.
Kullanıcı, Hızlandırıcı Tabloları'nde açıklanan klavye kısayollarını kullanarak çizim modu ile seçim modu arasında geçiş yapabilir. Kullanıcı bir üç noktaya tıklarsa, seçim modundan program sürükleme moduna geçer. Kullanıcı fare düğmesini serbest bıraktığında seçim moduna geri döner. Geçerli seçim, üç nokta listesinde yineleyici olarak depolanır. yardımcı yöntemi MainWindow::Selection
seçili üç noktanın işaretçisini veya seçim yoksa nullptr değeri döndürü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(); }
Aşağıdaki tabloda, üç modun her birinde fare girişinin etkileri özetlemektedir.
Fare Girişi | Çizim Modu | Seçim Modu | Sürükleme Modu |
---|---|---|---|
Sol düğme aşağı | Fare yakalamayı ayarlayın ve yeni bir üç nokta çizmeye başlayın. | Geçerli seçimi serbest bırakın ve isabet testi yapın. Bir üç noktaya isabet edilirse, imleci yakalayın, üç noktayı seçin ve sürükleme moduna geçin. | Eylem yok. |
Fareyle hareket etme | Sol düğme aşağıysa, üç noktayı yeniden boyutlandırın. | Eylem yok. | Seçili üç noktayı taşıma. |
Sol düğme yukarı | Üç nokta çizmeyi durdurun. | Eylem yok. | Seçim moduna geçin. |
MainWindow
sınıfında aşağıdaki yöntem WM_LBUTTONDOWN iletileri işler.
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);
}
Fare koordinatları piksel cinsinden bu yönteme geçirilir ve ardından DIP'lere dönüştürülür. Bu iki ünitenin karıştırılmaması önemlidir. Örneğin, DragDetect işlevi piksel kullanır, ancak çizim ve isabet sınaması DIP'leri kullanır. Genel kural, windows veya fare girişiyle ilgili işlevlerin pikselleri, Direct2D ve DirectWrite ise IP'leri kullanmasıdır. Programınızı her zaman yüksek DPI ayarında test edin ve programınızı DPI kullanan olarak işaretlemeyi unutmayın. Daha fazla bilgi için bkz. DPI ve Device-Independent Piksel.
WM_MOUSEMOVE iletileri işleyen kod aşağıdadır.
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);
}
}
Üç noktayı yeniden boyutlandırma mantığı daha önce Örnek: Çizim Daireleri bölümünde açıklanmıştır. Ayrıca InvalidateRectçağrısına da dikkat edin. Bu, pencerenin yeniden boyanmasını sağlar. Aşağıdaki kod WM_LBUTTONUP iletileri işler.
void MainWindow::OnLButtonUp()
{
if ((mode == DrawMode) && Selection())
{
ClearSelection();
InvalidateRect(m_hwnd, NULL, FALSE);
}
else if (mode == DragMode)
{
SetMode(SelectMode);
}
ReleaseCapture();
}
Gördüğünüz gibi, fare girişi için ileti işleyicilerinin tümü geçerli moda bağlı olarak dallanma koduna sahiptir. Bu oldukça basit bir program için kabul edilebilir bir tasarımdır. Ancak yeni modlar eklenirse çok karmaşık hale gelebilir. Daha büyük bir program için model-görünüm denetleyicisi (MVC) mimarisi daha iyi bir tasarım olabilir. Bu tür bir mimaride, kullanıcı girişini işleyen denetleyicisi, uygulama verilerini yöneten modelinden ayrılır.
Program modları değiştirdiğinde, imleç kullanıcıya geri bildirimde bulunmak üzere değişir.
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);
}
Son olarak, pencere bir WM_SETCURSOR iletisi aldığında imleci ayarlamayı unutmayın:
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(hCursor);
return TRUE;
}
break;
Özet
Bu modülde fare ve klavye girişini işlemeyi öğrendiniz; klavye kısayollarını tanımlama; ve imleç görüntüsünü programın geçerli durumunu yansıtacak şekilde güncelleştirme.