반응형
ScrollableControl을 상속받았다는 건, “이 컨트롤은 화면(뷰포트)보다 큰 가상의 캔버스를 갖고, 자동 스크롤바/스크롤 오프셋 관리까지 운영체제가 대신 해주는 컨테이너”라는 뜻입니다.
(= 스크롤바가 필요할 때 자동으로 생기고, 스크롤 위치에 맞춰 그리기 좌표가 바뀌는 베이스 클래스를 쓰는 것)
무엇을 얻나? (핵심 기능)
- AutoScroll / AutoScrollMinSize / AutoScrollPosition
- AutoScroll = true로 켜면, AutoScrollMinSize에 “가상 캔버스 크기”를 설정해 스크롤바를 자동 표시.
- 현재 스크롤 오프셋은 AutoScrollPosition으로 읽음(주의: 반환값은 음수).
- 표준 스크롤 UX
- 마우스 휠, 스크롤바 드래그, 키보드 스크롤 등 WinForms 기본 동작이 자동 지원.
- 컨테이너 역할
- 자식 컨트롤을 담을 수 있으며, 스크롤 시 자식도 함께 이동.
- 그리기 좌표 보정 필요
- 오너 드로잉 시, AutoScrollPosition만큼 TranslateTransform 해서 스크롤 오프셋 반영.
상속 계층 요약: Control → ScrollableControl → (Panel, ContainerControl, …)
즉, ScrollableControl은 스크롤 가능한 기반 컨트롤이에요.
이미지 뷰어(EmguVision)에 적용하는 법 (요령)
- 가상 캔버스 크기 지정
- 보여줄 이미지의 (확대/축소 반영) 크기를 AutoScrollMinSize로 설정.
- 그릴 때 스크롤 보정
- OnPaint에서 e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y) 호출 후 그리기.
- 확대/축소가 있다면 ScaleTransform(Zoom, Zoom) 도 함께.
- 좌표 변환
- 마우스 좌표 → 이미지 좌표로 바꿀 때, 스크롤 오프셋과 줌을 반영:
- imgX = (e.X - AutoScrollPosition.X) / Zoom
- imgY = (e.Y - AutoScrollPosition.Y) / Zoom
- (AutoScrollPosition은 음수 반환이므로 빼기 연산이 맞습니다.)
- 마우스 좌표 → 이미지 좌표로 바꿀 때, 스크롤 오프셋과 줌을 반영:
- 더블버퍼링
- 깜빡임 방지: DoubleBuffered = true, OptimizedDoubleBuffer, AllPaintingInWmPaint, UserPaint, ResizeRedraw 설정.
최소 예제 (핵심만)
public partial class EmguVision : ScrollableControl
{
public float Zoom { get; set; } = 1f;
private EmguImage m_SrcImage;
public EmguVision()
{
// 스크롤/더블버퍼 활성화
AutoScroll = true;
DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
}
// 이미지가 바뀌거나 줌이 바뀔 때 호출해 스크롤 영역 업데이트
private void UpdateScrollArea()
{
if (m_SrcImage != null && !m_SrcImage.IsVoid)
{
int w = (int)Math.Ceiling(m_SrcImage.Width * Zoom);
int h = (int)Math.Ceiling(m_SrcImage.Height * Zoom);
AutoScrollMinSize = new Size(w, h); // 가상 캔버스 크기
}
else
{
AutoScrollMinSize = Size.Empty;
}
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (m_SrcImage == null || m_SrcImage.IsVoid) return;
// 스크롤 오프셋(음수)을 그래픽스에 적용
e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y);
// 줌 적용
e.Graphics.ScaleTransform(Zoom, Zoom);
// 이미지 그리기
using (var bmp = m_SrcImage.BitmapImage) // 내부 구현에 따라 복제/캐시 고려
{
if (bmp != null)
e.Graphics.DrawImage(bmp, Point.Empty);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (m_SrcImage == null || m_SrcImage.IsVoid) return;
// 화면좌표 -> 이미지좌표
double ix = (e.X - AutoScrollPosition.X) / Zoom;
double iy = (e.Y - AutoScrollPosition.Y) / Zoom;
// 예: 픽셀 문자열 조회 이벤트 발행
string sPx = m_SrcImage.GetPixel(new Point((int)ix, (int)iy));
MouseMoveEvent?.Invoke(((int)ix).ToString(), ((int)iy).ToString(), sPx);
}
}
자주 하는 실수
- AutoScrollMinSize를 설정하지 않으면 스크롤바가 안 뜹니다(가상 캔버스가 뷰포트보다 커야 함).
- AutoScrollPosition을 안 적용하고 그리면, 스크롤해도 그림이 안 움직입니다.
- AutoScrollPosition은 읽을 때 음수, 설정할 때 양수 규칙이 있으니, 프로그램적으로 스크롤 이동 시:
- AutoScrollPosition = new Point(targetX, targetY); // set은 양수
요약
- 왜 ScrollableControl을 상속?
큰 이미지(또는 도형 캔버스)를 다룰 때 스크롤바/오프셋/휠 스크롤을 OS가 자동으로 처리하게 해서, 우리는 그리기와 좌표 변환에만 집중하려고요. - EmguVision 같은 이미지 뷰어 컨트롤엔 아주 잘 맞는 선택입니다.
위 가이드대로 스크롤 영역/좌표 보정만 잘 붙이면, 긴 코드 없이도 안정적인 스크롤 UX를 곧바로 얻을 수 있어요.
반응형
'C#' 카테고리의 다른 글
VisionThread/EmguVision 라이브·검사 공통 파이프라인 리팩토링 (풀코드) (0) | 2025.09.10 |
---|---|
EmguImage 안에 IInputArray를 반환하는 getter (0) | 2025.09.09 |
private static bool IsFiniteFloat(float v) => !(float.IsNaN(v) || float.IsInfinity(v)); (0) | 2025.09.09 |
소멸자(~EmguVision)로 GDI 리소스(Pen)를 해제하는 방식은 권장되지 않습니다. (0) | 2025.09.09 |
C# APM(Asynchronous Programming Model) 패턴 (1) | 2025.01.20 |
Windows 공유 폴더 동시 연결 제한 해결 방법 (0) | 2025.01.17 |
C# `ref`와 `out` 키워드의 차이점과 활용법 (2) | 2025.01.16 |
`TimeSpan.FromSeconds` vs `Thread.Sleep`: 차이와 올바른 사용법 (1) | 2025.01.16 |