摘要:在做Winform应用的时候,有些效果不太好做,不像网页,用CSS和HTML能做出灰常漂亮的界面来,其实用WebBrowser可以让你的程序拥有两者的优势。这里介绍一个winform内嵌WebBrowser做的一个RSS浏览器及内嵌在Winform里的html编辑器的光标恢复问题。
不知道大家有没有用过FeedDemon,它是一个不错的RSS订阅工具,左边的导航树是Winform的,右边的主区域是用WebBrowser来呈现的,而且在主区域点击某条RSS条目后左边的树节点相应的未读数目就会变化,点击左边的树选择“设置所有项目未已读”,右边主区域的RSS列表就会变为已读图标,这是一个典型的Winform和WebBrowser相结合的例子,既发挥了Winform能利用本地CPU计算能力,响应快的特点,又发挥了HTML编程容易,界面华丽的优势,要用winform写一个右边主区域效果的控件,估计得费不少力。
我们先来看下这个RSS阅读器的核心功能,首先是利用http请求去获取rss内容,RSS是XML格式的,显示的话,就可以用XSLT来显示,这样可以做到内容和显示相分离,显示部分用WebBrowser来显示,然后呢WebBrowser里的javascript要可以调用Winform方法,Winform也要能调用Webbrowser控件里的javascript方法。
RSS的XML格式大家都很熟悉了,随便找一段示例如下
RSS XML Sample
<?
xml version="1.0" encoding="utf-8"
?>
<
rss
version
="2.0"
xmlns:slash
="http://purl.org/dc/elements/1.1/"
xmlns:sy
="http://purl.org/rss/1.0/modules/syndication/"
xmlns:admin
="http://webns.net/mvcb/"
xmlns:rdf
="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
>
<
channel
>
<
title
>
老农专注数据库编程
</
title
>
<
link
>
http://www.5do8.com/
</
link
>
<
description
>
老农如是想如是为
</
description
>
<
copyright
>
copyright by 5do8
</
copyright
>
<
item
read
="true"
>
<
title
>
我军步兵营所属炮连的98式120火箭筒
..
</
title
>
<
description
>
我军步兵营所属炮连的98式120火箭筒
..
</
description
>
<
link
>
/tableforum/App/view.php?bbsid=4
&
subid=1
&
fid=65544
&
tbid=8075
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:03:01 GMT
</
pubDate
>
</
item
>
<
item
read
="true"
>
<
title
>
温今早看望广州火车站80万滞留旅客:
&
quot;大..
</
title
>
<
description
>
温今早看望广州火车站80万滞留旅客:
&
quot;大..
</
description
>
<
link
>
/tableforum/App/view.php?bbsid=4
&
subid=2
&
fid=65638
&
tbid=2819
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:03:01 GMT
</
pubDate
>
</
item
>
<
item
read
="true"
>
<
title
>
温今早看望广州火车站80万滞留旅客:
&
quot;大..
</
title
>
<
description
>
温今早看望广州火车站80万滞留旅客:
&
quot;大..
</
description
>
<
link
>
/tableforum/App/view.php?bbsid=4
&
subid=3
&
fid=65639
&
tbid=6065
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:03:01 GMT
</
pubDate
>
</
item
>
<
item
>
<
title
>
07最惹火十部国产片
</
title
>
<
description
>
07最惹火十部国产片
</
description
>
<
link
>
http://sh.sohu.com/7/1103/22/column216152269.shtml
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:02:55 GMT
</
pubDate
>
</
item
>
<
item
>
<
title
>
赚大钱取之有道火爆
</
title
>
<
description
>
赚大钱取之有道火爆
</
description
>
<
link
>
http://doc.go.sohu.com/200712/df66a3feef3110edd839666dcb3fc1de.php?url=http://vip.78.cn/zs/gbook_admin/getgo.php?id=452
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:02:55 GMT
</
pubDate
>
</
item
>
<
item
>
<
title
>
火!等一分钟
</
title
>
<
description
>
火!等一分钟
</
description
>
<
link
>
http://61.135.132.134/goto.php?aid=27
&
amp;pid=1153
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:02:55 GMT
</
pubDate
>
</
item
>
<
item
>
<
title
>
火车票预售期
</
title
>
<
description
>
火车票预售期
</
description
>
<
link
>
http://news.sogou.com/news?query=火车票预售??pid=01003102
&
p=01010302
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:02:55 GMT
</
pubDate
>
</
item
>
<
item
>
<
title
>
春运火车票
</
title
>
<
description
>
春运火车票
</
description
>
<
link
>
http://news.sogou.com/news?query=春运火车票
&
pid=01003102
&
p=01010302
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:02:55 GMT
</
pubDate
>
</
item
>
<
item
>
<
title
>
轻松购买火车票!
</
title
>
<
description
>
轻松购买火车票!
</
description
>
<
link
>
http://61.135.132.134/goto.php?aid=26
&
amp;pid=1175
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:02:55 GMT
</
pubDate
>
</
item
>
<
item
>
<
title
>
one-mall·火热招商
</
title
>
<
description
>
one-mall·火热招商
</
description
>
<
link
>
http://shop.focus.cn/shopview/450228.html
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:02:55 GMT
</
pubDate
>
</
item
>
<
item
>
<
title
>
六佰本火热招商进行中
</
title
>
<
description
>
六佰本火热招商进行中
</
description
>
<
link
>
http://shop.focus.cn/shopview/450319.html
</
link
>
<
author
></
author
>
<
pubDate
>
Wed, 30 Jan 2008 22:02:55 GMT
</
pubDate
>
</
item
>
</
channel
>
</
rss
>
我们还要再写个RSS来呈现这个RSS,就以列表的方式呈现就行了,然后我们设置简单的CSS,让已读的RSS条目显示为蓝色,未读的默认显示为黑色,然后点击每个条目的时候要把颜色改为已读的颜色,并且同时Winform。从上面的RSS定义可以看到我把前三个item元素加了一个read="true"的属性(为了演示目的我手工加的),我们用xslt的if语句来读取这个属性来动态设置RSS条目的样式。最后xslt还要定义一个让winform调用的javascript方法以供后面的演示。最终的XSLT如下,不太熟悉XSLT语法的可以参考我以前贴过的帖子。
<?
xml version="1.0" encoding="utf-8"
?>
<
xsl:stylesheet
version
="1.0"
xmlns:xsl
="http://www.w3.org/1999/XSL/Transform"
>
<
xsl:output
method
="html"
/>
<
xsl:template
match
="rss/channel"
>
<
html
>
<
head
>
<
title
>
<
xsl:value-of
select
="title"
/>
</
title
>
<
SCRIPT
LANGUAGE
="JavaScript"
>
function invokeWin(obj)
{
obj.childNodes[0].style.color='blue';
window.external.winFunction(obj.childNodes[0].href);
}
function scriptFunc(str)
{
alert(str);
}
</
SCRIPT
>
<
style
media
="all"
lang
="en"
type
="text/css"
>
body
{
background-color:#ccc;
}
.ChannelTitle
{
font-family: Verdana;
font-size: 11pt;
font-weight: bold;
500px;
text-align: center;
}
.PostDate
{
font-family: Verdana;
font-size: 7pt;
padding-left: 15px;
}
A,A:visited
{
text-decoration: none;
color: #000
}
A:link
{
text-decoration: none;
}
A:hover
{
text-decoration: underline;
}
</
style
>
</
head
>
<
body
>
<
xsl:apply-templates
select
="title"
/>
<
ol
>
<
xsl:apply-templates
select
="item"
/>
</
ol
>
<
div
align
="center"
>
©
2008WawaSoft 2008
</
div
>
</
body
>
</
html
>
</
xsl:template
>
<
xsl:template
match
="title"
>
<
div
class
="ChannelTitle"
>
<
xsl:value-of
select
="text()"
/>
</
div
>
<
br
/>
</
xsl:template
>
<
xsl:template
match
="item"
>
<
li
>
<
span
onclick
="invokeWin(this)"
>
<
a
TARGET
="_blank"
href
="{link}"
>
<
xsl:if
test
="@read='true'"
>
<
xsl:attribute
name
="style"
>
color:blue;
</
xsl:attribute
>
</
xsl:if
>
<
xsl:value-of
select
="title"
/>
</
a
>
</
span
>
<
span
class
="PostDate"
>
<
xsl:value-of
select
="pubDate"
/>
</
span
>
</
li
>
</
xsl:template
>
</
xsl:stylesheet
>
程序里面呢,我们得让WebBrowser来显示这个RSS,代码如下
private
void
FWBTest_Load(
object
sender, EventArgs e)
{
wb1.AllowWebBrowserDrop
=
false
;
//
wb1.IsWebBrowserContextMenuEnabled = false;
wb1.WebBrowserShortcutsEnabled
=
false
;
wb1.ObjectForScripting
=
this
;
wb1.ScriptErrorsSuppressed
=
true
;
try
{
XslCompiledTransform xslt
=
new
XslCompiledTransform();
xslt.Load(
"
rss.xslt
"
);
string
HTMLoutput;
using
(StringWriter writer
=
new
StringWriter())
{
xslt.Transform(
"
rss.xml
"
,
null
, writer);
HTMLoutput
=
writer.ToString();
}
wb1.DocumentText
=
HTMLoutput;
}
catch
(XsltException xsle)
{
Console.WriteLine(
"
样式表中有错。
"
);
}
catch
(XmlException xmle)
{
Console.WriteLine(
"
加载样式表时出现分析错误。
"
);
}
catch
(Exception ex)
{
Console.WriteLine(ex);
}
}
都是WebBrowser和.net的Xml对象的基本用法。
和javascript交互也很简单,以下分别是让javascript调用的方法和用c#调用javascript方法的代码。
public
void
winFunction(
string
str)
{
toolStripStatusLabel1.Text
=
string
.Format(
"
脚本执行方法:{0}
"
, str);
}
private
void
toolStripButton1_Click(
object
sender, EventArgs e)
{
wb1.Document.InvokeScript(
"
scriptFunc
"
,
new
String[] {
"
这是winform调用脚本方法
"
});
}
关键点就是javascript代码的下面这句
window.external.winFunction(obj.childNodes[0].href);
以及c#代码下面这句
wb1.Document.InvokeScript("scriptFunc",new String[] { "这是winform调用脚本方法" });
RSS这部分演示完毕了。
winform下的富文本编辑器控件不太多,一般有两种途径,一种是扩展RichTextBox,要去研究rtf格式,mime协议等,一种是用WebBrowser控件,并把designMode设置为on,然后就是和网页的html编辑器一样,调用一些ExecCommand方法来编辑格式等,这里介绍后者,因为后者实现简单,而且html格式可以直接按原格式贴到互联网上,通用性比较好,这里不具体说一些编辑命令如何实现,这些可以参考文后的参考链接,这里只讨论如何让你的HTML文档关闭后再加载恢复上次编辑的光标位置的功能。
这里需要用一些mshtml对象,所以要在项目里引用Microsoft.mshtml,引用位置默认应该是C:\Program Files\Microsoft.NET\Primary Interop Assemblies\Microsoft.mshtml.dll,我不知道这个组件是不是新安装的windows xp,2003都有,如果没有的话可以把这个dll放到你项目的bin目录下一起发布。
大致原理是这样的,在关闭当前编辑文档的时候获取当前光标的位置,也就是当前选择的元素(虽然doc.selection有可能是个图片,而不是TextRange,但我们只考虑textRange的情况,大家可以写代码过滤掉其它情况,如果当前编辑点不是文本类型,就不考虑恢复光标位置了,呵呵。)。然后给当前选择元素后面插入一个小的span元素,作为一个标识符,最后再把文档的Body的InnerHTML保存到数据库里。下次从数据库里读取保存的html文本,先设置到当前WebBrowser的Body的innerHTML属性,然后通过getElementById方法找上次插入的标识符span,找到这个span就相当于找到了上次的光标位置,最后用body元素createTextRange后调用其moveToElementText方法把光标编辑位置移动到找到的位置。
相应代码如下
private
void
NewDoc()
{
wb1.Navigate(
"
about:blank
"
);
IHTMLDocument2 doc
=
wb1.Document.DomDocument
as
IHTMLDocument2;
doc.designMode
=
"
On
"
;
}
private
void
SaveDoc()
{
if
(wb1.Document
!=
null
)
{
IHTMLDocument2 doc
=
wb1.Document.DomDocument
as
IHTMLDocument2;
HTMLDocumentClass documentClass
=
wb1.Document.DomDocument
as
HTMLDocumentClass;
IHTMLDOMNode caret_pos
=
(IHTMLDOMNode)documentClass.getElementById(
"
caret_pos
"
);
if
(caret_pos
!=
null
) caret_pos.removeNode(
true
);
IHTMLTxtRange range
=
doc.selection.createRange()
as
IHTMLTxtRange;
range.pasteHTML(caretHtml);
range.collapse(
true
);
_text
=
doc.body.innerHTML;
doc.body.innerHTML
=
""
;
}
}
private
void
LoadDoc()
{
if
(
!
string
.IsNullOrEmpty(_text))
{
IHTMLDocument2 doc
=
wb1.Document.DomDocument
as
IHTMLDocument2;
doc.body.innerHTML
=
_text;
IHTMLBodyElement bodyElement
=
doc.body
as
IHTMLBodyElement;
if
(bodyElement
!=
null
)
{
IHTMLTxtRange range
=
bodyElement.createTextRange();
HTMLDocumentClass documentClass
=
wb1.Document.DomDocument
as
HTMLDocumentClass;
IHTMLElement caret_pos
=
documentClass.getElementById(
"
caret_pos
"
);
if
(caret_pos
!=
null
)
{
range.moveToElementText(caret_pos);
range.select();
}
}
_text
=
""
;
}
}
注:代码写的不严谨,只用于演示目的,请勿用于生产环境。
关于Winform html编辑器的参考链接
http://windowsclient.net/articles/htmleditor.aspx
http://www.codeproject.com/cs/miscctrl/editor_in_windows_forms.asp
http://www.codeproject.com/KB/IP/WYSIWYG_netHTML2.aspx
关于恢复光标位置的参考链接
设置光标位置的问题:SetDocumentHTML(html) 之后, SetCaretPos(curpos) 为何失效?
http://topic.csdn.net/t/20050729/22/4177529.html
HTML可视化编辑器中IE丢失光标位置的问题。
http://hi.baidu.com/jindw/blog/item/8c3e928ba1f04dd0fc1f10d2.html
用土办法记忆可编辑div 内的光标位置- 极湖- by OU(链接不可用,请从google缓存里查看)
http://my.opera.com/jlake/blog/2008/05/05/div
How to RemoveChild from HtmlElement from C#
http://bytes.com/forum/thread453742.html
本文源码下载地址如下
WebBrowserDemo.zip