Home

Accessories for File Processing

Introduction to the Save File Dialog Box

Description

Many applications allow users to open or display a document. Such application expect the user to create a document. Once a document has been created, a user would usually want to store its contents on a medium (hard drive, floppy disk, etc). Microsoft Windows provides a common dialog box for this purpose: The Save File dialog box:

The Save As Dialog Box

The primary role of the Save File dialog box is to allow users to store a file on a drive of a computer. To make this efficient and complete, the user must supply two valuable pieces of information: the location and the name of the file. The location of a file is also known as its path. The name of a file follows the directives of the operating system.

Two objects are particularly important on the Save File dialog box: The Save In combo box and the File Name text box or combo box. To help with this, the Save File dialog box is equipped with a Save As Type combo box. This combo box allows the user to select a file extension. The available extensions have to be created by the programmer so the user can select from a preset list. If you, the programmer, specify a series of extensions, the user can select one of them.

If the user does not specify an extension, the operating system would associate the extension of the Save As Type combo box.

After working on a Save As dialog box, the user can click Save or press Enter to save the file. Otherwise, the user can click Cancel or press Esc to give up saving the file.

Practical LearningPractical Learning: Introducing File Processing Accessories

  1. Start Microsoft Visual Studio and create a Windows Forms application named GasUtilityMeters3
  2. Design the form as follows:

    Introduction to the Save File Dialog Box

    Control (Name) Text
    GroupBox gbxGasMeter New Gas Meter
    Label   Meter #:
    TextBox txtMeterNumber  
    Label   Make:
    TextBox txtMake  
    Label   Model:
    TextBox txtModel  
    Label   Meter Reading Date:
    MonthCalendar mcMeterReadingDate  
    Label   Counter Value:
    TextBox txtCounterValue  
    GroupBox gbxActionSelection Action Selection
    Button btnSave Save

Creating a Save As Dialog Box

To support the Save File dialog box, the .NET Framework provides a class named SaveFileDialog. To make a Save File dialog box available to your application, on the Toolbox, click the SaveFileDialog button and click the form. To programmatically provide this dialog box, declare a SaveFileDialog variable and initialize it using the class's default constructor. Here is an example:

using System;
using System.Windows.Forms;

public class Exercise : Form
{
    public Exercise()
    {
		InitializeComponent();
    }

    void InitializeComponent()
    {
        SaveFileDialog sfd = new SaveFileDialog();
    }
}

The SaveFileDialog class inherits from a class named FileDialog. The Save File dialog box gets most of its characteristics and behaviors from that parent class.

Practical LearningPractical Learning: Introducing the Save Dialog Box

  1. In the Toolbox, click Dialogs
  2. Click SaveFileDialog and click the form
  3. In the Properties window, click (Name) and type sfDialogBox

Characteristics of the Save As Dialog Box

The Title of the Dialog Box

By default, when the Save File dialog box comes up, it displays a caption on its title bar. To support the caption of this dialog box, the the FileSaveDialog class is equipped with a property named Title. To visually specify the caption of the dialog box, type the desired string in the Title field of the Properties window of the Save file dialog box. To do this programmatically, assign the desired string to the FileSaveDialog.Title property.

Practical LearningPractical Learning: Setting the Title of the Dialog Box

The File Extension

To make sure that your application can open the appropriate types of files, you should create a list of extensions that you want the users to be able to use.

The Filter

The extensions allowed on an application form a group called a filter. The filter is like a funnel that selects the good items. To create a list of allowable extensions for your FileSaveDialog object, you can use the Filter property from the Properties window. You can programmatically create a list of file extensions as a string. If the Save File dialog box will need only one extension, you can create the string using the following syntax:

Prompt|Extension

The Prompt is a section that defines what the user would see in the Save As Type combo box. An example would be 24-bit Bitmap. Such a string doesn't let the user know what actual extension the file would use. Therefore, as a courtesy, you can specify, between parentheses, the extension that would be applied if this extension is used.

The Prompt can be 24-bit Bitmap (*.bmp). In this case, the extension used would be bmp. The asterisk * lets the user know that whatever is provided as the file name would be used in place of the asterisk. The period indicates the separation from the file to its extension. This means that the characters on the left of the period would be the file name, the characters on the right side of the period would be used as the actual file extension.

To specify the extension that the operating system would use to associate to the file, you provide a second part of the string as Extension. An extension can use one letter such as "c" for a file of the C programming language, two characters such as pl for a file of the Perl programming language, three letters such as "bmp" for a graphics file, four letters such as "html" (or html) for a Web file, etc for some HTML files). The extension depends on the programmer (or the company that is publishing the application). Here is an example of creating a filter:

24-bit Bitmap (*.bmp)|*.bmp

If you want to provide various extensions to your Save File dialog box, you can separate them with a | symbol. An example would be:

HTML Files (*.htm)|*.htm|Active Server Pages (*.asp)|*.asp|Perl Script (*.pl)|*.pl

If you added a FileSaveDialog control to a form, as mentioned earlier, you can type the filter string in the Filter field of the Properties window. If your programmatically creating the dialog box, you can assign the string to the Filter property. Here is an example:

public class Exercise : Form
{
    private SaveFileDialog sfd;

    public Exercise()
    {
	InitializeComponent();
    }

    void InitializeComponent()
    {
        sfd = new SaveFileDialog();
		sfd.Filter = "HTML Files (*.htm)|*.htm|" +
                     "Active Server Pages (*.asp)|*.asp|" +
                     "Apache Files (*.php)|*.php|" +
                     "Perl Script (*.pl)|*.pl|" +
                     "All Files|";
    }
}

This would produce:

The Save As dialog box with various file extensions

Practical LearningPractical Learning: Specifying the Filter

The Filter Index

A filter organizes its extensions and categories as indexes. The above filter has the following indexes:

Index 1 = HTML Files (*.htm)
Index 2 = Active Server Pages (*.asp)
Index 3 = Apache Files (*.php)
Index 4 = Perl Script (*.pl)
Index 5 = All Files;

After creating a filter, when the dialog box comes up, the Save As Type combo box displays the first index of the filter. If you want, instead of displaying the first index by default, you can specify another index. To let you specify the desired index, the FileSaveDialog class is equipped with a property named FilterIndex. To visually set the filter, access the Properties window of the Save File dialog box. Click the FilterInder field and type the appropriate value. To programmatically specify it, assign a value to the FileSaveDialog.FilterIndex property. Here is an example:

public class Exercise : Form
{
    private SaveFileDialog sfd;

    public Exercise()
    {
	InitializeComponent();
    }

    void InitializeComponent()
    {
       	sfd = new SaveFileDialog();
		sfd.Filter = "HTML Files (*.htm)|*.htm|" +
                     "Active Server Pages (*.asp)|*.asp|" +
                     "Apache Files (*.php)|*.php|" +
                     "Perl Script (*.pl)|*.pl|" +
                     "All Files|";

    	sfd.FilterIndex = 3;
    }
}

The Default Extension

The default extension is the extention that automatically applies when a user saves a document. To visually specify the default extension for your FileSaveDialog object, type the desired extension in the DefaultExt field of the Properties window. If you had created a Filter and if you provide a default extension for a FileSaveDialog object, make sure it is one of the file extensions specified in the Filter list. To programmatically specify a default extension, assign it to the DefaultExt property of your FileSaveDialog variable. Here is an example:

public class Exercise : Form
{
    private SaveFileDialog sfd;

    public Exercise()
    {
		InitializeComponent();
    }

    void InitializeComponent()
    {
        sfd = new SaveFileDialog();
		sfd.Filter = "HTML Files (*.htm)|*.htm|" +
                     "Active Server Pages (*.asp)|*.asp|" +
                     "Apache Files (*.php)|*.php|" +
                     "Perl Script (*.pl)|*.pl|" +
                     "All Files|";
    	sfd.FilterIndex = 3;

    	sfd.DefaultExt = "htm";
    }
}

Practical LearningPractical Learning: Specifying the Default Extension

The Initial Directory

When a user decides to save a file, usually by default, the Save File dialog box chooses a folder named My Documents as the target repository. In some cases, you want to designage a directory of your choice. To support this change, the FileSaveDialog class is equipped with a property named InitialDirectory. To specify the primary folder that the Save File dialog box must select, assign a path to this property. Of course, you can do this visually or programmatically.

Practical LearningPractical Learning: Setting the Initial Directory

The File Name

Probably the most important issue users care about, as far as they are concerned, is a name for the file they are trying to save. Users know that they can set the name of the file in the File Name box. To make this action a little faster, you can provide a default name for a file in case a user does not want to specify a file name.

To support the name of the file of a Save File dialog box, the SaveFileDialog class is equipped with a property named FileName. Therefore, to specify a default file name, access the Properties window of a Save File box. Type the desired name in the FileName field.

Displaying the Dialog Box

Once a SaveFileDialog object is ready, to let you display it to the user, the class is equipped with a method named ShowDialog. Here is an example of calling it:

using System;
using System.Windows.Forms;

public class Exercise : Form
{
    private SaveFileDialog sfd;

    public Exercise()
    {
		InitializeComponent();
    }

    void InitializeComponent()
    {
        sfd = new SaveFileDialog();
        sfd.Filter = "HTML Files (*.htm)|*.htm|" +
                     "Active Server Pages (*.asp)|*.asp|" +
                     "Apache Files (*.php)|*.php|" +
                     "Perl Script (*.pl)|*.pl|" +
                     "All Files|";
        sfd.FilterIndex = 3;
        sfd.DefaultExt = "htm";

        sfd.ShowDialog();
    }
}

Practical LearningPractical Learning: Displaying a Save File Dialog Box

  1. On the form, double-click the Save button
  2. Change the document as follows:
    using System;
    using System.IO;
    using System.Windows.Forms;
    
    namespace GasUtilityMeters3
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
    
                /* If you want to use a drive other than the C:> drive, 
                 * change the letter in the following line. */
                Directory.CreateDirectory(@"C:\Gas Utility Company");
            }
    
            private void btnSave_Click(object sender, EventArgs e)
            {
                if (string.IsNullOrEmpty(txtMeterNumber.Text))
                {
                    MessageBox.Show("You must type a (unique) number for the gas meter.", "Gas Utility Company");
    
                    // If the user doesn't provide a meter number, don't do nothing.
                    return;
                }
    
                if (string.IsNullOrEmpty(txtMake.Text))
                {
                    MessageBox.Show("Please enter the name of the gas meter manufaturer.", "Gas Utility Company");
    
                    // If the user doesn't provide the meter manufaturer, don't do nothing.
                    return;
                }
    
                if (string.IsNullOrEmpty(txtModel.Text))
                {
                    MessageBox.Show("Can you identify the model of the gas meter? Type it.", "Gas Utility Company");
    
                    // If the user doesn't provide the meter model, don't do nothing.
                    return;
                }
    
                if (string.IsNullOrEmpty(txtCounterValue.Text))
                    MessageBox.Show("Type the initial or current reading counter on the gas meter.", "Gas Utility Company");
    
                /* We already have a directory where we will save the files of this application:
                 * Company File Repository: C:\Gas Utility Company */
                /* Each gas meter will have its own file. The name of the file of
                 * a gas meter will be the meter number and the "gms" extension. */
    
                sfDialogBox.FileName = txtMeterNumber.Text + ".gms";
                // sfDialogBox.InitialDirectory = @"C:\Gas Utility Company";
    
                if (sfDialogBox.ShowDialog() == DialogResult.OK)
                {
                    /* Create a file stream that can be used to create a new file, 
                     * that can access a file to write to it, and that can allow 
                     * clients (such as other/networked computers to write to it. */
                    FileStream fsGasMeters = new FileStream(sfDialogBox.FileName,
                                                            FileMode.Create, FileAccess.Write, FileShare.Write);
                    // Create file writer that will be used to write values to the file stream
                    BinaryWriter bwGasMeter = new BinaryWriter(fsGasMeters);
    
                    // Get each value from the form and write it to the file
                    bwGasMeter.Write(txtMeterNumber.Text);
                    bwGasMeter.Write(txtMake.Text);
                    bwGasMeter.Write(txtModel.Text);
                    bwGasMeter.Write(mcMeterReadingDate.SelectionStart.ToShortDateString());
                    bwGasMeter.Write(txtCounterValue.Text);
    
                    // After using the writer, close it
                    bwGasMeter.Close();
                    // After using the file stream, close it
                    fsGasMeters.Close();
    
                    // After saving the file, reset the form by setting the initial/empty values to the controls
                    txtMake.Text = "";
                    txtModel.Text = "";
                    txtMeterNumber.Text = "";
                    mcMeterReadingDate.SelectionStart = DateTime.Today;
                    txtCounterValue.Text = "0";
                }
            }
        }
    }
  3. To execute, on the main menu, click Debug -> Start Without Debugging

    Displaying a Save File Dialog Box

  4. Enter or select the following values:
    Meter #:            520246-85
    Make:               Garland Worldwide
    Model:              GFH-2260
    Meter Reading Date: 05/21/2021
    Counter Value:      22683
  5. Click the Save button:

    Displaying a Save File Dialog Box

  6. Accept the file name as 520246-85 (or accept that name) and click Save
  7. Close the form and return to your programming environment
  8. Click the Form1.cs [Design] tab to access the form

Introduction to the Open File Dialog Box

Description

Besides saving files, another common operation performed by users consists of opening files. This refers to existing files since a file must primarily exist. To support this operation, Microsoft Windows provides a standard object: the Open File dialog box:

Description of the Open File Dialog Box

The Open File Dialog Box Creation

To provide file opening support, the .NET Framework provides a class named OpenFileDialog. Like the SaveFileDialog class, the OpenFileDialog is derived from the FileDialog class.

To visually provide an Open File dialog box to your application, in the Toolbox, click the OpenFileDialog button and click the form. You can click anywhere on the form because the OpenFileDialog button would not appear to the user. To dynamically create an Open dialog box, declare a variable of type OpenFileDialog and use the new operator to allocate memory using its default constructor. Here is an example:

using System;
using System.Windows.Forms;

public class Exercise : Form
{
    public Exercise()
    {
	InitializeComponent();
    }

    void InitializeComponent()
    {
        OpenFileDialog ofd = new OpenFileDialog();
    }
}

Practical LearningPractical Learning: Introducing the Open File Dialog Box

  1. In the Toolbox, under Dialogs, click OpenFileDialog and click the form
  2. In the Properties window, click (Name) and type ofDialogBox
  3. Complete the design of the form as follows:

    Introducing the Open File Dialog Box

    Control (Name) Text Other Properties
    GroupBox gbxGasMeter New Gas Meter  
    Label   Meter #:  
    TextBox txtMeterNumber    
    Label   Make:  
    TextBox txtMake    
    Label   Model:  
    TextBox txtModel    
    Label   Meter Reading Date:  
    MonthCalendar mcMeterReadingDate    
    Label   Counter Value:  
    TextBox txtCounterValue    
    GroupBox gbxActionSelection Action Selection  
    CheckBox chkOpenSave Open Existing Record Appearance: Button
    Button btnSave Save  
    Button btnOpen Open Visible: False

Characteristics of an Open File Dialog Box

The File Name

Like the Save File dialog box, the Open File dialog box is equipped with a property named FileName. If you want a default file to be specified when the dialog box comes up, you can specify this in the FileName property of the Properties window.

The Filter

Like the Save File dialog box, the Open File dialog box uses a filter to identify the types of files its application can deal with. The extensions of the files that an appliction supports are created as we saw already. Here is an example:

void InitializeComponent()
{
    ofd = new  OpenFileDialog ();
    ofd.Filter = "HTML Files (*.htm)|*.htm|" +
                 "Active Server Pages (*.asp)|*.asp|" +
                 "Apache Files (*.php)|*.php|" +
                 "Perl Script (*.pl)|*.pl|" +
                 "All Files|";
}

The Filter Index

Like the Save File Dialog control, the default extension is the one the dialog box would first filter during file opening. If you want the Open dialog box to easily recognize a default type of file when the dialog box opens, you can specify the extension's type using the DefaultExt property. You can also use the FilterIndex we saw earlier to indicate the default index of the Files of Type combo box.

The Initial Folder

For convenience, or for security reasons, Open dialog boxes of applications are sometimes asked to first look for files in a specific location when the Open File dialog box comes up. This default folder is specified using the InitialDirectory property.

Practical LearningPractical Learning: Specifying the Filter

  1. Below the form, click ofDialogBox
  2. In the Properties window, click Filter and type
    Gas Meter Record (*.gms)|*.gms|Gas Utility Company Files (*.guc)|*.guc|All Files (*.*)|(*.*)
  3. Click Title and type Gas Utility Company - Opening Gas Meter Record
  4. Click InitialDirectory and type C:\Gas Utility Company

Showing the Dialog Box

The essence of using the Open dialog box is to be able to open a file. This job is handled by the ShowDialog() method. Here is an example:

using System;
using System.Drawing;
using System.Windows.Forms;

public class Exercise : Form
{
    Button btnOpen;
    private OpenFileDialog ofd;

    public Exercise()
    {
        InitializeComponent();
    }

    void InitializeComponent()
    {
        ofd = new  OpenFileDialog ();
        ofd.Filter = "HTML Files (*.htm)|*.htm|" +
                     "Active Server Pages (*.asp)|*.asp|" +
                            "Apache Files (*.php)|*.php|" +
                                      "Perl Script (*.pl)|*.pl|" +
                                      "All Files|";
        btnOpen = new Button();
        btnOpen.Text = "Open";
        btnOpen.Location = new Point(12, 12);
        btnOpen.Click += new EventHandler(btnWriteClick);

        Controls.Add(btnOpen);
    }

    void btnWriteClick(object sender, EventArgs e)
    {
        ofd.ShowDialog();
    }
}

After opening the dialog box, if the user selects a file and clicks OK or presses Enter, the file would be opened.

Practical LearningPractical Learning: Opening a File

  1. On the form, double-click the Open Existing Record check box
  2. Click Form1.cs [Design] tab to access the form
  3. On the form, double-click the Open button
  4. Change the document as follows:
    using System;
    using System.IO;
    using System.Windows.Forms;
    
    namespace GasUtilityMeters3
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
    
                /* If you want to use a drive other than the C:> drive, 
                 * change the letter in the following line. */
                Directory.CreateDirectory(@"C:\Gas Utility Company");
            }
    
            private void btnSave_Click(object sender, EventArgs e)
            {
                if (string.IsNullOrEmpty(txtMeterNumber.Text))
                {
                    MessageBox.Show("You must type a (unique) number for the gas meter.", "Gas Utility Company");
    
                    // If the user doesn't provide a meter number, don't do nothing.
                    return;
                }
    
                if (string.IsNullOrEmpty(txtMake.Text))
                {
                    MessageBox.Show("Please enter the name of the gas meter manufaturer.", "Gas Utility Company");
    
                    // If the user doesn't provide the meter manufaturer, don't do nothing.
                    return;
                }
    
                if (string.IsNullOrEmpty(txtModel.Text))
                {
                    MessageBox.Show("Can you identify the model of the gas meter? Type it.", "Gas Utility Company");
    
                    // If the user doesn't provide the meter model, don't do nothing.
                    return;
                }
    
                if (string.IsNullOrEmpty(txtCounterValue.Text))
                    MessageBox.Show("Type the initial or current reading counter on the gas meter.", "Gas Utility Company");
    
                /* We already have a directory where we will save the files of this application:
                 * Company File Repository: C:\Gas Utility Company */
                /* Each gas meter will have its own file. The name of the file of
                 * a gas meter will be the meter number and the "gms" extension. */
    
                sfDialogBox.FileName = txtMeterNumber.Text + ".gms";
                // sfDialogBox.InitialDirectory = @"C:\Gas Utility Company";
    
                if (sfDialogBox.ShowDialog() == DialogResult.OK)
                {
                    /* Create a file stream that can be used to create a new file, 
                     * that can access a file to write to it, and that can allow 
                     * clients (such as other/networked computers to write to it. */
                    FileStream fsGasMeters = new FileStream(sfDialogBox.FileName,
                                                            FileMode.Create, FileAccess.Write, FileShare.Write);
                    // Create file writer that will be used to write values to the file stream
                    BinaryWriter bwGasMeter = new BinaryWriter(fsGasMeters);
    
                    // Get each value from the form and write it to the file
                    bwGasMeter.Write(txtMeterNumber.Text);
                    bwGasMeter.Write(txtMake.Text);
                    bwGasMeter.Write(txtModel.Text);
                    bwGasMeter.Write(mcMeterReadingDate.SelectionStart.ToShortDateString());
                    bwGasMeter.Write(txtCounterValue.Text);
    
                    // After using the writer, close it
                    bwGasMeter.Close();
                    // After using the file stream, close it
                    fsGasMeters.Close();
    
                    // MessageBox.Show("A record for the gas meter has been created.", "Gas Utility Company");
    
                    // After saving the file, reset the form by setting the initial/empty values to the controls
                    txtMake.Text = "";
                    txtModel.Text = "";
                    txtMeterNumber.Text = "";
                    mcMeterReadingDate.SelectionStart = DateTime.Today;
                    txtCounterValue.Text = "0";
                }
            }
    
            private void chkOpenSave_CheckedChanged(object sender, EventArgs e)
            {
                if (chkOpenSave.Checked == false)
                {
                    btnSave.Visible = true;
                    btnOpen.Visible = false;
                    chkOpenSave.Text = "Open Existing Record";
                    gbxGasMeter.Text = "Create New Gas Meter Record";
    
                    MessageBox.Show("Please provide the values for the gas meter and then click Save.", "Gas Utility Company");
                }
                else // if (chkOpenSave.Checked == true)
                {
                    btnSave.Visible = false;
                    btnOpen.Visible = true;
                    chkOpenSave.Text = "Create New Record";
                    gbxGasMeter.Text = "Open Existing Gas Meter Record";
    
                    MessageBox.Show("Please click the Open button and select the desired file.", "Gas Utility Company");
                }
            }
    
            private void btnOpen_Click(object sender, EventArgs e)
            {
                if (ofDialogBox.ShowDialog() == DialogResult.OK)
                {
                    FileStream fsGasMeters = new FileStream(ofDialogBox.FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
                    BinaryReader brGasMeter = new BinaryReader(fsGasMeters);
    
                    txtMeterNumber.Text = brGasMeter.ReadString();
                    txtMake.Text = brGasMeter.ReadString();
                    txtModel.Text = brGasMeter.ReadString();
                    mcMeterReadingDate.SelectionStart = DateTime.Parse(brGasMeter.ReadString());
                    txtCounterValue.Text = brGasMeter.ReadString();
    
                    brGasMeter.Close();
                    fsGasMeters.Close();
                }
            }
        }
    }
  5. To execute, on the main menu, click Debug -> Start Without Debugging

    Characteristics of an Open File Dialog Box

  6. Enter or select the following values:
    Meter #:            797047-27
    Make:               Archimeda
    Model:              GFH-2260
    Meter Reading Date: 10/04/2021
    Counter Value:      725
  7. Click the Save button:

    Characteristics of an Open File Dialog Box

  8. Accept the file name and press Enter
  9. Click the Open Existing Record check box:

    Menu Bar - Viewport Shading

  10. Click OK on the message box
  11. Click the Open button
  12. Click one of the files:

    Modeling the Wine

  13. Click Open:

    Menu Bar - Viewport Shading

  14. Close the form and return to your programming environment

Opening Various Files

Most of the time, users are interested in opening one file to view or manipulate. It is also possible for a user to want to select various files and open them at the same time. When the user clicks OK after using the Open File dialog box, before taking the next step, you may need to find out whether the user selected various files. To get this information, you can check the value of the OpenFileDialog.Multiselect Boolean property. If the user had selected various files, this property produces a true result. If the user selected only one file, this property produces a false result. The result of this checking process allows you either to agree to open the files or to take some action of your choice.

Opening a File As Read-Only

After a file has been opened, the user may want to alter it. To let you control this aspect, the OpenFileDialog class is equipped with a property named ShowReadOnly. If you set it to true, the dialog box would be equipped with an Open As Read-Only check box in its lower section. Here is an example:

void InitializeComponent()
{
        ofd = new  OpenFileDialog ();
        ofd.Filter = "HTML Files (*.htm)|*.htm|" +
                                      "Active Server Pages (*.asp)|*.asp|" +
                                      "Apache Files (*.php)|*.php|" +
                                      "Perl Script (*.pl)|*.pl|" +
                                      "All Files|";
        ofd.ShowReadOnly = true;
        ofd.ReadOnlyChecked = true;
}

Open

On the other hand, when the user selects a file to open, you can check the value of the ReadOnlyChecked property. If it is true, this indicates that the user had clicked the Open As Read-Only check box.

Practical LearningPractical Learning: Ending the Lesson


Previous Copyright © 2008-2020, FunctionX Next