CP+ Visual Studio.NET 插件






3.65/5 (7投票s)
一个应用程序,用于从CP检索最新的文章和论坛帖子,并在您的VS.NET IDE中显示它们

摘要
CP+ 是一个为CodeProject爱好者(更广为人知的名称是“瘾君子”)设计的Visual Studio .NET插件。它允许您在您最喜欢的开发环境中随时了解最新的文章和论坛主题。您甚至可以点击您感兴趣的文章或帖子,您的浏览器将自动加载到正确的URL,是不是很方便?
CP+ 使用 Code Project Web Service 作为其数据源。
本文将介绍CP+项目的主要组件,并重点介绍我在开发此应用程序过程中发现的有用技术和方法。在接下来的几周内,还将撰写一篇更深入的文章。
免责声明
我的第一个C++应用程序是Hello World。这是我的第二个C++应用程序,所以请大家多多包涵。应用程序代码中存在一些明显的问题,我希望您,C++精英们,能够帮助我解决。任何提示、技巧和想法都非常欢迎。
您可能会嘲笑我C++代码中的一些VB风格,但您最好确保您提出的是建设性的批评,而不是纯粹的批评。
引言
该应用程序由三个主要部分组成:一个MC++类(CPBrief
),一个C# Windows Forms用户界面(一个用户控件,CPPlusUI
)和一个C# ActiveX宿主控件(CPPlusAddInUC
)。
ActiveX宿主控件是必需的,以便在VS.NET中托管您的插件。它充当您用户控件的容器,并将其显示在工具窗口插件中。这段代码不是我写的,而是修改了一些来自Visual Studio.NET自动化样本页面上非常有用的 示例插件代码。我曾尝试使用VC++代码,但它对我目前的C++技能来说太复杂了。
C# Windows Forms用户界面部分包括一个Tab
控件、两个DataGrid
控件、两个Timer
控件以及其他一些小的Windows Forms控件。计时器控制更新过程,而数据网格显示文章和论坛帖子。整个包被封装为一个用户控件,以便于将其轻松地插入到ActiveX宿主和其他应用程序中。此外,还包含一个名为Settings
的Windows Form,允许用户自定义CP+。
MC++类相对简单,是我写过的第一个C++类,它包含了从CP Web Service检索数据以及本地存储和返回数据的必要逻辑。它使用了.NET Framework中的一些XML类。
这项小小的努力有三个大致目标:
- 找出C++的炒作之处。
- 提供一种快速便捷的方式来查看CP,吸引开发者。
- 并探索.NET的跨语言和通用数据类型能力。
但实际上,我大嘴一张,就参加了Visual C++ .NET竞赛。在有人嘲讽“Paul Watson甚至连C++编译器都能让他错过一个指针”之后,我接受了挑战,并且,我希望,我做到了。我不打高尔夫,但那件夹克很帅。
CPBrief - MC++ 类
类成员
CPBrief
类包含以下类成员。
私有成员
static String* AppPath
:AppPath
使用 Application::StartupPath
初始化,用于访问CP+所需的各种文件。当作为插件运行时,它映射到VS.NET安装目录中的Common7\IDE文件夹。我使用了static
关键字,因为这个成员应该对类的所有实例都是相同的。
DateTime NextUpdate
:
这保存了类实例下一次更新周期的未来日期和时间。我将其与DateTime::Now
进行比较,以确定是否需要更新。每次运行UpdateBriefs
方法时,UpdateFrequency
值会被加到DateTime::Now
上并存储在此成员中。
String *BriefName
:
当这个类的实例被构造时,这个成员被设置为传递给构造函数的值。基本上,它保存了类实例的“英文”名称,然后用于XPath查询,从cp+settings.xml中检索和设置正确的设置。
String *BriefURL
:BriefURL
保存了需要调用此类的实例的Web Service方法的完整URL。此成员在构造函数中设置,并从设置文件中读取。
String *BriefFileName
:
此成员保存了类从Web Service检索的XML保存到的文件名,以及DataSet
从中读取XML的文件名。此成员在构造函数中设置,并从设置文件中读取。
公共成员
bool UpdateOnLoad
:
保存了CP+加载时是否联系Web Service的值。
int NumberOfBriefsToRetrieve
:
包含要从Web Service检索的简短项(例如文章或论坛帖子)的数量。
TimeSpan UpdateFrequency
:
保存了联系Web Service并更新DataGrid
的频率值。TimeSpan
类型在C#和MC++代码之间传递非常方便。
类方法
CPBrief
类包含以下方法。
CPBrief(String* BriefID)
(构造函数)
构造函数负责正确初始化类实例。在C#中,您通过调用private CPPlus.CPBrief cpArticles = new CPPlus.CPBrief("ArticleBrief");
来构造类实例。"ArticleBrief"
被放入BriefName
成员,并用于构造XPath查询,该查询从cp+settings.xml文件检索类实例的其余设置。
xdocSettings->Load (String::Concat(AppPath,"cp+settings.xml"));
BriefURL = ReturnNodeValue(xdocSettings,
String::Concat("settings/briefsettings[@id='",
BriefName,"']/wsurl"));
构造函数应用类似的代码来返回实例的所有设置。

SaveSettings
SaveSettings
基本上做了构造函数的反向操作。它从类成员中提取值,并将它们保存回设置文件。此方法仅在C#应用程序的Settings
Windows Form关闭时调用。
保存到XML类似于写入。您创建一个XmlDocument
,然后使用其SelectSingleNode
方法返回一个节点引用。然后将该节点的value
设置为设置值。然后保存XmlDocument
。
我将实际的节点设置部分包装在一个自定义函数SetNodeValue
中,以便在需要时轻松设置节点值。
SetNodeValue(xdocSettings->SelectSingleNode(String::Concat( "settings/briefsettings[@id='",BriefName,"']/updateonload")), Convert::ToString(UpdateOnLoad)); ... void SetNodeValue(XmlNode* xnodeToSet, String* NodeValue) { ... xnodeToSet = xnodeToSet->FirstChild; xnodeToSet->Value = NodeValue; }
需要注意的一点是,.NET XML类将attribute
和element
几乎相同地对待。这起初让我感到困惑,但实际上使代码更简洁,因为我不需要重载方法来处理attributes
。
UpdateBriefs
现在我们来看实际的Web Service部分。此方法非常简单地从指定的URL读取XML并将其保存到BriefFileName
XML文件中。它不使用Web Service代理,而是使用XmlTextReader
类,该类非常方便地可以通过HTTP读取URL。.
使用此类而不是Web Service代理的原因实际上是SOAP和REST拥护者之间持续辩论的一部分。SOAP是Web Service的方式,而使用XmlTextReader
基本上是REST的方式。简而言之,REST认为我们不需要额外的包包装器(如SOAP)就可以将Web服务器变成Web Service提供者。在大多数情况下,使用URL中的QueryString足以告知Web Service要返回什么XML。
您可以通过在URL栏中输入https://codeproject.org.cn/webservices/latest.asmx/GetLatestArticleBrief?NumArticles=2来练习REST方式。您刚刚使用了一个Web Service。返回的数据是XML格式,然后可以根据需要使用。使用SOAP,您需要一些额外的方法和函数来访问XML。
然而,在我看来,SOAP和REST都有其用武之地。当您拥有复杂的Web Service并返回类型化的XML时,SOAP效果更好。REST适用于更简单的场景。
我选择REST的另一个原因是,我所需要的只是将返回的XML直接保存到文件中。我不需要对XML执行任何进一步的操作。
这是该方法的代码
xtrBriefs = new XmlTextReader (String::Concat(BriefURL,
NumberOfBriefsToRetrieve.ToString()));
xtwBriefs = new XmlTextWriter (String::Concat(AppPath,BriefFileName), 0);
xtwBriefs->Formatting = Formatting::Indented;
while (xtrBriefs->Read())
{
xtwBriefs->WriteRaw(xtrBriefs->ReadOuterXml());
}
xtwBriefs->Flush();
xtwBriefs->Close();
xtrBriefs->Close();
简单得令人难以置信。但是,我确实希望将来对这段代码进行一些改进,因为错误处理几乎不存在。如果CP Web Service在调用此代码时宕机,XmlTextReader
将不返回任何数据,而XmlTextWriter
会将这“没有数据”的内容写入文件。
ReturnBriefs
此方法将写入的XML数据读入DataSet
,并提供DataSet
以绑定到DataGrid
。同样非常简单,非常直接,而且再次没有多少错误检查(如果我有一个弱点,那就是这个,我希望您能帮助我)。
DataSet* ReturnBriefs()
{
// Reads the Briefs file and returns a DataSet
DataSet* dsBriefs = 0;
dsBriefs = new DataSet();
try
{
dsBriefs->ReadXml(String::Concat(AppPath,BriefFileName));
return dsBriefs;
}
catch (Exception* e)
{
throw e;
return 0;
}
}
“表”结构由DataSet
类本身从XML中推断出来。但是,您可以创建自己的XSD架构文档,并将其与DataSet
关联,以提供强类型数据类型。对我们来说,推断就可以了(尽管从技术上讲,我们可以联系Web Service并下载包含架构信息的WSDL文档。然后我们可以保存它,并自动创建一个架构)。
在CP+中,C#代码像这样调用此代码:dgArticles.SetDataBinding(cpArticles.ReturnBriefs(),"ArticleBrief");
用法
该类非常易于使用。您只需要创建一个实例,例如:private CPPlus.CPBrief cpLoungePosts = new CPPlus.CPBrief("MessageBrief");
,并确保应用程序路径中存在与BriefName/ID
匹配的cp+settings.xml文件。然后调用UpdateBriefs
方法检索项目,然后调用ReturnBriefs
方法,该方法返回一个DataSet
,您可以将其数据绑定或按需使用。
在CP+中,该类被实例化两次,一次用于文章,一次用于论坛帖子,并且只要CP+加载,它就保持在作用域内。
CP+UI - C# 用户控件和插件
用户控件是一组简单的Windows Forms控件,它们调用并显示CPBrief
类。我不会在本文中过多介绍此应用的部分(因为这是VC++竞赛)。

继承的 DataGrid
UI的一个有趣方面是,我继承并重写了标准.NET DataGrid
控件的部分内容。标准的DataGrid
无法显示CP+中看到的下划线蓝字列项。
为了实现这种效果,我必须重写标准DataGridTextBoxColumn
(它是DataGrid
的一部分)的已经三次重载的paint
方法。
public class DataGridColoredTextBoxColumn : DataGridTextBoxColumn
{
protected override void Paint(Graphics g,Rectangle Bounds,
CurrencyManager Source,int RowNum, Brush BackBrush ,
Brush ForeBrush ,bool AlignToRight)
{
BackBrush = Brushes.White;
g.FillRectangle(BackBrush, Bounds.X, Bounds.Y, Bounds.Width, Bounds.Height);
System.Drawing.Font font = new Font(System.Drawing.FontFamily.GenericSansSerif ,
(float)8.25, System.Drawing.FontStyle.Underline );
g.DrawString( GetColumnValueAtRow(Source, RowNum).ToString(), font ,Brushes.Blue,
Bounds.X ,Bounds.Y );
}
}
最终,我会鼓起勇气和知识,在C++中做到这一点,但现在C#已经足够了。感谢Mazdak的《更改DataGrid单元格背景颜色》文章,它教会了我如何做到这一点。网上没有任何其他资源在这方面像它一样易于理解和有用。
关于DataGrid
功能的一个注意事项是,虽然它提供了自动列排序,但它对数字的排序效果并不好。它按字母数字排序,因此在排序时11会排在8之前,您已被警告(CP+目前尚未修复此问题,因此对“回复”列进行排序并不总是非常有效)。
打开浏览器窗口
UI的另一个相对有趣的部分是如何打开一个Web浏览器窗口并指向相关的URL。
System.Diagnostics.Process.Start("https://codeproject.org.cn");
如果它再简单一点,我可能就不会相信了。有趣的是,最困难的部分是弄清楚如何检测和使用对DataGrid
的鼠标点击。DataGrid
为所有子组件共用一套鼠标事件。所以,无论您点击单元格、列标题还是网格本身,都会触发一套事件。我发现这很愚蠢,因为通常需要编写不必要的代码来处理鼠标事件。要检测点击了哪个行并启动正确的URL,您必须使用HitTestInfo
和HitTestType
类。以下是此代码:
private void dgArticles_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{
DataGrid dgSender = (DataGrid) sender;
System.Windows.Forms.DataGrid.HitTestInfo hti;
hti = dgSender.HitTest(e.X, e.Y);
switch (hti.Type)
{
case System.Windows.Forms.DataGrid.HitTestType.Cell :
dgArticles.CurrentCell = new DataGridCell(hti.Row, hti.Column);
dgArticles.Select(hti.Row);
if (dgArticles.CurrentCell.ColumnNumber == 1)
{
System.Diagnostics.Process.Start(dgArticles[hti.Row,0].ToString());
}
break;
}
}
插件宿主
UI部分的最后一点兴趣在于用户控件如何在VS.NET中作为工具窗口托管。如前所述,我使用了自动化样本以及VS.NET插件模板向导。
但是,向导只引导您到一定程度,并且不包含生成工具窗口的功能。以下是创建此工具窗口并使其托管用户控件的代码(尽管严格来说,工具窗口托管Interop.VSUserControlHostLib
ActiveX控件,该控件又托管您的用户控件)。
public void OnConnection(object application,
Extensibility.ext_ConnectMode connectMode,
object addInInst, ref System.Array custom)
{
...
applicationObject = (_DTE)application;
addInInstance = (AddIn)addInInst;
windowToolWindow
= applicationObject.Windows.CreateToolWindow (addInInstance,
"VSUserControlHost.VSUserControlHostCtl", "CP+",
guidstr, ref objTemp);
windowToolWindow.Visible = true;
windowToolWindow.Width = 500;
windowToolWindow.Height = 300;
objControl = (VSUserControlHostLib.VSUserControlHostCtl)objTemp;
System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
objControl.HostUserControl(asm.Location, "CPPlusAddIn.CPPlusAddInUC");
...
}
用法
由于CP+的UI被封装在用户控件中,您可以获取程序集并根据需要将其拖放到Windows Forms上。将来,我希望扩展用户控件本身,以便您可以指定DataGrid
中的标签和列。实际上,当您想用CP+功能包装自己的UI时,您会使用CPBrief类,当您只想快速将CPBrief的功能集成到您的应用程序中时,您会使用CP+UI用户控件。
您也可以非常容易地将插件宿主代码用于您自己的插件,而无需运行向导或使用自动化样本。
CP+ - 插件应用
实际应用本身易于运行和使用。应用加载时,它会读取设置文件,并确定是更新还是仅从XML存储文件中返回简要项。您可以通过单击设置按钮来自定义设置以满足您的需求。您还可以通过单击左下角的两个按钮(一个用于文章,一个用于论坛帖子)来强制应用程序从Web Service更新,而无需等待下一个周期。
但是,请小心使用设置,因为某些值可能会导致应用程序崩溃。如果CP+停止工作,最好的办法是用本文提供的原始cp+settings.xml文件覆盖您的副本,那些设置是有效的。
如前所述,我的错误捕获和处理不尽如人意,我将在下一版应用程序中着手改进。
安装
最简单的安装方法是下载 演示代码 并运行msi安装程序。这将安装并注册应用程序。然后,您需要将cp+settings.xml文件从安装的文件夹复制到VS.NET安装目录下的Common7\IDE文件夹。如果不这样做,应用程序将无法工作。
或者,您可以自己编译源代码并合并ReCreateCommands.reg注册表文件。
安装完成后,打开VS.NET并单击“工具”菜单。应该有一个黄色的笑脸图标,点击它将打开CP+,然后您可以随意停靠和自动隐藏它。
注意事项和待办功能
- 如果您使用用户控件或MC++类库进行开发,我建议您在测试Windows Form项目中使用调试和测试,而不是通过实际的插件进行调试。这只会让您的生活更轻松,因为您可以简单地按F5,而无需加载另一个VS.NET副本并运行插件。
- 尽管我非常欣赏VS.NET IDE的功能,但我也对其稳定性感到相当不满,尤其是在使用窗体设计器时。使用继承控件开发用户控件通常会生成IASync错误,并且类库DLL经常会被锁定,只有关闭VS.NET才能解锁(重新构建)。
- 仅当您知道
static
关键字的作用时,才在类成员上使用它! - 并非所有类用法在C#中的作用都与C++相同。例如
Node
类是抽象的。在C#中,您可以正常实例化它,正如我所料,但在C++中不行。
我有很多想法来进一步改进这个应用程序。
- 实现更新函数的异步调用,这样在消耗Web Service时应用程序就不会“冻结”(在快速连接上您可能不会注意到锁定,但我在拨号连接上肯定会注意到)。
- 突出显示新项目并保留旧项目。目前,应用程序会用Web Service的新XML文件覆盖旧的XML文件。
- 实现一个系统托盘指示器,用于检测新的文章或论坛帖子(就像Outlook一样,当然是更好的!)。
- 处理文章标题、作者姓名和帖子主题中的HTML。我希望为此使用James的 Elementary HTML Parser。
- 添加一个收藏夹选项卡,以便您可以拥有一个来自Visual Studio.NET的收藏文章列表。类似于Code Project的书签功能。
- 实现简单的搜索。
- 允许“过滤”,这样您就不会收到关于您不感兴趣的新文章或帖子的警报。
- 设置验证,以防止您输入999篇文章。
- 更好的错误处理。目前,如果Web Service宕机且没有返回XML,该程序会清除文章和论坛帖子列表,这完全没用。
- 找到并使用比DataGrid更好的东西来显示项目,欢迎提出任何建议。
- 用MC++重写整个应用程序,只是为了证明我可以 :-D(尽管使用C++进行插件部分看起来是件非常可怕的事情)。
当然,应用程序的性能只能达到Web Service提供的数据水平。我希望看到Chris将Web Service扩展到其他论坛,并提供关于文章的额外信息,以便进行更高级的过滤和选择。尽管如此,目前的Web Service已经很棒了,再次证明了Web Service确实是好事。想象一下不得不抓取所有这些信息?

结论
当然,这个应用程序和文章中的实际C++部分对大多数C++程序员来说会相当简单。但是,这是我的第一步,我希望能走得更远。我已瞥见“你们”为什么如此热衷于C++。也许我见过的最好的瞥见是意识到您可以在C++中像贝克汉姆一样弯曲(球),而在C#中甚至连托尼·布莱尔(Tony Blair)都无法带球。然而,这种规则的弯曲和在出错时没有反馈可能会非常令人沮丧,当您不知道哪里出了问题时。
我还有很多东西要学,我希望通过这篇文章,我已经说明了我需要学习的内容,也许您可以提供一些指导(哈哈,我开了一个C++的玩笑:rolleyes:)。
我还对.NET中如此轻松地在同一个应用程序中使用不同语言感到印象深刻。我只有两个项目,一个C#项目,另一个MC++项目。C#项目然后只需添加MC++项目作为依赖项即可访问其类。
现在,我们有了Code Project屏幕保护程序、 Rama的Ticker以及我卑微的CP+应用,所有这些都可以让我们尽可能轻松地了解我们最喜欢的资源,The Code Project。
感谢,自然,Chris的坚定鼓励我参加这个竞赛,也感谢其他所有人提供了点燃火焰的燃料 :-D
希望您觉得这个应用程序很有用,我们稍后再见,您可以欣赏我新买的酷炫黄夹克;)