In this blog post, we’ll walk through building a complete solution that allows users to hover over HTML tables in a WebView2-based application, click an export button, and send the table data—complete with styling and merge info—to a VSTO PowerPoint Add-in that renders a native PowerPoint table.
Overview
Components:
- WebView2 Application (WinForms or WPF)
- JavaScript Injection to extract table data and styles
- C# VSTO Add-in to receive and render the PowerPoint table
Step 1: Setup WebView2
Install the Microsoft.Web.WebView2 NuGet package. Then initialize and navigate:
await webView2.EnsureCoreWebView2Async(null);
webView2.CoreWebView2.Navigate("https://your-webpage.com");
Step 2: Inject JavaScript to Export Tables
Inject the following JavaScript to add an export button and capture table data:
(function () {
const style = document.createElement("style");
style.textContent = `
.export-btn {
position: absolute;
padding: 4px 8px;
font-size: 12px;
background-color: #0078D4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
display: none;
z-index: 10000;
}
`;
document.head.appendChild(style);
const exportBtn = document.createElement("button");
exportBtn.className = "export-btn";
exportBtn.textContent = "Export Table";
document.body.appendChild(exportBtn);
let currentTable = null;
document.addEventListener("mouseover", (e) => {
const table = e.target.closest("table");
if (table && table !== currentTable) {
currentTable = table;
const rect = table.getBoundingClientRect();
exportBtn.style.top = `${window.scrollY + rect.top - 30}px`;
exportBtn.style.left = `${window.scrollX + rect.left}px`;
exportBtn.style.display = "block";
}
});
document.addEventListener("mouseout", (e) => {
if (!e.relatedTarget || !e.relatedTarget.closest("table")) {
exportBtn.style.display = "none";
currentTable = null;
}
});
exportBtn.addEventListener("click", () => {
if (!currentTable) return;
const getStyles = (el) => {
const style = window.getComputedStyle(el);
return {
fontSize: style.fontSize,
fontFamily: style.fontFamily,
color: style.color,
backgroundColor: style.backgroundColor,
fontWeight: style.fontWeight
};
};
const data = {
headers: [],
rows: [],
rowHeights: [],
theme: getStyles(currentTable),
merges: []
};
const rows = currentTable.querySelectorAll("tr");
rows.forEach((row, rIndex) => {
const cells = row.querySelectorAll("th, td");
const rowData = [];
let colPos = 0;
cells.forEach((cell) => {
const colspan = parseInt(cell.getAttribute("colspan") || "1");
const rowspan = parseInt(cell.getAttribute("rowspan") || "1");
if (colspan > 1 || rowspan > 1) {
data.merges.push({ row: rIndex, col: colPos, colspan, rowspan });
}
rowData.push({ text: cell.innerText.trim(), style: getStyles(cell) });
colPos += colspan;
});
if (row.querySelector("th")) data.headers.push(rowData);
else data.rows.push(rowData);
data.rowHeights.push(row.offsetHeight);
});
window.chrome.webview.postMessage({ type: "tableExport", payload: data });
});
})();
Step 3: Handle WebView2 Messages in C
webView2.CoreWebView2.WebMessageReceived += WebView2_WebMessageReceived;
private void WebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
string json = e.WebMessageAsJson;
var obj = JsonConvert.DeserializeObject<JObject>(json);
if (obj["type"]?.ToString() == "tableExport")
{
var payload = obj["payload"]?.ToString();
var data = JsonConvert.DeserializeObject<TableExportData>(payload);
GeneratePowerPointTable(data);
}
}
Step 4: Define Data Models
public class TableCellData
{
public string text { get; set; }
public Style style { get; set; }
}
public class Style
{
public string fontSize { get; set; }
public string fontFamily { get; set; }
public string color { get; set; }
public string backgroundColor { get; set; }
public string fontWeight { get; set; }
}
public class MergeInfo
{
public int row { get; set; }
public int col { get; set; }
public int colspan { get; set; }
public int rowspan { get; set; }
}
public class TableExportData
{
public List<List<TableCellData>> headers { get; set; }
public List<List<TableCellData>> rows { get; set; }
public List<int> rowHeights { get; set; }
public Style theme { get; set; }
public List<MergeInfo> merges { get; set; }
}
Step 5: Generate PowerPoint Table
private void GeneratePowerPointTable(TableExportData data)
{
var app = Globals.ThisAddIn.Application;
var slide = app.ActivePresentation.Slides[1];
int numRows = data.headers.Count + data.rows.Count;
int numCols = data.headers.FirstOrDefault()?.Count ?? data.rows.FirstOrDefault()?.Count;
var shape = slide.Shapes.AddTable(numRows, numCols, 50, 50, 600, 300);
var table = shape.Table;
int rowIndex = 1;
foreach (var row in data.headers.Concat(data.rows))
{
int colIndex = 1;
foreach (var cell in row)
{
var pptCell = table.Cell(rowIndex, colIndex);
pptCell.Shape.TextFrame.TextRange.Text = cell.text;
var style = cell.style;
if (style != null)
{
pptCell.Shape.TextFrame.TextRange.Font.Name = style.fontFamily;
pptCell.Shape.TextFrame.TextRange.Font.Size = ConvertFontSize(style.fontSize);
pptCell.Shape.Fill.ForeColor.RGB = ConvertColor(style.backgroundColor);
pptCell.Shape.TextFrame.TextRange.Font.Bold = style.fontWeight == "bold" ? MsoTriState.msoTrue : MsoTriState.msoFalse;
pptCell.Shape.TextFrame.TextRange.Font.Color.RGB = ConvertColor(style.color);
}
colIndex++;
}
rowIndex++;
}
foreach (var merge in data.merges)
{
table.Cell(merge.row + 1, merge.col + 1).Merge(
table.Cell(merge.row + merge.rowspan, merge.col + merge.colspan));
}
}
Helper Methods
private int ConvertColor(string cssColor)
{
var color = System.Drawing.ColorTranslator.FromHtml(cssColor);
return color.B << 16 | color.G << 8 | color.R;
}
private float ConvertFontSize(string fontSize)
{
return float.TryParse(fontSize.Replace("px", ""), out float px) ? px * 0.75f : 12f;
}
JSON Structure Sample
{
"type": "tableExport",
"payload": {
"headers": [[{ "text": "Year", "style": {"fontSize": "14px", "fontFamily": "Arial", "color": "#fff", "backgroundColor": "#0078D4", "fontWeight": "bold"} }]],
"rows": [[{ "text": "2023", "style": {"fontSize": "13px", "fontFamily": "Segoe UI"} }]],
"rowHeights": [30, 28],
"theme": { "fontSize": "14px", "fontFamily": "Arial" },
"merges": [{ "row": 0, "col": 0, "colspan": 2, "rowspan": 1 }]
}
}
Wrapping Up
This integrated solution demonstrates how to:
- Extend WebView2 with JavaScript to capture styled table data.
- Use messaging to transfer JSON from WebView2 to C#.
- Dynamically build a styled, merged PowerPoint table with VSTO.