Convert RTF and TXT files into DOCX files in C#

[convert RTF]


First add a reference to Microsoft.Office.Interop.Word. To make using the library easier, add this using statement:

using Word = Microsoft.Office.Interop.Word;

When you enter the input and output file names and click Convert, the program uses the following code to open the file and save it in the new format.

// Get the Word application object.
Word._Application word_app = new Word.ApplicationClass();

// Make Word visible (optional).
//word_app.Visible = true;

// Open the file.
object input_file = txtInputFile.Text;
object missing = Type.Missing;
word_app.Documents.Open(ref input_file, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing,
    ref missing,
    ref missing, ref missing, ref missing, ref missing);

// Save the output file.
object output_file = txtOutputFile.Text;
object format_doc = (int)16;    // 16 for docx, 0 for doc.
Word._Document active_document = word_app.ActiveDocument;
active_document.SaveAs(ref output_file, ref format_doc,
    ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing, 
    ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing);

// Exit the server without prompting.
object false_obj = false;
active_document.Close(ref false_obj, ref missing, ref missing);
word_app.Quit(ref missing, ref missing, ref missing);

MessageBox.Show("Done");

The code first creates a Word application object. It uses that object’s Documents.Open method to open the input file. It then creates a Word._Document object to represent the active document. If you don’t do that, there’s an ambiguity about how the SaveAs method should be called so you get an error.

The code calls the document’s SaveAs method passing it a reference to the variable format_doc to tell it to save in Word .docx format. Notice how all parameters are objects passed by reference because that’s what Word needs. Notice also how the code uses the special value missing to indicate a missing parameter and make the Word method use a default value.

The code next calls the document’s Close method, passing it the value false (again as a reference to an object) so it doesn’t save changes. It finishes by calling the Word server’s Quit method to close Word the server.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in files, interoperability, Office, Word | Tagged , , , , , , , , , , , , , | Leave a comment

Draw a scrolling family tree in C#


[family tree]

The example Draw a family tree in C# shows how to draw a family tree. Unfortunately if the tree is too big, you can’t make the form big enough to display it all.

One solution would be to use smaller images so everything will fit. This example shows a different approach. It moves the tree into a Panel control to make a scrolling family tree.

The previous solution draws the family tree centered on a PictureBox. The PictureBox is docked to the form so when you make the form bigger the PictureBox gets bigger, too. The problem occurs when you can’t make the form big enough to show the whole tree.

This example makes the following changes to create a scrolling family tree.

  • The form now contains a Panel control with:
    • AutoScroll = true. When the control’s contents (the PictureBox won’t fit, the Panel automatically displays scroll bars.
    • Dock = Fill. The Panel is docked to fill the form so when the form resizes the Panel does, too.
  • The PictureBox is now contained inside the Panel control. It has:
    • Dock = None. It does not resize when its container (the Panel) resizes.
    • ScaleMode = AutoSize. It resizes to fit its contents, which will be a Bitmap.
    • Location = (0, 0). That places the PictureBox in the Panel control’s upper left corner.

The previous version of the program drew the tree in the PictureBox control’s Paint event handler. This example draws it when the form loads. The following code shows the DrawTree method that draws the tree.

// Draw the tree on a Bitmap sized to fit.
private Bitmap DrawTree(TreeNode<PictureNode> root, int margin)
{
    float xmin = margin, ymin = margin;

    // Make a small bitmap so we can use its graphics handle.
    using (Bitmap bm = new Bitmap(10, 10))
    {
        using (Graphics gr = Graphics.FromImage(bm))
        {
            // Arrange the tree to see how big it is.
            root.Arrange(gr, ref xmin, ref ymin);
        }
    }

    // Make the result bitmap.
    int wid = (int)xmin + margin;
    int hgt = (int)ymin + margin;
    Bitmap result_bm = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(result_bm))
    {
        // Draw the tree.
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        gr.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
        root.DrawTree(gr);
    }

    return result_bm;
}

The code first creates a small Bitmap so it can use it to make a Graphics object. It then calls the root node’s Arrange method to make the tree arrange itself. After the call to Arrange, the xmin and ymin variables hold the tree’s right- and bottom-most coordinates. (They represent the minimum X and Y values that a new part of the tree could have if you were going to add to the tree.)

Now that the method knows how big the tree needs to be, it creates a Bitmap sized to fit. It creates a Graphics object for the Bitmap and calls the root node’s DrawTree to draw the tree on the Bitmap. The tree’s nodes draw themselves in the positions calculated by the earlier call to Arrange.

The following code shows how the main program uses the DrawTree method.

// Make the Panel display scroll bars if needed.
panScroller.AutoScroll = true;

// Draw the tree into a Bitmap and
// display the result in the PictureBox.
picTree.Location = new Point(0, 0);
picTree.SizeMode = PictureBoxSizeMode.AutoSize;
picTree.Image = DrawTree(root, 5);

The code sets the Panel control’s AutoScroll property so it displays scroll bars as needed. It then positions the PictureBox in the Panel control’s upper left corner and sets its SizeMode property to it resizes to fit its image. Finally it sets the PictureBox control’s Image equal to the Bitmap returned by the DrawTree method.

After that everything is automatic. The Panel displays scroll bars if needed and the PictureBox redraws its Image if necessary. There’s no need for a Paint event handler.

Both versions of the program have a MouseClick event handler that displays the name of people in the tree when you click on them. The new version is exactly the same as the old one. Even if you scroll the tree, the MouseClick event handler knows what point on the PicturyeBox you clicked so you don’t need to change the code to account for the scrolling.

This is a common technique for displaying large images. Use a PictureBox with ScaleMode = AutoSize and make it display a Bitmap. Place that inside a Panel with AutoScroll = true.

Download the example to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, classes, generic, graphics, OOP | Tagged , , , , , , , , , , , , , , , , , , , , , | 1 Comment

Remove non-printable ASCII characters from a string in C#

[non-printable ASCII characters]

The following TrimNonAscii extension method removes the non-printable ASCII characters from a string.

public static string TrimNonAscii(this string value)
{
    string pattern = "[^ -~]+";
    Regex reg_exp = new Regex(pattern);
    return reg_exp.Replace(value, "");
}

In ASCII, the printable characters lie between space (” “) and “~”. The code makes a regular expression that represents all characters that are outside of that range repeated one or more times. It uses the expression to create a Regex object and then uses its Replace method to remove those characters. The method then returns the resulting string.

Note that this method removes many useful Unicode characters such as £, Æ, and ♥, in addition to fonts such as Cyrillic and Kanji. It’s mostly useful for standard English text.

I don’t know of a simple way to remove Unicode characters in bulk. You would probably need to make a table of characters that you do or do not want to include and then either loop through the string looking for them or use a Regex object to remove the ones you don’t want.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in extension methods, strings | Tagged , , , , , , , , , , , , | 7 Comments

See what processes have a file locked in C#

[file locked]

In older versions of Windows, it was impossible to determine what processes had a file locked. The system kept track of the number of locks on a file and processes were responsible for incrementing and decrementing the count to represent their own locks.

Recently (I think starting in Windows XP) Windows included a new Restart Manager to aid in restarting the system during installs. To make restarts easier, Windows now keeps track of the processes that have a file locked, and you can use the Restart Manager to read that list of processes.

The .NET Framework doesn’t have any tools for working with the Restart Manager, so you need to use its API and that requires the usual ugly declarations. Download the example to see all of them in their full glory.

The following code shows the FindLockers method that this example uses to get a list of the processes that have a file locked.

// Return a list of processes that have locks on a file.
static public List<Process> FindLockers(string filename)
{
    // Start a new Restart Manager session.
    uint session_handle;
    string session_key = Guid.NewGuid().ToString();
    int result = RmStartSession(out session_handle, 0, session_key);
    if (result != 0)
        throw new Exception("Error " +
            result + " starting a Restart Manager session.");

    List<Process> processes = new List<Process>();
    try
    {
        const int ERROR_MORE_DATA = 234;
        uint pnProcInfoNeeded = 0, num_procs = 0,
            lpdwRebootReasons = RmRebootReasonNone;
        string[] resources = new string[] { filename };
        result = RmRegisterResources(
            session_handle, (uint)resources.Length,
            resources, 0, null, 0, null);
        if (result != 0)
            throw new Exception("Could not register resource.");

        // There's a potential race around condition here.
        // The first call to RmGetList() returns the total
        // number of process. However, when we call RmGetList()
        // again to get the actual processes this number may
        // have increased.
        result = RmGetList(session_handle, out pnProcInfoNeeded,
            ref num_procs, null, ref lpdwRebootReasons);
        if (result == ERROR_MORE_DATA)
        {
            // Create an array to store the process results.
            RM_PROCESS_INFO[] processInfo =
                new RM_PROCESS_INFO[pnProcInfoNeeded];
            num_procs = pnProcInfoNeeded;

            // Get the list.
            result = RmGetList(session_handle,
                out pnProcInfoNeeded, ref num_procs,
                processInfo, ref lpdwRebootReasons);
            if (result != 0)
                throw new Exception("Error " + result +
                    " listing lock processes");

            // Add the results to the list.
            for (int i = 0; i < num_procs; i++)
            {
                try
                {
                    processes.Add(
                        Process.GetProcessById(processInfo[i].
                            Process.dwProcessId));
                }
                // In case the process is no longer running.
                catch (ArgumentException) { }
            }
        }
        else if (result != 0)
            throw new Exception("Error " + result +
                " getting the size of the result.");
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally
    {
        RmEndSession(session_handle);
    }

    return processes;
}

The method first creates a new Restart Manager session. It then makes an empty list to hold Process objects and enters a try-catch block.

The method then calls the RmRegisterResources method to register the file’s name with the Restart Manager. Next it calls RmGetList method to see how many processes have locks on the file. It calls RmGetList again to get information on the processes. Then code then loops through the results and uses the returned process IDs to get Process objects representing the objects that have the file locked.

The method finishes by returning the list of Process objects.

Download the example to see more details. This example was based on the MSDN post, How to know the process locking a file. See that post for additional information.

To test the program, start it and then open the executable file in Microsoft Word. (It will look like gibberish in Word.) Then press the program’s Find Locks button. You should see processes named howto_find_file_locker.vshost (Visual Studio running the program) and WINWORD.

If you then double-click the executable file, you can run the program again. It and the previously instance of the program should list the two earlier processes plus howto_find_file_locker representing the new instance of the program.

I don’t know of a way to figure out what kind of locks a process might have on a file. For example, if you write a program that uses File.OpenRead to open a file for reading, then that program appears in the list of processes that have the file locked, but I don’t know how to determine whether the lock is for reading or writing. If you figure that out please say something in a comment below.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in files, system | Tagged , , , , , , , , , | 1 Comment

Use “banker’s rounding” and “normal rounding” in C#

[banker's rounding]

By default the Math.Round method uses “banker’s rounding.” In banker’s rounding a number with a final digit of 5 is rounded to the nearest even number rather than to the next larger number as you might expect. The idea is that statistically half of a sample of numbers are rounded up and half are rounded down.

For example, if you want to round to the nearest tenth, the value 1.35 and 1.45 are both rounded to 1.4 because 4 is the closest even tenth.

In contrast if you use normal rounding, values are rounded away from 0. For example, 1.45 is rounded up to 1.5 and -1.45 is rounded down to -1.5.

Note that neither rule is needed if the number doesn’t end with 5 in the final digit and that digit must be the one after the smallest digit that you want to keep. For example, suppose again that you’re rounding to the nearest tenth. The value 1.4500001 is slightly bigger than 1.45 so it rounds up to 1.5 no matter which rounding scheme you’re using.

The Math.Round method can take up to three parameters: the number to round, the number of digits after the decimal point to display, and a flag telling the method whether to use banker’s rounding or to round away from 0.

The following code demonstrates using banker’s rounding and normal rounding.

// By default, banker's rounding gives 1.4. 
double i = Math.Round(1.45, 1);

// Rounding away from 0 gives 1.5.
double j = Math.Round(1.45, 1, MidpointRounding.AwayFromZero);

The example program displays values for several numbers rounded with banker’s rounding and normal “away from 0” rounding.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, mathematics | Tagged , , , , , , , , , , , , | Leave a comment

See if a file is locked in C#

[locked]

Some applications lock files so you cannot write, read, delete, or otherwise mess with them. For example, when you open a file in Microsoft Word, it locks the file so you cannot delete it or open it for writing with another application.

The following FileIsLocked method returns true if a file is locked for a given kind of access. For example, pass this routine the access value FileAccess.Write if you want to learn whether the file is locked to prevent you from writing into it.

// Return true if the file is locked for the indicated access.
private bool FileIsLocked(string filename, FileAccess file_access)
{
    // Try to open the file with the indicated access.
    try
    {
        FileStream fs =
            new FileStream(filename, FileMode.Open, file_access);
        fs.Close();
        return false;
    }
    catch (IOException)
    {
        return true;
    }
    catch (Exception)
    {
        throw;
    }
}

The code simply tries to open the file for the given access method and sees whether that causes an error. It uses a try-catch block to handle any error that might occur. If the error is an IOException, then the method assumes the file is locked.

Note, however, that there may be other reasons the method could not access the file. For example, if the file’s path is on a nonexistent disk drive, the code will receive an IOException.

To avoid this kind of false positive, you might want to first check whether the file exists, at least if you’re checking read permissions. If you’re checking for a write lock, you may not need the file to exist if you’re going to overwrite the file. I decided to leave this issue out of the example to keep things simpler.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in files, system | Tagged , , , , , , , , , , , , , , | 3 Comments

Map between host names and IP addresses in C#

[IP addresses]

When you enter a host name and click Go, the program uses the following code to look up the host and display the IP addresses associated with it.

using System.Net;
...
// Display the entered host's IP address.
private void btnGo_Click(object sender, EventArgs e)
{
    this.Cursor = Cursors.WaitCursor;
    lstAddresses.Items.Clear();
    txtHost.Clear();
    try
    {
        IPHostEntry ip_host_entry =
            Dns.GetHostEntry(txtHost.Text);
        foreach (IPAddress address in ip_host_entry.AddressList)
        {
            lstAddresses.Items.Add(address.ToString());
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    this.Cursor = Cursors.Default;
}

The code clears its ListBox and then uses the Dns.GetHostEntry method to get information about the host. It loops through the host information’s AddressList collection of IPAddress objects and adds the IP addresses to the ListBox.

When you select an entry in the ListBox, the following code displays the host assigned to the selected IP address.

// Look up the selected IP address's host.
private void lstAddresses_SelectedIndexChanged(
    object sender, EventArgs e)
{
    IPHostEntry ip_host_entry =
        Dns.GetHostEntry(lstAddresses.SelectedItem.ToString());
    txtRecoveredHost.Text = ip_host_entry.HostName;
}

This code uses the Dns.GetHostEntry method to get information about the selected IP address. It then displays the assigned host’s name.

Previously this program returned multiple IP addresses for a single host. For example, the loookup for maps.google.com might return a dozen or more IP addresses. The current program seems to return a single address. I suspect this is a change in Windows 10 but I’m not sure. If you have more information about this, please post it in a comment below.

If you look up the host localhost, the program lists the two special IP addresses ::1 and 127.0.0.1. If you click either of those in the ListBox, the program displays your computer’s name.

If you enter your computer’s name in the Host text box or if you leave it blank, the program seems to return your current IP address plus some IPv6 addresses. See WikiPedia for more information on IPv6 addresses. If you click one of those valeus in the ListBox, you should see the host name followed by a dot and the DNS domain name. For more information on this, see this WikiPedia entry.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in internet, network, system | Tagged , , , , , , , , , , , , , , , | Leave a comment

List available culture codes in C#

[culture codes]

This example shows how you can make a list of the culture codes that are available on the system.

Several C# Helper examples deal with globalization including Localize a program in C#. To localize a program, you need to specify a culture code as in en-US for English in the United States or de-CH for German in Switzerland.


This example uses the following code to list the available culture codes.

// List the available culture names.
private void Form1_Load(object sender, EventArgs e)
{
    lvwCultures.FullRowSelect = true;

    // Add the names to the ListView.
    foreach (CultureInfo culture_info in
        CultureInfo.GetCultures(CultureTypes.AllCultures))
    {
        string specific_name = "(none)";
        try
        {
            specific_name = CultureInfo.CreateSpecificCulture(
                culture_info.Name).Name;
        }
        catch { }

        ListViewItem lvi =
            lvwCultures.Items.Add(culture_info.Name);
        lvi.SubItems.Add(specific_name);
        lvi.SubItems.Add(culture_info.EnglishName);
    }

    // Sort the names.
    lvwCultures.Sorting = SortOrder.Ascending;
    lvwCultures.Sort();

    // Color related cultures.
    Color color1 = Color.FromArgb(192, 255, 192);
    Color color2 = Color.LightGreen;
    Color bg_color = color2;
    string last_name = "";
    foreach (ListViewItem lvi in lvwCultures.Items)
    {
        string item_name = lvi.Text.Split('-')[0];
        if (item_name != last_name)
        {
            // Switch colors.
            bg_color = (bg_color == color1) ? color2 : color1;
            last_name = item_name;
        }
        lvi.BackColor = bg_color;
    }

    // Size the columns.
    lvwCultures.Columns[0].Width = -2;
    lvwCultures.Columns[1].Width = -2;
    lvwCultures.Columns[2].Width = -2;
}

The code starts by looping through the CultureInfo objects returned by CultureInfo.GetCultures(CultureTypes.AllCultures). For each CultureInfo object, the program adds the culture’s name, specific name, and English name to a ListView control. For some cultures the CreateSpecificCulture method may fail so the program uses a try-catch block to protect itself.

The culture codes initially come unsorted so the the program makes the ListView sort its values. It then loops through the values to give groups of related cultures the same background colors. As it loops through the values, it splits the culture’s name at the dash character. For example, the German languages have culture codes de-DE, de-AT, de-CH, and so forth. This code pulls out the de at the beginning.

If a culture’s prefix is different from the previous one, the code switches the background color it is using. In either case, the program sets the current item’s background color to the current color.

The program finishes by sizing the ListView control’s columns to fit their data.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in globalization, internationalization, system | Tagged , , , , , , , , , , , , , | Leave a comment

Make a cannon game in C#


[cannon game]

This simple cannon game randomly positions a target house. Then when you click the Shoot button, it gets angle and speed information for the cannon and enables a timer. To keep the graphics and timing simple, the program uses a scale of 1 pixel = 1 meter.

If you set the compile-time variable DEBUGGING to true, then the program draws the position where the cannon ball should ideally cross the Y coordinate where it was launched. If the ball started at ground level and the ground were flat, this is where the ball would land. It calculates this position using the formula:

    Distance = 2 * V * V * Sin(T) * Cos(T) / g

Here g is the acceleration due to gravity, which is 9.8 meters per second. The program redraws TICKS_PER_SECOND times per second so the acceleration per tick is 9.8 /(TICKS_PER_SECOND * TICKS_PER_SECOND) meters per second squared.

The following code shows how the cannon game sets up when you click the Shoot button.

// Launch a cannonball.
private void btnShoot_Click(object sender, EventArgs e)
{
    // Redraw.
    using (Graphics gr = picCanvas.CreateGraphics())
    {
        DrawField(gr);
    }

    // Get the speed.
    float speed;
    try
    {
        speed = float.Parse(txtSpeed.Text);
    }
    catch
    {
        MessageBox.Show("Invalid speed", "Invalid Speed",
            MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        return;
    }
    if (speed < 1)
    {
        MessageBox.Show("Speed must be at least 1 mps",
            "Invalid Speed", MessageBoxButtons.OK,
            MessageBoxIcon.Exclamation);
        return;
    }

    // Get the speed components in meters per tick.
    Vx = speed * Math.Cos(Theta) / TICKS_PER_SECOND;
    // Negative to go up.
    Vy = -speed * Math.Sin(Theta) / TICKS_PER_SECOND;

    // Disable UI elements.
    btnShoot.Enabled = false;
    txtDegrees.Enabled = false;
    txtSpeed.Enabled = false;
    Cursor = Cursors.WaitCursor;
    Application.DoEvents();

#if DEBUGGING
    // Draw the location where the cannon ball
    // should pass the Y position where it started.
    // Distance =
    //     2 * V^2 * Sin(T) * Cos(T) / g = V^2 * Sin(2*T) / g
    gr.DrawEllipse(Pens.Blue,
        (float)(BulletX + 2 * speed * speed * Math.Sin(Theta) *
            Math.Cos(Theta) / 9.8),
        (float)(BulletY), CANNON_HGT, CANNON_HGT);
#endif

    // Start moving the cannon ball.
    tmrMoveShot.Enabled = true;
}

The cannon game keeps track of the cannonball’s position and velocity with the variables BulletX, BulletY, Vx, and Vy. When the timer event occurs, the program moves the cannonball. It uses the velocity components to update the ball’s position and then redraws the ball.

private double Theta, BulletX, BulletY, Vx, Vy;

private const int TICKS_PER_SECOND = 10;

// Acceleration in meters per tick squared.
private const double YAcceleration =
    9.8 / (TICKS_PER_SECOND * TICKS_PER_SECOND);

private void tmrMoveShot_Tick(object sender, EventArgs e)
{
    // Erase the cannon ball's previous position.
    using (Graphics gr = picCanvas.CreateGraphics())
    {
        using (Brush br = new SolidBrush(picCanvas.BackColor))
        {
            gr.FillEllipse(br, (float)(BulletX), (float)(BulletY),
                CANNON_HGT, CANNON_HGT);
        }

        // Move the cannon ball.
        Vy += YAcceleration;
        BulletX += Vx;
        BulletY += Vy;

        // Draw the new cannon ball.
        gr.FillEllipse(Brushes.Black,
            (float)(BulletX), (float)(BulletY),
            CANNON_HGT, CANNON_HGT);

        // See if we should stop.
        if ((BulletY > picCanvas.ClientRectangle.Height) ||
            (BulletX > picCanvas.ClientRectangle.Width))
        {
            // Stop running.
            tmrMoveShot.Enabled = false;

            // Re-enable UI elements.
            btnShoot.Enabled = true;
            txtDegrees.Enabled = true;
            txtSpeed.Enabled = true;
            Cursor = Cursors.Default;
        }
    }
}

Some improvements that you could make to the cannon game include hit detection to know when the ball hits the target, drawing a nice irregular surface between the cannon and the target, placing the target at different altitudes,allowing the target to shoot back, etc. Perhaps I’ll get around to some of them one day.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, animation, games, graphics | Tagged , , , , , , , , , , , , , | Leave a comment

Convert a bitmap into a cursor in C#

[cursor]

Converting a bitmap into a cursor isn’t easy, but you can do it by using the CreateIconIndirect API function. This example uses the following BitmapToCursor method to create a cursor from a bitmap.

// Create a cursor from a bitmap.
private Cursor BitmapToCursor(Bitmap bmp, int hot_x, int hot_y)
{
    // Initialize the cursor information.
    ICONINFO icon_info = new ICONINFO();
    IntPtr h_icon = bmp.GetHicon();
    GetIconInfo(h_icon, out icon_info);
    icon_info.xHotspot = hot_x;
    icon_info.yHotspot = hot_y;
    icon_info.fIcon = false;    // Cursor, not icon.

    // Create the cursor.
    IntPtr h_cursor = CreateIconIndirect(ref icon_info);
    return new Cursor(h_cursor);
}

Download the example and look at the code to see the API declarations.

This code creates an ICONINFO structure to describe the cursor that it will create. It calls the bitmap’s GetHicon method to get a handle to an icon that has the same image as the bitmap. It passes that handle to the GetIconInfo API function to get ICONINFO data describing the icon.

Next the code sets the X and Y coordinates of the cursor’s hot spot, the position inside the cursor that represents the mouse’s position. For example, an arrow cursor would normally use the arrow’s tip as the hot spot. A cross would normally use the center of the cursor as its hotspot.

The code then sets the ICONINFO structure’s fIcon property to false to indicate that this should be a cursor and not an icon. It then calls the CreateIconIndirect method to create the icon and get a new handle to it. Finally the code passes the handle to the Cursor class’s constructor and returns the resulting Cursor object.

Note that these API functions allocate unmanaged memory that is never freed by the program. If the program only creates a few cursors, that’s not a problem. If the program could create hundreds of cursors on the fly, this will cause a memory leak that may be a problem. In that case, use the DestroyIcon API function to release cursors that are no longer needed.

The program uses the following code to create its cursor.

private void Form1_Load(object sender, EventArgs e)
{
    // Make pixels that match the one in the
    // upper left corner transparent.
    Bitmap bm = Properties.Resources.Diamond;
    bm.MakeTransparent(bm.GetPixel(0, 0));
    this.Cursor = BitmapToCursor(bm, 7, 7);
}

The program gets the bitmap resource named Diamond and makes the pixels that match the one in its upper left corner transparent. It then calls the BitmapToCursor method and uses the result as the form’s cursor.

Using this technique you can make cursors of any size, but you should probably stick to relatively small sizes such as the typical 16×16 pixels.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in graphics, image processing | Tagged , , , , , , , , , , , , , | Leave a comment