Solution:NIIT/GNIIT Sonugiri0032@gmail.com

Friday, February 05, 2016

Tiny Java Editor

Tiny Java Editor

Update

This project has been updated with new features. The image illustrates the new GUI.
Features include:
  • Open a whole folder as a project
  • Add multiple projects
  • Save all opened files at once
  • Compile the currently selected file
  • Create new folders and files in selected project
  • View image files

INTRODUCTION

Lately I've been trying to brush up on some of my Java. It's been some time since I wrote a piece ofJava code. I downloaded the latest Java SDK and began writing some basic code in Notepad and used the command prompt to execute my programs. This worked well until I began working with more Javafiles. I found myself using more command prompts and this just got out of control. Now you're probably wondering why I didn't just download a Java editor such a NetBeans or Eclipse. Well my poor laptop is running various applications and using enough of my limited memory as it is, I didn't want to install another bulky IDE that I would use rarely. I was about to do a Google search for a lightweightJava editor when I was hit with a light bulb moment. I thought about developing my own editor. I mean how hard could it be?

DESIGN

I began to design the application and like most developers I did this in my head. After all it’s a very simple editor. My first attempt at designing the UI consisted of the following controls:
  • MenuStrip – To hold menus such as New, Open file
  • TabControl – To contain RichTextBox to create/edit files
With the design complete, I began implementing the code.

CODE

I removed the default TabPages from the TabControls collection. I would add a new TabPage programmatically when the New File menu was clicked. Listing 1.0 below shows the code for theNewFile() method.

Listing 1.0

public void NewFile()
{
    string Filename = "Untitled" + (FileTabControl.TabCount + 1);
    TabPage Page = new TabPage();
    Page.Name = Filename;
    Page.Text = Filename;

    RichTextBox Editor = new RichTextBox();
    Editor.AcceptsTab = true;
    Editor.Dock = DockStyle.Fill;

    Page.Controls.Add(Editor);

    FileTabControl.TabPages.Add(Page);
}
The above method creates a TabPage with a RichTextBox control. By default, all TabPages are titled "Untitled" followed by a number which is the total tab count.
With the NewFile() method implemented, it was time to move onto the SaveFile() method, another easy implementation I thought. The SaveFile() method would get the selected tabs text from the RichTextBox control and show the SaveFileDialog. Getting the text from theRichTextBox control was easy as all I had to do was use the ControlCollection class to get all controls placed in the TabPage. This was done using a simple foreach loop.
After getting the text from the RichTextBox control, I displayed the SaveFileDialog and using an instance of the StreamWriter class, I managed to save the file.
Happy with my code so far, I ran my project and everything worked fine. However I realised that if I wanted to save my updated file and clicked on the SaveFile menu, the SaveFileDialog would show again and prompt me to save the file even though it had been previously saved.
What I needed was a way to determine if a file was previously saved so I can take the appropriate action, either save the file for the first time or override the existing file.
This was my first problem and I decided I would deal with it quickly by created a custom class calledFileObject. Listing 1.1 below shows the FileObject class.

Listing 1.1

public class FileObject
{
    private bool _SaveState = false;
    private string _FilePath;
    private string _FileName;
    
    public bool SaveState
    {
        get
        {
            return this._SaveState;
        }
        set
        {
            this._SaveState = value;
        }
    }
    
    public string FilePath
    {
        get
        {
            return this._FilePath;
        }
        set
        {
            this._FilePath = value;
        }
    }
    
    public string FileName
    {
        get
        {
            return this._FileName;
        }
        set
        {
            this._FileName = value;
        }
    }
}
What does this class have to do with my problem you ask? Well the TabPage class has a property called Tag. You can use this property to get/set an object. By supplying this Tag with an object that held information about the current TabPage, I could determine if a selected TabPage was previously saved by checking the _SaveState variable in the FileObject class.
This meant I would have to modify the NewFile() method and set the Tag property of the TabPageusing an instance of the FileObject class. When I save the file, I can get the FileObject from theTag property and check if the file was previously saved. Listing 1.2 below shows the modifiedNewFile() and listing 1.3 shows the SaveFile() method.

Listing 1.2

public void NewFile()
{
    string Filename = "Untitled" + (FileTabControl.TabCount + 1);
    FileObject FileObj = new FileObject();
    FileObj.SaveState = false;
    FileObj.FileName = Filename;
    
    TabPage Page = new TabPage();
    Page.Name = Filename;
    Page.Text = Filename;
    Page.Tag = FileObj;
    
    RichTextBox Editor = new RichTextBox();
    Editor.AcceptsTab = true;
    Editor.Dock = DockStyle.Fill;
    
    Page.Controls.Add(Editor);
    
    FileTabControl.TabPages.Add(Page);
}

Listing 1.3

public void SaveFile()
{
    TabPage Page = FileTabControl.SelectedTab;
    Control.ControlCollection TxtCollection = Page.Controls;
    
    string FileData = "";
    
    foreach (RichTextBox Editor in TxtCollection)
    {
        FileData = Editor.Text;
    }
    
    FileObject FileObj = (FileObject)Page.Tag;
    
    if (FileObj.SaveState == false)
    {
        SaveFileDialog SaveDialog = new SaveFileDialog();
        SaveDialog.Filter = "Java|*.java";
        DialogResult SaveResult = SaveDialog.ShowDialog();
        
        if (SaveResult == DialogResult.OK)
        {
            string FileName = SaveDialog.FileName;
            string FilePath = Path.GetFullPath(FileName);
            FileObj.SaveState = true;
            FileObj.FilePath = FilePath;
            FileObj.FileName = FileName;
            
            StreamWriter FileWriter = new StreamWriter(FilePath);
            FileWriter.Write(FileData);
            FileWriter.Flush();
            FileWriter.Close();
            FileWriter.Dispose();
            
            Page.Tag = FileObj;
            Page.Text = Path.GetFileName(FileName);                   
        }
    }
    else
    {
        string FilePath = FileObj.FilePath;
        StreamWriter FileWriter = new StreamWriter(FilePath);
        FileWriter.Write(FileData);
        FileWriter.Flush();
        FileWriter.Close();
        FileWriter.Dispose();
    }
I tested my application again and this time a previously saved file was saved again without showing the SaveFileDialog.
Now I can create new files and save them. All that is needed now is to compile and run the files. This is where the Process class is introduced. I used this class to run a process. This process simply executed the Javac and Java executables files with a filename as its argument. All I had to do was create a Compile button and run these processes.
But wait, what file would I be compiling? The currently selected file? Would I need to save the file first before compiling? So many questions and very quickly problems started occurring.
To solve this problem, I thought of how Visual Studio compiles a program. When you click the Run button in Visual Studio, the project is saved and Visual Studio tries to compile the program.
I decided that I didn't want to have to save all the files individually before compiling a file. I wanted the Compile button to loop through all the TabPages and save each file. As each file is being saved, a new process should be executed which should try and compile the file. The output from the process should be captured and displayed in a textbox.
This meant I had to modify the UI to show the output from the process. I added two new controls to the form:
  • SplitContainer
  • TabControl (OutputTabContainer) contained within Panel2 of the SplitContainer
I copied the existing TabControl and pasted it into Panel1 of the SplitContainer control. TheOutputTabContainer consists of two TabPages (ConsolePage and ErrorPage). TheConsolePage shows any output from the process which is not an error whilst the ErrorPage shows any output from the process which is an error.
With the UI controls in place, I began to work on the code for the Compile button. I created three methods (CompileJavaand Java). The Compile method is responsible for creating a new process, it takes three arguments and returns a Boolean indicating whether the process started successfully. Listing 1.4 below shows the Compile() method.

Listing 1.4

public bool Compile(string EXE, string WorkingDirectory, string FileName)
{
    _Compiler.StartInfo.FileName = EXE;
    _Compiler.StartInfo.Arguments = FileName;
    _Compiler.StartInfo.WorkingDirectory = WorkingDirectory;
    _Compiler.StartInfo.CreateNoWindow = true;
    _Compiler.StartInfo.ErrorDialog = false;
    _Compiler.StartInfo.UseShellExecute = false;
    _Compiler.StartInfo.RedirectStandardOutput = true;
    _Compiler.StartInfo.RedirectStandardError = true;
    bool processStarted = _Compiler.Start();
    return processStarted;
}
The first argument is the executable file to launch, the second argument is the working directory of theJava file and the third is the file name of the Java file. The working directory argument is needed as there may be Java files in different directories.
The Compile() method is called from within the Javac() and Java() methods. The Javac()method calls the Compile() method and provides the Compile() method with its arguments. It is also responsible for capturing the output from the process started by the Compile() method. TheJavac() method only checks for output errors, this is when a Java file cannot compile for any particular reason, the output error is displayed in the ErrorPage TabPage.
Remember that the Javac() method is called for every TabPage. If there are any output errors, a global Boolean variable (_CompileProgram) is set to false. If there were no output errors, the variable is set to true.
By setting this variable, the Compile button can determine whether to move to the next phase of the compiling process, which is to execute a file. Listing 1.5 below shows the Javac() method.

Listing 1.5

public void Javac(string FileName)
{
    string FullPath = Path.GetFullPath(FileName);
    int LastIndex = FullPath.LastIndexOf('\\') + 1;
    string WorkingDirectory = FullPath.Substring(0, LastIndex);
    
    if (this.Compile(_JavacPath, WorkingDirectory, Path.GetFileName(FileName)))
    {
        _ErrorReader = _Compiler.StandardError;
        _ErrorOutput = _ErrorReader.ReadToEnd();
        
        if (_ErrorOutput != "")
        {
            ErrorOutput.AppendText(_ErrorOutput);
            this._CompileProgram = false;
        }
        else
        {
            this._CompileProgram = true;
        }
    }
}
Only when the global variable _CompileProgram is set to true is when the Java() method is called. This method is only called once unlike the Javac() method which is called for every TabPage. The reason for this is because, not every Java file will contain a main method. As you know, a main()method is required in many programming languages as it is the main point of program execution.
The Tiny Java Editor may consist of files which do not have a main() method. For this reason, we cannot call the Java() method for every TabPage. So how then do we execute a Java file.
I decided to place a ComboBox control on the form which would contain a list of all the files. This list is populated every time a new file is added or saved. Using this ComboBox, I can select a single file, which would be a Java file that consists of a main() method. Listing 1.6 below shows the Java() method:
Listing 1.6
public void Java(string FileName)
{
    string FullPath = Path.GetFullPath(FileName);
    int LastIndex = FullPath.LastIndexOf('\\') + 1;
    string WorkingDirectory = FullPath.Substring(0, LastIndex);
    
    if (Compile(_JavaPath, WorkingDirectory, Path.GetFileNameWithoutExtension(FileName)))
    {
        _OutputReader = _Compiler.StandardOutput;
        _ConsoleOutput = _OutputReader.ReadToEnd();
        ConsoleOutput.Text = _ConsoleOutput;
    }
}
Notice in the above code, the StandardOutput from the _Compiler process is being retrieved unlike theJavac() method which was getting the StandardError. By now the project was complete. I was able to create/save files as well as compile them.

HIGHLIGHTING LINE WITH ERROR

In the middle of a tea break, I though wouldn't it be great to highlight the line an error occurred. The output from the StandardError contains the filename and the line number of the error. All I had to do was get a reference to the RichTextBox control, find the line and highlight it. I modified the Javac() method to reflect the code in Listing 1.7.

Listing 1.7

public void Javac(string FileName, RichTextBox Editor)
{
    string FullPath = Path.GetFullPath(FileName);
    int LastIndex = FullPath.LastIndexOf('\\') + 1;
    string WorkingDirectory = FullPath.Substring(0, LastIndex);
    
    if (this.Compile(_JavacPath, WorkingDirectory, Path.GetFileName(FileName)))
    {
        _ErrorReader = _Compiler.StandardError;
        _ErrorOutput = _ErrorReader.ReadToEnd();
        int ErrorLineNumber = 0;
        
        try
        {
            string[] ErrorLines = _ErrorOutput.Split('\n');
            string[] Error = ErrorLines[0].Split(':');
            ErrorLineNumber = Convert.ToInt32(Error[1]) - 1;
        }
        catch (Exception e) { }
           
        if (_ErrorOutput != "")
        {
            ErrorOutput.AppendText(_ErrorOutput);
            
            try
            {
                int FirstCharIndex = Editor.GetFirstCharIndexFromLine(ErrorLineNumber);
                int LineLength = Editor.Lines[ErrorLineNumber].Length;
                Editor.Select(FirstCharIndex, LineLength);
                Editor.SelectionBackColor = Color.Purple;
            }
            catch (Exception e) { }
            
            this._CompileProgram = false;
        }
        else
        {
            this._CompileProgram = true;
        }
    }
}
Tiny_Java_Editor/image2.png
Tiny_Java_Editor/image3.png
Finally I had a Tiny Java Editor, which I could use to create my Java programs. Did my laptop like my Tiny Java Editor? No it didn't. Why? Because the application freezes when you try to execute a Javaprogram with a GUI. The reason for this is because the _Compile process should be executed in a different thread and not on the main thread as it is in this case.
Share:

0 comments:

GNIITSOLUTION GNIIT SOLUTION. Powered by Blogger.

Translate

Blog Archive

Unordered List