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

CodeProject 离线文章编辑器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (78投票s)

2012 年 10 月 1 日

CPOL

4分钟阅读

viewsIcon

149993

downloadIcon

4255

一个离线 WYSIWYG 编辑器,用于备份、编辑或创建新的 CodeProject 文章。

目录

引言

CP 文章 编辑器允许您在无需互联网连接的情况下创建/编辑 CodeProject 的文章。您还可以使用此程序登录以备份(您的文章和相关图片)并编辑您已发布的文章。它体积小巧且便携。

Screenshot of the main window. Browsing for an article to edit
(点击图片在新窗口中查看全尺寸图片。)

背景

让我们学习或回顾一些有助于理解源代码的内容。

异步操作

异步操作是独立运行的,它们有自己的线程。如果我们想在它们完成工作后执行代码,我们不能将这些代码写在启动它们的方法的下面。因为它们不会阻塞调用它们的方法。我们还必须知道的是,一个线程不能访问在另一个线程上创建的对象……

通常,我们使用 event 来处理在异步方法完成任务后必须执行的操作,或者在事件发生后执行的操作。但我为此目的使用了另一种方法,我认为它相对更容易理解。

假设我们有一个线程,并且想在我们的线程完成工作后调用一个函数

class Example
{
    private Action after_MyThreadFinished;
    private Form invoker;

    public Example(Action after_MyThreadFinished, Form invoker)
    {
        this.after_MyThreadFinished = after_MyThreadFinished;
        this.invoker = invoker;
    }

    public void doSomeWorksAsynchronously()
    {
        new Thread(() =>
        {
            // some codes that take long time...
            Thread.Sleep(2000);

            if (after_MyThreadFinished != null)
                invoker.Invoke(after_MyThreadFinished);
        }).Start();
    }
}

一个线程不能直接更改由 UI 线程拥有的可视化对象。而且,我们不能确定用户不会在我们新线程中的代码中访问 UI 线程创建的可视化控件。这就是为什么我们需要一个调用者对象。

用法

private void button1_Click(object sender, EventArgs e)
{
    Action myAction = new Action(() =>
    {
        MessageBox.Show("Finished.");
        this.Text = "I can access all the objects without any trouble." +
                    "Because I will be invoked by the thread of this form.";
    });

    Example example = new Example(myAction, this);
    example.doSomeWorksAsynchronously(); // It doesn't block the current thread.
}

CKEditor

CKEditor 是一个开源的所见即所得编辑器,可以轻松地集成到网页中。在这个项目中,我将它用在一个浏览器中。

我自定义了工具栏并删除了不必要的插件以减小总体大小。此外,我还修改了 image.js 插件以支持使用相对 URL。

// Original code: B.preview.setAttribute('src',s.$.src);
// Modified for CP Article Editor
// The modification has been made to convert a relative image src to an
// absolute src in preview window.
// begin
if(s.$.src.substring(0,7)=='http://' || s.$.src.substring(0,8)=='https://')
    B.preview.setAttribute('src',s.$.src);
else
{
    var kok=(document.getElementsByTagName('base')[0]==null?'**biseyyapma**':
             document.getElementsByTagName('base')[0].getAttribute('href'));
    var verilenAdres=C;
    var tamAdres=kok+C;
    B.preview.setAttribute('src',tamAdres);
}
// end

// Original code: --
// Modified for CP Article Editor
// The modification has been made to convert an absolute image src to a relative src.
// begin
kok=(document.getElementsByTagName('base')[0]==null?'**biseyyapma**':
     document.getElementsByTagName('base')[0].getAttribute('href')),
D=(D.indexOf(kok)>-1?D.replace(kok,''):D),
// end

// Original code: C.data('cke-saved-src',D.getValue()); C.setAttribute('src',D.getValue());
// Modified for CP Article Editor
// The modification has been made to convert a relative image src to an absolute src.
// begin
var verilenAdres=D.getValue();
if(verilenAdres.substring(0,7)=='http://' || verilenAdres.substring(0,8)=='https://')
{
    C.data('cke-saved-src',D.getValue());
    C.setAttribute('src',D.getValue());
}
else
{
    var kok=(document.getElementsByTagName('base')[0]==null?'**biseyyapma**':
             document.getElementsByTagName('base')[0].getAttribute('href'));
    var tamAdres=(verilenAdres.indexOf(kok)<0?kok+verilenAdres:verilenAdres)
    C.data('cke-saved-src',tamAdres);
    C.setAttribute('src',tamAdres);
}
// end

使用的配置

fullPage        : false  // We are not editing a full page
language        : 'en'   // I removed all other language files to decrease the size.
resize_enabled  : false  // Prevent resizing the editor.
uiColor         : '#CCC' // Grey
tabSpaces       : 4      // Each tab character is replaced with 4 spaces.
contentsCss     : 'CodeProject.css'  // CSS stylesheet file of codeproject template.
removePlugins   : 'contextmenu,liststyle,tabletools' // For hiding ckeditor's context menu
docType         : '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"' +
                  '"http://www.w3.org/TR/html4/loose.dtd">' // Document type of CodeProject.

访问和修改 HTML 文档的元素

我使用 WebBrowser 来登录和提取所需数据。这里有一些重要的方法。

读取元素的属性

webBrowser.Document.GetElementById("IdOfTheElement").GetAttribute("attribute");

更改元素的属性

webBrowser.Document.GetElementById("IdOfTheElement").SetAttribute("attribute", "new value");

从 C# 调用 WebBrowser 对象中的 JavaScript 函数

object[] args = new object[] { ... };
object returned = webBrowser.Document.InvokeScript("functionName", args);

从网站下载文件

有一个非常简单的方法可以从网站下载文件。我用这个方法下载与文章相关的图片。

WebClient webClient = new WebClient();
webClient.DownloadFile("http://www.site.com/file.extension", "C:\file.extension");

禁用 WebBrowser 中的点击声音

浏览器中没有禁用点击声音的属性。但是可以通过 CoInternetSetFeatureEnabled API 来实现。

static class ClickSoundDisabler
{
    // ---------------------------------------------------
    //  CodeProjectArticleEditor > ClickSoundDisabler.cs
    // ---------------------------------------------------
    //  CodeProject Article Editor
    //  Huseyin Atasoy
    //  September 2012
    // ---------------------------------------------------

    private const int FEATURE_DISABLE_NAVIGATION_SOUNDS = 21;

    private const int SET_FEATURE_ON_PROCESS = 0x00000002;

    [DllImport("urlmon.dll")]
    [PreserveSig]
    [return: MarshalAs(UnmanagedType.Error)]
    private static extern int CoInternetSetFeatureEnabled(int FeatureEntry,
        [MarshalAs(UnmanagedType.U4)] int dwFlags, bool fEnable);

    public static void disableClickSound()
    {
        try
        {
            CoInternetSetFeatureEnabled(FEATURE_DISABLE_NAVIGATION_SOUNDS,
                                        SET_FEATURE_ON_PROCESS, true);
        }
        catch{}
    }
}

截图

这是程序的主窗口

Codeproject article editor main window

如果您想备份已发布的文章进行编辑或仅保存它们,可以输入您的电子邮件和密码进行登录。登录后,您的文章将列出:

Welcome after login

article list

当您选择其中一篇文章时,将显示一些有关该文章的信息

Informations about selected article

即使您未连接到互联网,也可以撰写新文章或编辑现有文章

New article

将文章传输到 CodeProject 的在线编辑器

  1. 从右键菜单中选择“将源复制到剪贴板”。
    (请注意,所有绝对图片路径都已转换为相对路径。因此,您无需更改复制的 HTML 代码中的任何内容。)
  2. 打开 CodeProject 在线编辑器。
  3. 点击“HTML”按钮切换到 HTML 模式
  4. 粘贴您的内容。(Ctrl+V)
  5. 再次点击“HTML”按钮切换回设计模式……

值得关注的点

让我们通过查看源代码来列出我们可以学到的东西

  • .NET 中的所见即所得 HTML 编辑器。
  • 如何在 .NET 中使用 ckeditor?
  • 如何从 C# 函数调用 WebBrowser 中的 JavaScript 函数?
  • 自动化登录和数据提取。
  • 在 C# 中使用 Action 而不是 Event 来处理异步函数。
  • 禁用 WebBrowser 控件中的点击声音。
  • 在 C# 中从互联网下载文件。

历史

  • 2013 年 12 月 15 日(v1.0.4)
    • 在 CodeProject 进行一些更改后,修复了文章列表和登录方法。
  • 2013 年 4 月 4 日(v1.0.3)
    • 由于 CodeProject 的一些更新,登录最近失败了。已修复。
    • 向 URL 追加了随机字符串,以强制 WebBrowser 重新加载缓存的页面。
  • 2012 年 10 月 17 日(v1.0.2)
    • 新的代码用于记住上次浏览的路径
    • extractMemberId() 函数进行的小改动
    • “打开方式”支持
      (您可以使用“打开方式”菜单,或将 cpa 文件拖放到程序图标上。)
    • 用于验证 CPA(CodeProject 文章)文件的唯一文件头
      (因此,旧的 cpa 文件无法与此版本打开。)
  • 2012 年 10 月 5 日(v1.0.1)
    • CodeProject 的登录页上的“ctl00”变成了“ctl01”。因此,编写了一个新函数(getElementId())来使用 Regex 类获取元素的 ID,并用此函数替换了所有可能在未来被 CodeProject 更改的旧常量。
  • 2012 年 10 月 1 日(v1.0.0)
    • 首次发布。
© . All rights reserved.