Click here to Skip to main content
Click here to Skip to main content

Offline Article Editor For CodeProject

By , 3 Apr 2013
 

Editorial Note

Having trouble writing or posting articles? These articles aim to gather together tips and tricks from authors and mentors to help you write great articles.

Contents 

Introduction

CP Article Editor allows you to create/edit articles for CodeProject without internet connection requirement. You can also login to backup (both your articles and related images) and edit your published articles using this program. It is very small in size and portable. 

Screenshot of the main window. Browsing for an article to edit
(Click on the images to view the full sized images in a new window.)

Background

Let's learn or remember something that we will need to understand the source code.

Asynchronous Operations

Asynchronous operations work separately and they have their own threads. If we need to execute codes after they finished their works we can't write these codes below the method it starts them. Because they don't block the method they are called from. Another thing that we must know is that a thread can't access an object created on a different thread...

Generally we are using events for operations that must be done after an asynchronous method finished its mission or an event occured. But I used a different way for this purpose and I think it is relatively simpler to understand.

Suppose that we have a thread and we want to call a function after our thread finished its work:

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();
    }
}

A thread can't make any change directly on a visual object owned by an UI thread. And we can't be sure about that the user will not use codes in our new thread to access visual controls created by the UI thread. This is why we need an invoker object. 

Usage:

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 is an open source WYSIWYG editor that can be integrated with web pages easily. I used it inside a webbrowser in this project.

I customized the toolbar and removed unneccessary plugins to reduce total size. Additionally I modified image.js plugin to make using relative urls possible:

// 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

Used configurations:

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.

Accessing And Modifying Elements Of An Html Document

I used a WebBrowser to login and extract required data. Here are some important methods.

Reading an attribute of an element:

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

Changing an attribute of an element:

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

Calling a javascript function that is inside a WebBrowser object from c#:

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

Downloading Files From A Website

There is a very simple method for downloading files from a website. I used that method to download images related with an article.

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

Disabling Click Sound In A WebBrowser

There isn't a property to disable the click sound in a web browser. But there is a way to do that using 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{}
    }
}

Screenshots

This is the main window of the program:

Codeproject article editor main window

If you want to backup your published articles to edit or just to save them you can enter your email and password to login. Your articles will be listed after you logged in: 

Welcome after login

article list

When you choosed one of them, some informations about it will appear:

Informations about selected article

You can write a new article or edit an existing one even if you are not connected to the internet:

New article

Transferring Articles To CodeProject's Online Editor

  1. Select "Copy source to the clipboard" from the right-click menu.
    (Note that all absolute image paths are converted to relative paths. So you don't have to change anything on copied html codes.)
  2. Open codeproject online editor.
  3. Click on "HTML" button to switch to HTML mode:
  4. Paste your contents. (CTRL+V) 
  5. Click again on "HTML" button to switch back to design mode...  

Points Of Interest

Let me list things that we can learn while examining the source codes:

  • WYSIWYG html editor in .NET.
  • How to use ckeditor in .NET?
  • How to call a javascript function inside a WebBrowser from a c# function?
  • Automated login and data extraction.
  • Using Actions instead of Events for asynchronous functions in c#.
  • Disabling click sound in a WebBrowser control.
  • Downloading files from internet in c#.

History

  • 4 April 2013 (v1.0.3) 
    • Logins have failed recently because of some updates on CodeProject. Fixed.  
    • Random strings were appended to the urls to force the WebBrowser to reload cached pages. 
  • 17 October 2012 (v1.0.2)
    • New codes to remember last browsed path 
    • A small change in extractMemberId() function
    • "Open with" support
      (You can use "Open with" menu or drag a cpa file and drop it on icon of the program.)
    • A unique file header to validate CPA (CodeProject Article) files
      (Because of that old cpa files cannot be opened with this version.)
  • 5 October 2012 (v1.0.1)
    • "ctl00" became "ctl01" on the login page of codeproject. So a new function (getElementId()) was written to get IDs of elements using Regex class and this function was used instead of all old constants that will be able to be changed by codeproject in future.
  • 1 October 2012 (v1.0.0)
    • First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Huseyin Atasoy
Engineer
Turkey Turkey
Member
I am interested in artifical intelligence, signal processing, electronics, robotics and internet/network technologies.
 
A few of my works:
Real-time face recognition
Real-time facial emotion recognition
LANPhone [Android, Windows]
Javascript encryptor
 
My blog

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberRajesh Pillai7 Oct '12 - 21:07 
Thanks for sharing.
GeneralMy vote of 5memberАslam Iqbal6 Oct '12 - 19:46 
what cool engineeeer's article. my 5
GeneralRe: My vote of 5memberHuseyin Atasoy7 Oct '12 - 7:39 
Thanks.
QuestionLooks very good, but why the use "different way" use of threading ?memberBillWoodruff5 Oct '12 - 14:20 
Hi,
 
I voted this a 4; certainly looks like a useful tool, and one you put a lot of work into. I'm going to try it out today.
 
But, I am curious what this statement: "Generally we are using events for operations that must be done after an asynchronous method finished its missision or an event occured. But I used a different way for this purpose and I think it is relatively simpler to understand" means.
 
I'd like to know what problem your "different way" solved, or what advantage your "different way" gives your application. Or, just plain: why you needed to use multi-threading here in the first place.
 
thanks, Bill
~
Confused by Windows 8 ? This may help: [^] !

AnswerRe: Looks very good, but why the use "different way" use of threading ?memberHuseyin Atasoy6 Oct '12 - 4:35 
Thanks for the feedback.
My "different way" is only an alternative approach.
We have a WebClient and we are using it to download files. You know, DownloadFile() function blocks the thread it is called from. That is why we need a new thread.
On the other hand I used DocumentCompleted event. When this event is fired we must do the next operation.(Navigate to the login page > fill the form and submit > navigate to article list) All these functions are asynchronous, because we are calling them from DocumentCompleted event.
What my way alternative for?
In such situations "generally" we use events. I can fire an event instead of invoking an action when DocumentCompleted event is fired. But I think using Actions and an invoker is simpler than using events in c#. In vb.net events are easier and this approach looks like them.
Finally, I can't use English efficiently. So things that I can explain are limited. I am sorry about that.
Please examine the source code, I think you will understand it easly.
Thanks for the feedback again...
GeneralRe: Looks very good, but why the use "different way" use of threading ?memberBillWoodruff6 Oct '12 - 15:22 
Thanks for your thoughtful response which does clarify for me what you are doing. Since I am so "socialized" to using C# Events, perhaps that makes me less "open" to an alternate approach.
 
I have changed my vote to a #5 on this article.
 
thanks, Bill
~
Confused by Windows 8 ? This may help: [^] !

GeneralMy vote of 4memberAl-Samman Mahmoud5 Oct '12 - 10:00 
Very Good
GeneralRe: My vote of 4memberHuseyin Atasoy6 Oct '12 - 4:01 
Thank you.
GeneralMy vote of 5memberfredatcodeproject5 Oct '12 - 9:12 
pretty good
I like it
GeneralRe: My vote of 5memberHuseyin Atasoy6 Oct '12 - 3:59 
Thanks.
GeneralMy vote of 4memberAlluvialDeposit5 Oct '12 - 1:42 
This is a great idea!
But I'll hav to give you a 4 because of some missing features:
1) Remember login information. I don't want to login each time I open this program. And when I reopen and try to log in I get this message "You are already logged in. Please don't forget to logout before you exit the program..." And the i get logged in.. (a small bug i guess)
2) If I right click a cpa-file and choose to open with your program, it wont. I have to open the program, hit "browse" and find my cpa file.
3) It should remember my path's. When I click "create new" it should remember my last browsed path.
 
But it's a graet program and I think I'll use it when writing my next article Smile | :)
GeneralRe: My vote of 4memberHuseyin Atasoy5 Oct '12 - 3:43 
Frown | :( I want it to be portable and so I didn't implement such features. But these are easy to implement. I will do these when I found some free time...
AlluvialDeposit wrote:
And when I reopen and try to log in I get this message "You are already logged in. Please don't forget to logout before you exit the program..." And the i get logged in.. (a small bug i guess)
No, this is not a bug. This can be seen as a feature like "Remember Me". You don't have to write your email and password if you were logged in before and didn't logged out. If you don't want to get such a message, you should click on "Logout" button before you close the program.
Thanks for the suggestions Smile | :)
GeneralRe: My vote of 4memberHuseyin Atasoy16 Oct '12 - 23:43 
Those features are added. You can download the new version. (1.0.2)
GeneralSounds Good!memberShemeer NS4 Oct '12 - 23:42 
I was thinking something like this Smile | :)
Good JobThumbs Up | :thumbsup: Thanks for sharing
GeneralRe: Sounds Good!memberHuseyin Atasoy5 Oct '12 - 3:16 
Smile | :) Thank you.
GeneralMy vote of 5memberwangqi11314 Oct '12 - 23:07 
good
GeneralRe: My vote of 5memberHuseyin Atasoy5 Oct '12 - 3:47 
Thanks you.
GeneralMy vote of 4memberCésar de Souza4 Oct '12 - 4:15 
Seems like a great idea! However, some of the pictures on the article aren't loading. The Points of Interest section also seems a bit incomplete; is it finished?
 
Otherwise, great job man Smile | :) did you think about putting a ribbon on top of it? :P
GeneralRe: My vote of 4memberHuseyin Atasoy4 Oct '12 - 5:18 
Codeproject servers are sometimes slow in these days. But I can see all the images now, maybe it was a temporary problem.
I listed somthing that we can learn from the source in the "Points of Interest" section. I am sorry if this section is not for such purposes.
And thanks for your vote and comment.
GeneralRe: My vote of 4memberCésar de Souza4 Oct '12 - 5:34 
Indeed, the images are loading now. It must have been some temporary problem! Still, great article. I will change my vote into a 5 Smile | :)
 
By the way, you have some very interesting works listed on your profile. I was really interested in the workings of your real-time facial emotion recognition; I followed the links the the pages are in Turkish. Have you ever posted something in English about it? It seems really interesting!
 
Best regards,
Cesar
Interested in Machine Learning in .NET? Check the Accord.NET Framework.
See also Handwriting Recognition Revisited: Kernel Support Vector Machines

GeneralRe: My vote of 4memberHuseyin Atasoy4 Oct '12 - 7:37 
Unfortunately no. The article that I have written about it is available only in Turkish and I don't know if google translate can work on pdf files.
Thanks for 5 Smile | :)
GeneralMy vote of 5memberKenneth Haugland4 Oct '12 - 4:00 
Im loving it Big Grin | :-D Very nice work.
GeneralRe: My vote of 5memberHuseyin Atasoy4 Oct '12 - 5:00 
Thanks Smile | :)
GeneralMy vote of 5memberAkram El Assas3 Oct '12 - 10:28 
Nice one. That's the missing piece of CP Vanity[^].
GeneralRe: My vote of 5memberHuseyin Atasoy4 Oct '12 - 1:09 
Thanks for your vote.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 4 Apr 2013
Article Copyright 2012 by Huseyin Atasoy
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid