Visual C++/CLI: рисуем линию на PictureBox "как в Paint"
То есть, при движении мыши с зажатой кнопкой линия должна динамически обновляться,
а при отпускании кнопки добавляться на существующий рисунок. Так как PictureBox
удобно сохранять и легко снабдить полосами прокрутки, будем рисовать на ней.
Компонента может быть частью интерфейса приложения как в этой заметке или растянутой на всю форму. Проект создан как приложение Windows Forms, код проверен в Visual Studio 2015.
Опишем в классе формы (после директивы #pragma endregion
) необходимые данные:
private: Point p1, p2; //начало и конец линии Pen^ pen1; //перо Bitmap ^Img1; //основная картинка, на которой рисуем bool isPressed; //флажок "кнопка мыши зажата"
Для самой формы нам понадобится запрограммировать только событие Load
, где мы инициализируем всё, что нужно -
создадим рисунок по текущим размерам PictureBox
, назначим его компоненте,
создадим перо и выставим в "ложь" флажок:
private: System::Void MyForm_Load(System::Object^ sender, System::EventArgs^ e) { Img1 = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height); pictureBox1->Image = Img1; pen1 = gcnew Pen(Color::Black); isPressed = false; }
Ещё лучше было создать рисунок размером с клиентскую часть окна формы:
Img1 = gcnew Bitmap(this->ClientSize.Width, this->ClientSize.Height);
Всё остальное запрограммируем в событиях PictureBox
. На нажатие кнопки мыши будем включать флажок и запоминать место клика p1
:
private: System::Void pictureBox1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { isPressed = true; p1 = e->Location; }
На отпускание кнопки получим координаты второй точки p2
и соединим её с первой, проведя линию на образе Img1
. В реальном коде можно добавлять точки в какой-то контейнер, например, в список как здесь.
private: System::Void pictureBox1_MouseUp(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { p2 = e->Location; Graphics ^gr = Graphics::FromImage(Img1); gr->DrawLine(pen1,p1,p2); gr->Save(); isPressed = false; delete gr; pictureBox1->Invalidate(); }
На перемещение мыши обработка будет немного хитрей. Если кнопка не зажата, ничего делать не нужно, а в противном случае
будем проводить текущую линию на копии рисунка Img2
, созданной из Img1
, чтобы не получилось "веера" из линий при перемещении
мыши с зажатой кнопкой. Img2
всё равно придётся временно назначить рисунком для PictureBox
, чтобы линия была видна в процессе
движения мыши.
private: System::Void pictureBox1_MouseMove(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { if (!isPressed) return; //Кнопка не зажата - выйти Point p2 = e->Location; Bitmap ^Img2 = gcnew Bitmap(Img1); pictureBox1->Image = Img2; Graphics ^gr = Graphics::FromImage(Img2); gr->DrawLine(pen1, p1, p2); delete gr; pictureBox1->Invalidate(); }
Это всё, приложение можно собирать.
P.S. Здесь у нас все координаты были "внутри PictureBox
" и получались непосредственно из аргумента MouseEventArgs
обработчика события. По-другому можно делать так:
форма->Location.X, форма->Location.Y //Координаты верхнего левого угла формы Cursor->Position.X, Cursor->Position.Y //координаты курсора относительно экрана int x1 = Cursor->Position.X - this->Location.X; int y1 = Cursor->Position.Y - this->Location.Y; //координаты мыши относительно левого верхнего угла формы - разность
К сожалению, из-за плохой "очистки мусора" (а мы создаём каждый раз временную копию рисунка при движении мыши с зажатой кнопкой) такое приложение может быстро пожирать память.
Как вариант решения проблемы, можно поддерживать список отрисованных линий и обновлять его, когда нужно, а для самой отрисовки задействовать обработчик события Paint
от pictureBox1
, вот вся пользовательская часть кода после #pragma endregion
для той же формы.
Можно, конечно, ввести ещё список "тип фигуры" и рисовать другие объекты.
private: System::Collections::Generic::List <Point>^ p1List = gcnew System::Collections::Generic::List <Point>(); System::Collections::Generic::List <Point>^ p2List = gcnew System::Collections::Generic::List <Point>(); //список точек, на самом деле лучше отдельный класс для рисуемых вами объектов Point p1, p2; //точки для текущей линии Pen^ pen1; //перо bool isPressed; //флажок "кнопка мыши зажата" Graphics^ gr; //графический контекст private: System::Void MyForm_Load(System::Object^ sender, System::EventArgs^ e) { pictureBox1->Image = gcnew Bitmap(this->ClientSize.Width, this->ClientSize.Height); gr = Graphics::FromImage (pictureBox1->Image); pen1 = gcnew Pen (Color::Black); isPressed = false; } private: System::Void pictureBox1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { isPressed = true; p1 = e->Location; } private: System::Void pictureBox1_MouseUp (System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { p2 = e->Location; p1List->Add (p1); p2List->Add (p2); isPressed = false; pictureBox1->Invalidate(); pictureBox1->Update(); pictureBox1->Refresh(); //обновили всё "с гарантией" } private: System::Void pictureBox1_MouseMove (System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { if (!isPressed) return; //Кнопка не зажата - выйти pictureBox1->Invalidate(); p2 = e->Location; gr->DrawLine(pen1, p1, p2); } private: System::Void pictureBox1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) { gr->Clear(DefaultBackColor); for (int x = 0; x < p2List->Count; x++) { gr->DrawLine(pen1, p1List[x], p2List[x]); } }
Скачать пример "Рисование линий как в Paint", архив .zip с проектом Visual Studio 2019 (7 Кб)
Если хотим рисовать просто карандашом "по точкам", можно применять и подход с таймером (код для той же формы):
private: Point p1, p2; //начало и конец линии Pen^ pen1; //перо Bitmap^ Img1; //основная картинка, на которой рисуем bool isPressed; //флажок "кнопка мыши зажата" Timer ^timer1; //таймер для перерисовки private: System::Void MyForm_Load(System::Object^ sender, System::EventArgs^ e) { Img1 = gcnew Bitmap(this->ClientSize.Width, this->ClientSize.Height); pictureBox1->Image = Img1; pen1 = gcnew Pen(Color::Black); isPressed = false; timer1 = gcnew Timer(); timer1->Interval= 5; //интервал перерисовки, мс timer1->Tick += gcnew System::EventHandler(this, &MyForm::timer1_Tick); } private: System::Void timer1_Tick(System::Object^ sender, System::EventArgs^ e) { ResetImage(e); } private: System::Void ResetImage(System::EventArgs^ e) { Graphics^ gr = Graphics::FromImage(Img1); gr->DrawLine(pen1, p1, p2); delete gr; pictureBox1->Image = Img1; pictureBox1->Invalidate(); p1 = p2; } private: System::Void pictureBox1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { timer1->Start(); p1 = e->Location; } private: System::Void pictureBox1_MouseUp(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { timer1->Stop(); } private: System::Void pictureBox1_MouseMove(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { p2 = e->Location; }
14.11.2017, 13:29 [7561 просмотр]