创建类似 Facebook 的网站预览器
如何在 C# 中为 Winforms 创建类似 Facebook 的网站预览器用户控件


引言
我们大多数人在某个时候都使用过Facebook来分享一个URL链接。当你这样做时,你可能注意到Facebook会提供一个URL中包含的网站的“预览”。最近,我需要一个做类似事情的用户控件,但在网上搜寻了一番,却一无所获。这就是激发我编写这个用户控件的灵感。这里提供的控件是一个完整的多线程网站预览器,并附带多个图像控件。
使用控件
在深入探讨控件的工作原理之前,我将带你了解如何在自己的项目中使用它。你只需要引用DLL文件。引用后,你应该会在Windows窗体设计器工具箱中看到该图标。只需将控件拖到你的窗体上,然后调整到合适的大小(我推荐大约400x120的大小)。在设计模式下,控件不会渲染任何有用的东西。
要使用代码,只需调用StartRender()
方法或StartRenderHtml()
方法即可。下面是使用StartRender()
的示例:
htmlSitePreviewer1.StartRender("http://www.ebay.com");
执行此代码将导致htmlSitePreviewer1
控件开始渲染eBay的网站。该控件是多线程的,并且控件会立即返回到调用线程。当控件在后台解析网站时,一个*加载*动画会在控件中渲染。如果你不想在后台播放*加载*动画,可以使用以下代码:
htmlSitePreviewer1.ShowLoadingStatus = false;
htmlSitePreviewer1.StartRender("http://www.ebay.com");
默认情况下,*加载*动画是启用的。姊妹方法StartRenderHtml()
的用法也非常相似。下面是一个示例:
string html = "<html><head><title>Test eBay Site</title></head><body><img src=\"http://www.ebay.com/test.png\" /><p>This is a test site for eBay. This is the first paragraph in the test html code.</p></body></html>";
htmlSitePreviewer1.StartRenderHtml("http://www.ebay.com", html);
StartRenderHtml()
方法适用于从数据库中提取HTML代码而不是从实际网站提取(也许是为了缓存)的情况。要使用它,你需要传递URL和要渲染的HTML代码。上面的示例是一个非常简单的演示。此方法也与StartRender()
方法一样是多线程的。
HtmlSitePreview控件公开了一个名为RenderingComplete
的事件。每当控件完成渲染和解析网站时,就会触发此事件。下面是一个示例:
htmlSitePreviewer1.RenderingComplete += RenderingDone;
htmlSitePreviewer1.StartRender("http://www.ebay.com");
private void RenderingDone()
{
Console.WriteLine("Rendering is complete!");
}
执行此代码将启动渲染过程,完成后,将在控制台屏幕上显示“渲染完成!”。
在渲染之前,你还可以设置UserAgent
属性字符串。通过设置此字符串,你可以在检索网站数据时模拟特定的浏览器。默认情况下,此字符串设置为模拟在Windows上运行的FireFox。根据不同的网站,不同的浏览器可能会以不同的方式显示网站。例如,在手机上,网站通常比桌面端有不同的HTML代码。
渲染完成后,将激活几个属性。以下属性可用:
Title
- 这是一个只读属性,返回网站的标题
Description
- 这是一个只读属性,返回网站的描述(如果存在)。
NumberOfImages
- 这是一个只读属性,返回网站上找到的图像数量。HtmlSitePreviewer控件只会抓取网站上宽度最小为50像素且高度最小为50像素的图像。此外,图像的宽度不得大于130像素,高度不得大于110像素。最后,图像的宽度与高度之比以及高度与宽度之比必须小于或等于3.0。不符合此标准的任何图像都将被排除。
ImageIndex
- 最后,此属性用于设置所需的图像。有效数字为1到NumberOfImages
。如果设置为0,则不显示任何图像。默认值为1,即第一张图像。如果网站上没有图像,则默认值为0。
控件的工作原理
这一节对所有人都有用,特别是Web开发人员,可以了解控件的工作原理。
调用StartRender()
后,控件首先会检查当前是否正在渲染一个网站。如果正在进行,该方法将直接返回。如果ShowLoadingStatus
属性设置为true
,控件将调用一个私有方法PrepareLoadingStatus
()。该方法如下所示:
private void PrepareLoadingStatus()
{
var panel = new Panel { Dock = DockStyle.Fill, BackColor = Color.White, BorderStyle = BorderStyle.FixedSingle };
Controls.Add(panel);
panel.BringToFront();
var label = new Label
{
Text = "Loading",
AutoSize = false,
Width = Width,
Height = Height,
TextAlign = ContentAlignment.MiddleCenter
};
panel.Controls.Add(label);
Assembly asm = Assembly.GetExecutingAssembly();
var backgroundImage = new Bitmap(asm.GetManifestResourceStream("HtmlSitePreviewer.Wait.gif"));
var pic = new PictureBox
{
Location = new Point(Width / 2 - 50, Height / 2 - 10),
Image = backgroundImage,
SizeMode = PictureBoxSizeMode.StretchImage,
Width = backgroundImage.Width - 20,
Height = backgroundImage.Height - 20
};
panel.Controls.Add(pic);
pic.BringToFront();
}
此方法创建了一个新的Panel
控件。然后将Panel
控件置于最前面,以便位于渲染区域的前面,并将其停靠以填充控件的整个区域。然后创建一个Label
控件,文本为“Loading”,并将其居中放在控件区域的中间。最后,构造一个PictureBox
控件。此控件也居中放置,并稍微移到“Loading”文本的左侧。PictureBox
图像只是一个嵌入式的动画GIF,通常是等待图标。
接下来,StartRender()
方法创建一个新线程来读取网站的HTML源代码。订阅了一个私有事件,该事件会在线程完成读取网站HTML代码时发出信号。读取HTML源的代码非常简单。它使用HttpWebRequest
和HttpWebResponse
来获取网站的HTML源代码。
源代码读取完成后,将执行一个私有方法GetTitle()
来提取页面的标题。为此,它只需解析HTML代码,并提取<title>
和</title>
标签之间的标题。GetTitle()
方法如下所示:
private string GetTitle()
{
int ndx = _source.ToLower().IndexOf("<title", 0, StringComparison.Ordinal);
if (ndx >= 0)
{
int ndx2 = _source.ToLower().IndexOf(">", ndx, StringComparison.Ordinal);
if (ndx2 >= 0)
{
ndx2++;
int ndx3 = _source.ToLower().IndexOf("</title", ndx2, StringComparison.Ordinal);
if (ndx3 >= 0)
{
return _source.Substring(ndx2, ndx3 - ndx2).Trim(new[] { '\r', '\n', ' ', '\t' });
}
}
}
return "";
}
该方法相当简单,只需搜索<title>
和</title>
标签,然后提取标签之间的任何内容。 如果控件找不到开始或结束标签,则返回空字符串。
提取标题后,控件将尝试获取描述。它通过两种方式尝试执行此操作。第一种是,它尝试定位一个名为description
的<meta>
标签。如果找到这样的meta标签,则读取content
元素,并将其用作描述。如果无法通过meta标签提取描述,则它会尝试在正文中搜索第一个段落<p>
标签,如果段落标签之间有文本,并且长度超过120个字符,则将其提取并用作描述。如果找不到,则返回空字符串作为描述。
提取标题和描述后,将检索图像。首先,控件跳转到HTML源的<head>
部分。然后它尝试定位一个名为image_src
的<link>
标签。在此部分找到的任何图像都将被检索。扫描完<link>
标签后,它会扫描<img>
标签。图像将被下载并调整大小(成比例宽度为100像素),并存储在通用的List<Bitmap>
集合中。
最后,实际渲染即将开始。整个控件的内容填充了一个TableLayoutPanel。TableLayoutPanel被划分为2列和3行。这是代码:
var tlp = new TableLayoutPanel { Location = new Point(0, 0), Name = "TableLayoutPanel1", Dock = DockStyle.Fill, BackColor = Color.White, TabIndex = 0 }; Controls.Add(tlp); tlp.RowCount = 3; tlp.ColumnCount = 2; tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 14f)); tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 18f)); tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 12f)); tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100f)); tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f));
左列用于图像,尺寸为100像素。右列的尺寸为左侧剩余空间的100%。添加了三个Label
控件,一个用于URL,一个用于标题,一个用于描述。还创建了一个PictureBox
来保存图像。然后将这四个控件添加到TableLayoutPanel
中,并渲染控件。最后,它触发RenderingComplete
事件。
最终想法
我已经用几个网站测试了这个控件。如果控件有任何问题或您希望看到任何改进,请给我反馈。
历史
版本1.0 -- 初始发布