본문 바로가기
C#

ScrollableControl을 상속받았다는 건, “이 컨트롤은 화면(뷰포트)보다 큰 가상의 캔버스를 갖고, 자동 스크롤바/스크롤 오프셋 관리까지 운영체제가 대신 해주는 컨테이너”라는 뜻입니다.

by 공부봇 2025. 9. 9.
반응형

 

ScrollableControl을 상속받았다는 건, “이 컨트롤은 화면(뷰포트)보다 큰 가상의 캔버스를 갖고, 자동 스크롤바/스크롤 오프셋 관리까지 운영체제가 대신 해주는 컨테이너”라는 뜻입니다.
(= 스크롤바가 필요할 때 자동으로 생기고, 스크롤 위치에 맞춰 그리기 좌표가 바뀌는 베이스 클래스를 쓰는 것)


무엇을 얻나? (핵심 기능)

  • AutoScroll / AutoScrollMinSize / AutoScrollPosition
    • AutoScroll = true로 켜면, AutoScrollMinSize에 “가상 캔버스 크기”를 설정해 스크롤바를 자동 표시.
    • 현재 스크롤 오프셋은 AutoScrollPosition으로 읽음(주의: 반환값은 음수).
  • 표준 스크롤 UX
    • 마우스 휠, 스크롤바 드래그, 키보드 스크롤 등 WinForms 기본 동작이 자동 지원.
  • 컨테이너 역할
    • 자식 컨트롤을 담을 수 있으며, 스크롤 시 자식도 함께 이동.
  • 그리기 좌표 보정 필요
    • 오너 드로잉 시, AutoScrollPosition만큼 TranslateTransform 해서 스크롤 오프셋 반영.

상속 계층 요약: Control → ScrollableControl → (Panel, ContainerControl, …)
즉, ScrollableControl은 스크롤 가능한 기반 컨트롤이에요.


이미지 뷰어(EmguVision)에 적용하는 법 (요령)

  1. 가상 캔버스 크기 지정
    • 보여줄 이미지의 (확대/축소 반영) 크기를 AutoScrollMinSize로 설정.
  2. 그릴 때 스크롤 보정
    • OnPaint에서 e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y) 호출 후 그리기.
    • 확대/축소가 있다면 ScaleTransform(Zoom, Zoom) 도 함께.
  3. 좌표 변환
    • 마우스 좌표 → 이미지 좌표로 바꿀 때, 스크롤 오프셋과 줌을 반영:
      • imgX = (e.X - AutoScrollPosition.X) / Zoom
      • imgY = (e.Y - AutoScrollPosition.Y) / Zoom
      • (AutoScrollPosition은 음수 반환이므로 빼기 연산이 맞습니다.)
  4. 더블버퍼링
    • 깜빡임 방지: 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를 곧바로 얻을 수 있어요.
반응형