How to draw a rectangle on Webview2 when its content is loaded?

82 Views Asked by At

If the WebView2 Source is not set, then it draws the rectangle. If we set the Source, then a rectangle is not drawn, because the content is on top of the WebView2 surface.

How do I show a rectangle on top of the content of the WebView2 Control?

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        webView21.Source = new Uri("https://www.google.com");
    }

    public void HighlightElementInRectangle(int x, int y, int width, int height)
    {
        SolidBrush myBrush = new SolidBrush(Color.Red);
        Graphics formGraphics;
        formGraphics = webView21.CreateGraphics();
        Pen pen = new Pen(Color.Red, 2);
        formGraphics.DrawRectangle(pen, x, y, width, height);
    }

    private void webView21_Click(object sender, EventArgs e)
    {
        HighlightElementInRectangle(18, 111, 319, 20);
    }
}
1

There are 1 best solutions below

1
Jimi On BEST ANSWER

A couple of notes:

  1. You cannot use [Control].CreateGraphics() (a bad idea in any case) to draw onto the surface of a WebView2 Control. This is just a required container for the actual WebView2 object. You cannot really interact with it.
  2. For similar reasons, Mouse events are not registered. You could handle Mouse events with injected scripts, though.

To draw shapes on the rendered HTML page, you can inject a <canvas> element, then use its 2D context to paint on it.

Here, I'm using a static class to store the JavaScripts, then call AddScriptToExecuteOnDocumentCreatedAsync() to inject the script when the page is loaded.
After that, you just need to call ExecuteScriptAsync() to execute functions in the injected script(s).

The JavaScript drawRectangle() function has some parameters:

  • x, y, width and height specify the size of the shape
  • line sets the size of the border
  • color specifies the color of the border
  • clear (bool) defines whether previous drawings should be erased or not

For the color, you can use the name of a known color ('red', 'blue', etc.), or use the HTML format.
To translate an ARGB color to HTML, use the ColorTranslator.ToHtml() method.

For example, using a Button Click event to call the script:

private async void SomeButton_Click(object sender, EventArgs e) {
    string color = ColorTranslator.ToHtml(Color.FromArgb(100, 50, 70, 34));
    await webView.ExecuteScriptAsync($"drawRectangle(100, 100, 80, 200, 4, '{color}', true)");
}

This draws a Rectangle (100, 100, 80, 200) with a border of 4 pixels, using the specified color and erases previous drawings.


WebView2 initialization (the Control is named webView here) and scripts injection:

public partial class SomeForm : Form {
    string canvasFunction = string.Empty;
    string canvasFunctionID = string.Empty;

    public SomeForm() {
        InitializeComponent();
        canvasFunction = JavaScripts.DrawRectangleOnCanvas(fixedElement: false);
    }

    protected override async void OnLoad(EventArgs e) {
        await InitializeWebView2();
    }

    private async Task InitializeWebView2() {
        // When deployed, you should specify the Browser / User folder's path
        var env = await CoreWebView2Environment.CreateAsync(null, null, null);
        webView.CoreWebView2InitializationCompleted += InitializationCompleted;
        await webView.EnsureCoreWebView2Async(env);
    }

    private async void InitializationCompleted(object? sender, CoreWebView2InitializationCompletedEventArgs e) {
        if (e.IsSuccess) {
            // The script ID is used to remove the script if/when needed 
            var canvasFunctionID = await 
                webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(canvasFunction);
        }
        else {
            MessageBox.Show("Core WebView2 component initialization failed");
            Application.ExitThread();
        }
    }
}

The DrawRectangleOnCanvas() method generates a JavaScript function (drawRectangle()) that paints a rectangular shape on a <canvas> element. It creates one if no element with the specified ID is present.
This element is click-through. To make it solid, pass false to the createOverlayCanvas().

The <canvas> element has fixed size, set to the current window's viewport.
Its position can be set as 'fixed' (doesn't scroll) or 'absolute' (it scrolls).
Modify as required (e.g., if the WebView2 Control can be resized at run-time). In this case, you have to store the canvas object, then replace it when the viewport is resized, otherwise canvas.getContext('2d') returns a new context each time (hence, you cannot persist the drawings, if you decide so).

▶ If the syntax here is not available (you didn't specify the .NET version in use), you can store the scripts in a file on disk or in the Project's Resources and load it from there.

public static class JavaScripts {
    public static string DrawRectangleOnCanvas(bool fixedElement) {
        var sb = new StringBuilder(@"
            function drawRectangle(x, y, width, height, line, color, clear) {
              var canvas = document.getElementById('overlayCanvas');
              if (canvas === null) canvas = createOverlayCanvas({clickThrough: true});
              const ctx = canvas.getContext('2d');

              if (clear) {
                ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
              }
              ctx.beginPath();
              ctx.lineWidth = line;
              ctx.strokeStyle = color;
              ctx.rect(x, y, width, height);
              ctx.stroke();
            }

            function createOverlayCanvas(clickThrough) {
                let overlay = document.createElement('canvas');
                overlay.id = 'overlayCanvas';
                overlay.style.top = 0; 
                overlay.style.left = 0;
                overlay.width = document.body.clientWidth;
                overlay.height = window.innerHeight;
                overlay.style.position = 'absolute';
                if (clickThrough) overlay.style.pointerEvents = 'none';
                document.body.appendChild(overlay);
                overlay.style.zIndex = '1';
                return overlay;
            }"
        );
        if (fixedElement) sb.Replace("'absolute'", "'fixed'");
        return sb.ToString();
    }
}