WebView handle로 Click 이벤트를 보내는 방법

2025. 6. 4. 22:42프로그램/C#

WebView2 핸들로 클릭 이벤트를 보내는 것의 한계 및 문제점

WebView2 컨트롤의 Handle은 해당 컨트롤이 렌더링되는 Windows 창 자체를 나타냅니다. User32.dll을 통해 이 핸들에 마우스 이벤트를 보내는 것은 운영체제 수준에서 해당 창 영역에 마우스 클릭을 시뮬레이션하는 것입니다.

그러나 다음과 같은 이유로 웹 콘텐츠 클릭에는 부적합합니다.

  1. 창과 콘텐츠의 분리: WebView2는 내부적으로 Edge Chromium 브라우저 엔진을 사용하여 웹 콘텐츠를 렌더링합니다. Handle에 메시지를 보내는 것은 웹뷰 컨트롤의 경계 내에서 마우스를 클릭하는 것일 뿐, 웹뷰 내부의 DOM 트리나 렌더링된 요소와 직접 상호작용하는 것이 아닙니다.
  2. 좌표계 불일치:
    • User32 API의 마우스 이벤트는 일반적으로 스크린 좌표 또는 클라이언트 영역 좌표를 사용합니다.
    • 웹뷰 내부의 HTML 요소는 CSS 픽셀 단위의 자체적인 좌표계를 가집니다.
    • DPI 스케일링이 적용되면 이 둘 사이의 변환이 더욱 복잡해지고 오차가 발생하기 쉽습니다. 웹뷰가 스크롤되면 내부 요소의 상대적 위치도 변합니다.
  3. 안정성 및 신뢰성 부족: 웹 페이지의 레이아웃이 동적으로 변경되거나, 요소가 로드되는 타이밍이 달라지면, 특정 스크린 좌표에 보낸 클릭이 예상치 못한 다른 요소를 클릭하거나 아무것도 클릭하지 못할 수 있습니다.
  4. 활성화 필요: SetCursorPos나 mouse_event 같은 전역 마우스 시뮬레이션 방법을 사용하려면 대상 창이 활성화(Active)되어 있어야 합니다.
  5. 예상치 못한 부작용: 실제 마우스 커서가 움직이거나, 다른 애플리케이션에 영향을 줄 수 있습니다.

User32.dll을 이용한 WebView2 핸들 클릭 (권장하지 않음)

이 방법은 WebView2 내부 콘텐츠를 클릭하는 데 사용하지 않는 것이 좋지만, 기술적으로 가능함을 보여주기 위해 예시를 제공합니다.

C#
 
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing; // Point 등을 위해

// User32.dll 함수 선언
public static class User32
{
    // 메시지 상수
    public const int WM_LBUTTONDOWN = 0x0201; // 왼쪽 마우스 버튼 누름
    public const int WM_LBUTTONUP = 0x0202;   // 왼쪽 마우스 버튼 뗌
    public const int WM_SETCURSOR = 0x0020;   // 커서 설정 메시지

    // 창에 메시지를 보냅니다.
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

    // 창의 클라이언트 영역 좌표를 스크린 좌표로 변환합니다.
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);

    // 스크린 커서 위치를 설정합니다. (전역 마우스 이동)
    [DllImport("user32.dll")]
    static extern bool SetCursorPos(int X, int Y);

    // 마우스 이벤트를 시뮬레이션합니다. (전역 클릭)
    [DllImport("user32.dll")]
    private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo);

    public const int MOUSEEVENTF_LEFTDOWN = 0x0002;
    public const int MOUSEEVENTF_LEFTUP = 0x0004;
}

public partial class MainForm : Form
{
    private Microsoft.Web.WebView2.WinForms.WebView2 webView21;

    public MainForm()
    {
        InitializeComponent();
        webView21 = new Microsoft.Web.WebView2.WinForms.WebView2();
        this.Controls.Add(webView21);
        webView21.Dock = DockStyle.Fill;
        this.Load += MainForm_Load;
    }

    private async void MainForm_Load(object sender, EventArgs e)
    {
        await webView21.EnsureCoreWebView2Async(null);
        webView21.Source = new Uri("https://www.google.com"); // 테스트 URL
        // 예시를 위해 버튼 추가
        Button clickButton = new Button { Text = "WebView Handle Click", Location = new Point(10, 10) };
        clickButton.Click += ClickButton_Click;
        this.Controls.Add(clickButton);
        clickButton.BringToFront(); // 버튼이 웹뷰 위에 보이도록
    }

    private void ClickButton_Click(object sender, EventArgs e)
    {
        // WebView2 컨트롤의 핸들
        IntPtr webViewHandle = webView21.Handle;

        // 웹뷰 클라이언트 영역 내의 클릭할 상대 좌표 (예: 웹뷰의 중앙)
        // 이 좌표가 실제 웹 콘텐츠의 어떤 요소를 클릭할지는 보장할 수 없음.
        int clickX = webView21.Width / 2;
        int clickY = webView21.Height / 2;

        // LParam 값을 구성 (Y 좌표를 상위 16비트에, X 좌표를 하위 16비트에)
        IntPtr lParam = (IntPtr)((clickY << 16) | clickX);

        // 방법 1: SendMessage를 사용하여 핸들로 직접 메시지 보내기
        // 이 방법은 WebView2 내부 DOM 요소에 대한 클릭을 보장하지 않습니다.
        // 웹뷰 자체의 컨트롤 이벤트가 발생할 수 있습니다.
        User32.SendMessage(webViewHandle, User32.WM_LBUTTONDOWN, IntPtr.Zero, lParam);
        User32.SendMessage(webViewHandle, User32.WM_LBUTTONUP, IntPtr.Zero, lParam);
        MessageBox.Show("WM_LBUTTONDOWN/UP 메시지를 WebView2 핸들로 보냈습니다.", "정보");


        // 방법 2: 전역 커서 이동 및 마우스 이벤트 시뮬레이션 (더 비추천)
        // 이 방법은 실제 마우스 커서를 움직이고 전역 클릭을 발생시킵니다.
        // WebView2가 활성화되어 있어야 하고, 정확한 스크린 좌표 변환이 필요합니다.
        // Point clientPoint = new Point(clickX, clickY);
        // User32.ClientToScreen(webViewHandle, ref clientPoint);
        // User32.SetCursorPos(clientPoint.X, clientPoint.Y);
        // User32.mouse_event(User32.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
        // User32.mouse_event(User32.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
        // MessageBox.Show("전역 마우스 클릭을 시뮬레이션했습니다.", "정보");
    }
}

권장되는 방법 (재강조)

WebView2 내부의 웹 콘텐츠를 클릭하거나 조작하려면, ExecuteScriptAsync()를 사용하여 JavaScript를 주입하는 것이 가장 정확하고 강력하며 권장되는 방법입니다.

C#
 
// WebView2 내부에서 특정 좌표 (x, y)의 요소를 클릭하는 JavaScript 주입
private async Task ClickElementInWebView(int x, int y)
{
    string script = $@"
        (function() {{
            const element = document.elementFromPoint({x}, {y});
            if (element) {{
                element.click();
                return 'Clicked element at {x}, {y}';
            }} else {{
                return 'No element found at {x}, {y}';
            }}
        }})();
    ";
    await webView21.CoreWebView2.ExecuteScriptAsync(script);
}

// 사용 예:
// await ClickElementInWebView(100, 200);

이 방법은 웹뷰의 Handle에 직접 이벤트를 보내는 것과 달리, 웹 콘텐츠의 DOM 구조와 상호작용하여 실제 사용자가 클릭한 것과 동일한 효과를 낼 수 있습니다.