65.9K
CodeProject 正在变化。 阅读更多。
Home

CP+ Visual Studio.NET 插件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.65/5 (7投票s)

2002年7月31日

MIT

16分钟阅读

viewsIcon

351395

downloadIcon

1778

一个应用程序,用于从CP检索最新的文章和论坛帖子,并在您的VS.NET IDE中显示它们

Screenshot of CP+ in action, Article tab displayed with surrounding 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"));

构造函数应用类似的代码来返回实例的所有设置。

Screenshot of CP+ in action, Settings form displayed without surrounding VS.NET IDE

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类将attributeelement几乎相同地对待。这起初让我感到困惑,但实际上使代码更简洁,因为我不需要重载方法来处理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++竞赛)。

Screenshot of CP+ in action, Lounge Posts tab displayed without surrounding VS.NET IDE

继承的 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,您必须使用HitTestInfoHitTestType类。以下是此代码:

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确实是好事。想象一下不得不抓取所有这些信息?

Screenshot of CP+ in action, Activity Log tab displayed without surrounding VS.NET IDE

结论

当然,这个应用程序和文章中的实际C++部分对大多数C++程序员来说会相当简单。但是,这是我的第一步,我希望能走得更远。我已瞥见“你们”为什么如此热衷于C++。也许我见过的最好的瞥见是意识到您可以在C++中像贝克汉姆一样弯曲(球),而在C#中甚至连托尼·布莱尔(Tony Blair)都无法带球。然而,这种规则的弯曲和在出错时没有反馈可能会非常令人沮丧,当您不知道哪里出了问题时。

我还有很多东西要学,我希望通过这篇文章,我已经说明了我需要学习的内容,也许您可以提供一些指导(哈哈,我开了一个C++的玩笑:rolleyes:)。

我还对.NET中如此轻松地在同一个应用程序中使用不同语言感到印象深刻。我只有两个项目,一个C#项目,另一个MC++项目。C#项目然后只需添加MC++项目作为依赖项即可访问其类。

现在,我们有了Code Project屏幕保护程序、 Rama的Ticker以及我卑微的CP+应用,所有这些都可以让我们尽可能轻松地了解我们最喜欢的资源,The Code Project。

感谢,自然,Chris的坚定鼓励我参加这个竞赛,也感谢其他所有人提供了点燃火焰的燃料 :-D

希望您觉得这个应用程序很有用,我们稍后再见,您可以欣赏我新买的酷炫黄夹克;)

© . All rights reserved.