BLOG.CSHARPHELPER.COM

Display thumbnails for image files in a directory in C#

This example displays thumbnails for the images in a directory. It displays a tooltip when the mouse hovers over a picture and selects a picture if you click on it.

This program is actually much easier than you might think because controls do all of the work of arranging the pictures. The program contains a (green) FlowLayoutPanel with its AutoScroll property set to true. The program only needs to drop PictureBox controls onto it and it automatically arranges the PictureBoxes and displays scroll bars if necessary.

The following code shows how the program displays thumbnails when the directory in the TextBox changes.

// PictureBoxes we use to display thumbnails.
private List PictureBoxes = new List();

// Thumbnail sizes.
private const int ThumbWidth = 100;
private const int ThumbHeight = 100;

// The selected PictureBox.
private PictureBox SelectedPictureBox = null;

// Display thumbnails for the selected directory.
private void txtDirectory_TextChanged(object sender, EventArgs e)
{
    // Delete the old PictureBoxes.
    foreach (PictureBox pic in PictureBoxes)
    {
        pic.Click -= PictureBox_Click;
        pic.Dispose();
    }
    flpThumbnails.Controls.Clear();
    PictureBoxes = new List();
    SelectedPictureBox = null;

    // If the directory doesn't exist, do nothing else.
    if (!Directory.Exists(txtDirectory.Text)) return;

    // Get the names of the files in the directory.
    List filenames = new List();
    string[] patterns = { "*.png", "*.gif", "*.jpg", "*.bmp", "*.tif" };
    foreach (string pattern in patterns)
    {
        filenames.AddRange(Directory.GetFiles(txtDirectory.Text,
            pattern, SearchOption.TopDirectoryOnly));
    }
    filenames.Sort();

    // Load the files.
    foreach (string filename in filenames)
    {
        // Load the picture into a PictureBox.
        PictureBox pic = new PictureBox();

        pic.ClientSize = new Size(ThumbWidth, ThumbHeight);
        pic.Image = new Bitmap(filename);

        // If the image is too big, zoom.
        if ((pic.Image.Width > ThumbWidth) ||
            (pic.Image.Height > ThumbHeight))
        {
            pic.SizeMode = PictureBoxSizeMode.Zoom;
        }
        else
        {
            pic.SizeMode = PictureBoxSizeMode.CenterImage;
        }

        // Add the Click event handler.
        pic.Click += PictureBox_Click;

        // Add a tooltip.
        FileInfo file_info = new FileInfo(filename);
        tipPicture.SetToolTip(pic, file_info.Name + 
            "\nCreated: " + file_info.CreationTime.ToShortDateString() +
            "\n(" + pic.Image.Width + " x " + pic.Image.Height + ") " +
            ToFileSizeApi(file_info.Length));
        pic.Tag = file_info;
        
        // Add the PictureBox to the FlowLayoutPanel.
        pic.Parent = flpThumbnails;
    }
}

This code defines the List of PictureBoxes that it uses to store the images that it creates. It also defines constants ThumbWidth and ThumbHeight, which determine how big the thumbnails are. Finally it declares the SelectedPictureBox variable to track the currently selected PictureBox.

The txtDirectory_TextChanged does most of the work. First it removes the Click event handlers from any existing PictureBoxes and disposes of them. It removes all of the child controls from the flpThumbnails FlowLayoutPanel, resets the PictureBoxes List, and sets SelectedPictureBox to null.

Next the code uses Directory.Exists to see if the directory entered in the TextBox exists and returns if it doesn't. This happens, for example, if you are typing in the TextBox and haven't finished entering the directory's name.

The code then loops over an array of file patterns looking for png, gif, jpg, and other image file formats. For each format, the code uses the Directory.GetFiles method to find files matching the pattern. That method returns an array of file names. The code uses the filenames array's AddRange method to add all of the files it found to the filenames array. After finding the files that match all of the patterns, the program sorts the filenames array.

Now the program loops through the file names. For each file, it creates a PictureBox, sets its size to the right thumbnail size, and loads the file into the PictureBox's Image property.

Next if the image is larger than the PictureBox, the program sets the PictureBox's SizeMode to Zoom so it displays it as large as possible without distorting it. If the image is smaller than the PictureBox, the program sets SizeMode to CenterImage so it displays the image at full scale centered in the control.

The code sets the PictureBox_Click event handler (shown shortly) to catch the control's Click event and adds a tooltip describing the image. Finally the code sets the PictureBox's Parent property to the FlowLayoutPanel (so the PictureBox is displayed inside the FlowLayoutPanel).

The following code shows the PictureBox_Click event handler that executes when you click on a PictureBox.

// Select the clicked PictureBox.
private void PictureBox_Click(object sender, EventArgs e)
{
    PictureBox pic = sender as PictureBox;
    if (SelectedPictureBox == pic) return;

    // Deselect the previous PictureBox.
    if (SelectedPictureBox != null) SelectedPictureBox.BorderStyle = BorderStyle.None;

    // Select the clicked PictureBox.
    SelectedPictureBox = pic;
    SelectedPictureBox.BorderStyle = BorderStyle.Fixed3D;
}

The code converts the sender from a generic object into a PictureBox. If this is the same as the currently selected PictureBox, the event handler exits.

If there is a currently selected PictureBox, the code resets its BorderStyle to None. The code then sets SelectedPictureBox to the clicked control and sets its border to Fixed3D so you can tell it is selected. A real application might do something else with the image such as print it or display it at full scale in another window, or it might provide a separate button to let you print the currently selected image.

(Note that if you need to store more information about each image, such as the full file name, you could put it in its PictureBox's Tag property.)

Notice how the program makes controls do most of the graphical work. The FlowLayoutPanel automatically arranges the pictures and displays scroll bars if necessary so you don't have to. The PictureBoxes' ScaleMode properties size the images appropriately without distorting them. All the code really does is list the files and create the PictureBoxes.

   

Get information about a Windows shortcut in C#

The GetShortcutInfo method shown in the following code gets information about a shortcut.
// Get information about this link.
// Return an error message if there's a problem.
private string GetShortcutInfo(string full_name, out string name, out string path, out string descr, out string working_dir, out string args)
{
    name = "";
    path = "";
    descr = "";
    working_dir = "";
    args = "";
    try
    {
        // Make a Shell object.
        Shell32.Shell shell = new Shell32.Shell();

        // Get the shortcut's folder and name.
        string shortcut_path = full_name.Substring(0, full_name.LastIndexOf("\\"));
        string shortcut_name = full_name.Substring(full_name.LastIndexOf("\\") + 1);
        if (!shortcut_name.EndsWith(".lnk")) shortcut_name += ".lnk";

        // Get the shortcut's folder.
        Shell32.Folder shortcut_folder = shell.NameSpace(shortcut_path);

        // Get the shortcut's file.
        Shell32.FolderItem folder_item = shortcut_folder.Items().Item(shortcut_name);

        if (folder_item == null)
            return "Cannot find shortcut file '" + full_name + "'";                
        if (!folder_item.IsLink)
            return "File '" + full_name + "' isn't a shortcut.";

        // Display the shortcut's information.
        Shell32.ShellLinkObject lnk = (Shell32.ShellLinkObject)folder_item.GetLink;
        name = folder_item.Name;
        descr = lnk.Description;
        path = lnk.Path;
        working_dir = lnk.WorkingDirectory;
        args = lnk.Arguments;
        return "";
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
}

The program first creates a Shell32.Shell object to work with the Windows shell. It uses that object's NameSpace method to get a Folder object representing the folder that contains the shortcut of interest. It uses that object's Items collection to get the shortcut's name and a ShellLinkObject representing the shortcut. From that object, the method finally gets the rest of the information it needs.

If you look closely at the picture, you'll see that in that example the shortcut's ...
  • ... name was "Signature"
  • ... description was "Creates and edits text documents with complex formatting"
  • ... path was the path to the WordPad executable
  • ... working directory was my desktop
  • ... arguments was "sig.txt"
So when I double-click this shortcut, it opens Word in my desktop's directory and opens the file sig.txt located there.

   

List the shortcuts in the computer's network neighborhood in C#

This example uses the Windows Script Host to list shortcuts in the network neighborhood. Before the program can use it, you must add a reference to the COM object "Windows Script Host Object Model." To make using that library easier, the program includes the following two using statements.

using System.IO;
using IWshRuntimeLibrary;

The following code shows how the program builds its list of shortcuts.

private void Form1_Load(object sender, EventArgs e)
{
    // Make a Windows Script Host Shell object.
    IWshShell_Class wsh_shell = new IWshShell_Class();

    // Find the Nethood folder.
    IWshCollection special_folders = wsh_shell.SpecialFolders;
    object path_name = "Nethood";
    string nethood_path = special_folders.Item(ref path_name).ToString();
    DirectoryInfo di = new DirectoryInfo(nethood_path);

    // Enumerate Nethood's subdirectories.
    foreach (DirectoryInfo subdir in di.GetDirectories())
    {
        lstLinks.Items.Add(subdir.Name);
    }
}

The code creates an IWshShell_Class object and gets the Nethood entry from its SpecialFolders collection. It converts that value into a string and makes a DirectoryInfo object representing that location.

The code then loops through the directory's subdirectories, listing them in the lstLinks ListBox.

   

Graph an equation entered by the user in C#

The example Graph an equation y = F(x) in C# shows how to graph a function. The example Evaluate numeric expressions that are entered by the user in C# shows how to compile user-entered equations. This example combines those two techniques to graph a function entered by the user.

The program takes the user-entered equation and uses it to build a C# class that looks like this:

public static class Evaluator
{
    public static double Evaluate(double x)
    {
        return  (1 / x + 1 / (x + 1) - 2 * x * x) / 10;
    }
};

It then compiles this code and gets a MethodInfo object representing the Evaluator class's Evaluate method (as described in the example Evaluate numeric expressions that are entered by the user in C#).

Next the program loops through x values to draw the graph (as done in the example Graph an equation y = F(x) in C# ). It uses the MethodInfo object's Invoke method to call the Evaluate function for the different x values and uses the results to draw the graph.

The only really new thing to notice here is that you only need to compile the function once and then you can invoke it many times. That's important because compiling the function takes a bit of time so recompiling every time you needed to evaluate the function would be relatively time consuming.

See the previous examples and download this example's code to see the details.

   

Graph an equation y = F(x) in C#

This program's MakeGraph method does all of the interesting work.

// Make the graph.
private void MakeGraph()
{
    // The bounds to draw.
    float xmin = -3;
    float xmax = 3;
    float ymin = -3;
    float ymax = 3;

    // Make the Bitmap.
    int wid = picGraph.ClientSize.Width;
    int hgt = picGraph.ClientSize.Height;
    Bitmap bm = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;

        // Transform to map the graph bounds to the Bitmap.
        RectangleF rect = new RectangleF(xmin, ymin, xmax - xmin, ymax - ymin);
        PointF[] pts = 
        {
            new PointF(0, hgt),
            new PointF(wid, hgt),
            new PointF(0, 0),
        };
        gr.Transform = new Matrix(rect, pts);

        // Draw the graph.
        using (Pen graph_pen = new Pen(Color.Blue, 0))
        {
            // Draw the axes.
            gr.DrawLine(graph_pen, xmin, 0, xmax, 0);
            gr.DrawLine(graph_pen, 0, ymin, 0, ymax);
            for (int x = (int)xmin; x <= xmax; x++)
            {
                gr.DrawLine(graph_pen, x, -0.1f, x, 0.1f);
            }
            for (int y = (int)ymin; y <= ymax; y++)
            {
                gr.DrawLine(graph_pen, -0.1f, y, 0.1f, y);
            }
            graph_pen.Color = Color.Red;

            // See how big 1 pixel is horizontally.
            Matrix inverse = gr.Transform;
            inverse.Invert();
            PointF[] pixel_pts =
            {
                new PointF(0, 0),
                new PointF(1, 0)
            };
            inverse.TransformPoints(pixel_pts);
            float dx = pixel_pts[1].X - pixel_pts[0].X;
            dx /= 2;

            // Loop over x values to generate points.
            List points = new List();
            for (float x = xmin; x <= xmax; x += dx)
            {
                bool valid_point = false;
                try
                {
                    // Get the next point.
                    float y = F(x);

                    // If the slope is reasonable, this is a valid point.
                    if (points.Count == 0) valid_point = true;
                    else
                    {
                        float dy = y - points[points.Count - 1].Y;
                        if (Math.Abs(dy / dx) < 1000) valid_point = true;
                    }
                    if (valid_point) points.Add(new PointF(x, y));
                }
                catch
                {
                }

                // If the new point is invalid, draw
                // the points in the latest batch.
                if (!valid_point)
                {
                    if (points.Count > 1) gr.DrawLines(graph_pen, points.ToArray());
                    points.Clear();
                }

            }

            // Draw the last batch of points.
            if (points.Count > 1) gr.DrawLines(graph_pen, points.ToArray());
        }
    }

    // Display the result.
    picGraph.Image = bm;
}

This code start by defining the coordinate bounds -3 ≤ x ≤ 3, -3 ≤ y ≤ 3 that it will draw. The code then makes a Bitmap to fit the program's picGraph PictureBox and makes a Graphics object to draw on the Bitmap.

Next the code defines a transformation to map the coordinate bounds to the bitmap. This example doesn't worry about whether that distorts the graph. Alternatively you might want to scale the graph to make the coordinate area as large as possible without distortion.

The code sets the Graphics object's Transform property to the transformation to perform the mapping. Now the code can draw in normal coordinate space and the transformation automatically converts the drawing into the transformed coordinate system.

Next the program creates a pen with thickness 0. This is important because the transformation will affect pens that have any other thickness. For example, if the transformation scales to make the result slightly tall and thin, then it will scale pens to make them thicker in the vertical direction than in the horizontal direction. However, pens with 0 thickness are always drawn 1 pixel wide and are not transformed.

The code draws the X and Y axes using the thin pen and then changes the pen's color before drawing the graph.

To make the graph smooth, the program plots values where X increases by 1/2 pixel at each step. You could plot a value for every pixel horizontally but every half pixel produces a better result when the graph is very vertical. (More analytically, you could take the function's derivative and plot more points when the derivative is high and fewer when it is low. To think of it another way, you could plot similar numbers of points per unit of curve length. However, plotting one point per half pixel seems to produce a fairly good result.)

To plot points for every 1/2 pixel in the X direction, the curve makes an array holding 2 points 1 pixel apart. It uses the inverse of the Graphics object's transformation to see, if the points represent points on the Bitmap, where they map from in coordinate space. The distance between them tells how far apart points in coordinate space must be to end up 1 pixel apart on the Bitmap. The program simply divides by 2 to get the X coordinate distance for half a pixel.

Finally the program is ready to plot some points. It loops through the points calling the function F to get Y values. If the slope between this point and the previous one is very steep, the program assumes that this is a discontinuity. For example, if F(x) = 1/x, then there is a discontinuity at x = 0 because the point slightly to the left is a very large negative value, the point slightly to the righ is a very large positive value, and the function is undefined at x = 0.

If the program does not finds a discontinuity, it adds the new point to the points list. If the program does find a discontinuity, it draws the previously saved points and starts a new list.

After it has covered every X value in the desired range, the program draws any points that it has not yet drawn.

The following code shows the function F that this example uses, but you should be able to plug in other equations without too much work.

// The function to graph.
private float F(float x)
{
    return (float)((1 / x + 1 / (x + 1) - 2 * x * x) / 10);
}

If you look at the equation, you can see that it should have discontinuities at x = -1 and x = 0.

   

Find a file in the program's startup directory in C#

In some programs you may want to find a file that was installed with the program. One way to do that is to put the file in the program's installation directory. Open the Project menu, select Add Existing Item, find the file, and click Add. Now select the file in Project Explorer and use the Properties window to set its "Copy to Output Directory" property to "Copy if newer." Now when you build the project, the file is copied into the output directory. When you install the project, be sure you include the file, too.

This example uses the following code to find the file Greeting.rtf in the startup directory when it starts.

private void Form1_Load(object sender, EventArgs e)
{
    string filename = Path.Combine(
        Application.StartupPath, "Greeting.rtf");
    rchGreeting.LoadFile(filename);
}

The program uses the System.IO.Path.Combine method to add the file name Greeting.rtf to the startup path. The Combine method adds a \ between the two pieces of the path if necessary so you don't need to worry about whether the startup path includes a trailing \. After composing the file's full name, the program simply loads it into its RichTextBox control.

   

Evaluate numeric expressions that are entered by the user in C#

Enter a numeric expression involving x and y in the upper text box. Then enter values for x and y in the lower text boxes and press the Evaluate button. The program evaluates the expression you entered using the values for x and y that you entered and displays the result.

The program executes the following code when you click the Evaluate button.
// Evaluate the expression.
private void btnEvaluate_Click(object sender, EventArgs e)
{
    // Turn the equation into a function.
    string function_text =
        "public static class Evaluator" +
        "{" +
        "    public static double Evaluate(double x, double y)" +
        "    {" +
        "        return " + txtExpression.Text + ";" +
        "    }" +
        "}";

    // Compile the function.
    CodeDomProvider code_provider = CodeDomProvider.CreateProvider("C#");

    // Generate a non-executable assembly in memory.
    CompilerParameters parameters = new CompilerParameters();
    parameters.GenerateInMemory = true;
    parameters.GenerateExecutable = false;

    // Compile the code.
    CompilerResults results =
        code_provider.CompileAssemblyFromSource(parameters, function_text);

    // If there are errors, display them.
    if (results.Errors.Count > 0)
    {
        string msg = "Error compiling the expression.";
        foreach (CompilerError compiler_error in results.Errors)
        {
            msg += "\n" + compiler_error.ErrorText;
        }
        MessageBox.Show(msg, "Expression Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    else
    {
        // Get the Evaluator class type.
        Type evaluator_type = results.CompiledAssembly.GetType("Evaluator");

        // Get a MethodInfo object describing the Evaluate method.
        MethodInfo method_info = evaluator_type.GetMethod("Evaluate");

        // Make the parameter list.
        object[] method_params = new object[]
        {
            double.Parse(txtX.Text),
            double.Parse(txtY.Text)
        };

        // Execute the method.
        double expression_result = 
            (double)method_info.Invoke(null, method_params);

        // Display the returned result.
        txtResult.Text = expression_result.ToString();
    }
}

The code first builds a function that contains the expression entered by the user. The resulting function looks something like this:

public static class Evaluator
{
    public static double Evaluate(double x, double y)
    {
        return 2 * x + 4 * y + x * y;
    }
}

The actual string has the class and its function all on one line of text. I've added line breaks here to make it more readable but the compiler doesn't care.

Next the code creates a CodeDomProvider to compile C# code. It sets parameters to tell the provider to create a non-executable assembly built in memory, and then it calls the CompileAssemblyFromSource method to compile the Evaluator class with its Evaluate function.

If there are errors, the program displays them. Try adding some syntax errors to the equation. If the error isn't too extreme, the error messages are often good enough to let you fix the problem.

The code then gets a Type representing the Evaluator class and from that gets a MethodInfo representing the class's Evaluate method. The program makes a parameter array that holds the values of x and y to pass into the method and invokes the method, passing it those parameters. The program converts the method's return result from a generic object into a double and displays it.

This method is not blindingly fast because it takes a little while to compile the Evaluator class. Once the class is compiled, however, calling it is fairly quick. That means you should be able to evaluate the same function for many different values of x and y relatively quickly by compiling the class once and then invoking it many times.

   

List the locations of special folders in C#

The System.Environment class's SpecialFolders enumeration lists special folders such as System, Cookies, Desktop, and so forth. The class's GetFolderPath method returns the full path for one of the special folder values.

This program uses the following code to enumerate the System.Environment.SpecialFolders values and call the DescribeFolder method for each.

// List the folder types.
private void Form1_Load(object sender, EventArgs e)
{
    foreach (System.Environment.SpecialFolder folder_type
        in Enum.GetValues(typeof(System.Environment.SpecialFolder)))
    {
        DescribeFolder(folder_type);
    }
    txtFolders.Select(0, 0);
}

The following code shows the DescribeFolder method.

// Add a folder's information to the txtFolders TextBox.
private void DescribeFolder(System.Environment.SpecialFolder folder_type)
{
    txtFolders.AppendText(
        String.Format("{0,-25}", folder_type.ToString()) +
        Environment.GetFolderPath(folder_type) + "\r\n");
}

The DescribeFolder method adds text to the txtFolders TextBox. It uses String.Format to display the folder type converted to a string and padded on the right to 25 spaces. It uses the Environment.GetFolderPath method to add the folder's path to the text and finishes the folder's entry with a new line.

   

List the languages that you can compile with code in C#

The example Compile code entered at run time, execute it, and get the return result in C# shows how to compile code at run time, but how do you know what languages you can compile that way? You might guess C# and Visual Basic but you may be able to compile other languages, too.

This example uses the following code to list the languages that you can compile by using the CodeDomProvider class.

// List compiler supported languages.
private void Form1_Load(object sender, EventArgs e)
{
    string txt = "";

    // Loop through information about all compilers.
    CompilerInfo[] compiler_infos =
 CodeDomProvider.GetAllCompilerInfo(); foreach (CompilerInfo info in compiler_infos) { if (info.IsCodeDomProviderTypeValid) { // Get information about this compiler. CodeDomProvider provider = info.CreateProvider(); txt += "Provider: " + provider.ToString() + "\r\n"; // List supported extentions. string extensions = ""; string default_extension = provider.FileExtension; if (default_extension[0] != '.') default_extension = '.' + default_extension; foreach (string extension in info.GetExtensions()) { extensions += ", " + extension; if (extension == default_extension) extensions += " (default)"; } if (extensions.Length > 0) extensions = extensions.Substring(2); txt += " Extensions: " + extensions + "\r\n"; // List supported languages. string languages = ""; string default_language = CodeDomProvider.GetLanguageFromExtension(default_extension); foreach (string language in info.GetLanguages()) { languages += ", " + language; if (language == default_language) languages += " (default)"; } if (languages.Length > 0) languages = languages.Substring(2); txt += " Languages: " + languages + "\r\n"; // Get the compiler settings for this provider. CompilerParameters parameters = info.CreateDefaultCompilerParameters(); txt += " Options: " + parameters.CompilerOptions + "\r\n"; txt += " Warning Level: " + parameters.WarningLevel + "\r\n"; } } // Display the results. txtInfo.Text = txt; txtInfo.Select(0, 0); }

The program uses the CodeDomProvider class's GetAllCompilerInfo method to get information about available compilers and loops thbrough them. For each CompilerInfo object, the program checks the object's IsCodeDomProviderValid property to make sure the compiler is actually valid on your computer. If the compiler is valid, the code displays information about that compiler.

First the code creates a provider for the compiler and displays its name. It then loops through the provider's supported extensions listing them. It adds the text "(default)" after the provider's default extension.

Next the code performs similar steps to list the compiler's supported languages. These give strings such as C# and CSharp that you can use for the language in the previous example.

Finally the code lists the compiler's default parameters and warning level.

If you look closely at the picture of this example, you can see that my system support C#, Visual Basic, JavaScript, and Visual C++.

   

Compile code entered at run time, execute it, and get the return result in C#

This example uses the following using statements.

using System.CodeDom.Compiler;
using System.Reflection;

The following code executes when you click the Run button.

// Compile and execute the code.
private void btnRun_Click(object sender, EventArgs e)
{
    txtResults.Clear();
    CodeDomProvider code_provider = CodeDomProvider.CreateProvider("C#");

    // Generate a non-executable assembly in memory.
    CompilerParameters parameters = new CompilerParameters();
    parameters.GenerateInMemory = true;
    parameters.GenerateExecutable = false;

    // Add references used by the code. (This one is used by MessageBox.)
    parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");

    // Compile the code.
    CompilerResults results =
        code_provider.CompileAssemblyFromSource(parameters, txtCode.Text);

    // If there are errors, display them.
    if (results.Errors.Count > 0)
    {
        foreach (CompilerError compiler_error in results.Errors)
        {
            txtResults.AppendText(
                "Line: " + compiler_error.Line + ", " +
                "Error Number: " + compiler_error.ErrorNumber + ", " +
                compiler_error.ErrorText + "\n");
        }
    }
    else 
    {
        // There were no errors.
        txtResults.Text = "Success!";

        // Get the compiled method and execute it.
        foreach (Type a_type in results.CompiledAssembly.GetTypes())
        {
            if (!a_type.IsClass) continue;
            if (a_type.IsNotPublic) continue;

            // Get a MethodInfo object describing the SayHi method.
            MethodInfo method_info = a_type.GetMethod("SayHi");
            if (method_info != null) 
            {
                // Make the parameter list.
                object[] method_params = new object[] {"This is the parameter string. Isn't it great?"};

                // Execute the method.
                DialogResult method_result =
                    (DialogResult)method_info.Invoke(null, method_params);

                // Display the returned result.
                MessageBox.Show(method_result.ToString());
            }
        }
    }
}

The code first clears any old results. It then creates a CodeDomProvider to work with C# code. (My next post will show how to figure out what other languages you can use.)

Next the code creates a parameters object. It sets the GenerateInMemory property to true to indicate that the compiler should compile into memory (as opposed to creating a compiled file). It sets the GenerateExecutable property to false to indicate that it should create am assembly that this program can invoke (as opposed to a standalone executable).

The program then adds a reference to the System.Windows.Forms library that the code needs to use MessageBox.Show. If the code entered at runtime uses other libraries, you should include them here.

Now the program compiles the code. It calls the code provider's CompileAssemblyFromSource method passing it the parameters selected by the code and the code entered at run time. When the code returns, in this example after the user closes the MessageBox, the program saves the results in a CompilerResults object.

If the results object's Errors collection is not empty, the program displays each error's line number, error code, and error text. (You should try introducing an error into the code to see what happens. In a real application, you might list the errors in a ListBox and jump to the appropriate line when the clicks on one.

If there were no errors, the code has been compiled successfully so the program displays a success message. It then uses reflection to examine the types provided by the compiled code. It looks for a class that is public and tries to create a MethodInfo object describing that class's SayHi method.

If it succeeds in creating the MethodInfo object, then the SayHi method exists for this class and the program invokes it. The code creates an array to hold parameters for the method, in this case a single string. It then calls the MethodInfo object's Invoke method, passing it the array of parameters. The null used as the first parameter would represent the object for which the method should be invoked, but the SayHi method defined in this example is a static method so it doesn't need an object. This example's SayHi method returns a DialogResult (to show which button the user clicks on the MessageBox) so the program casts the return result into this data type and displays it.

The following code shows the text that the program initially uses for its code entered at run time.

using System.Windows.Forms;

public static class MyScriptClass
{
    public static DialogResult SayHi(string msg)
    {
        return MessageBox.Show(msg, "Message",
            MessageBoxButtons.YesNoCancel);
    }
}

This code defines a static class named MyScriptClass. That class defines the single static method SayHi. The SayHi method displays whatever parameter it is passed and returns the DialogResult returned by the call to MessageBox.Show.
Note: You should be wary of the source for code executed at run time. For example, if the user enters this code (as you can in this example), then the program could potentially perform any actions that the user could. For example, the code might be able to download and install a virus, erase a hard drive, or perform other destructive actions. It might make sense to let the program execute scripts that you have written or that a power user writes but you probably should execute any old code that you find floating around on the Internet without carefully examining it first.

   

Calendar

January 2012
SuMoTuWeThFrSa
1234567
891011121314
15161718192021
22232425262728
293031

Subscribe


Blog Software
Blog Software