Tile a board with randomly colored trominoes in C#

[example]

The post Tile a board with colored trominoes in C#explains how to color a tromino filing so no two adjacent trominoes share the same color. That example basically tried every possible color for each of the trominoes until it found one that worked.

Unfortunately, that approach makes the algorithm prefer colors near the beginning of the color list. That’s not a big problem with the trominoes because the tiling pattern requires the program to use a lot of colors, but the result can look strange when you color some other maps. In those cases, the coloring uses the first few colors the most and only occasionally uses the other colors. For example, the result might be a map that uses lots of yellow and blue areas and only one or two red areas.

This example examines the allowed colors in random order so it will use more of the colors that are later in the list of allowed colors. The program also uses more colors than the previous version, so the trominoes look more randomly colored.

The only change to the program is in the following code snippet where the program tries different colors for a tromino.

// Try each of the colors for this Chair.
Chair this_chair = Chairs[start_chair];
int[] color_nums =
    Enumerable.Range(0, Chair.BgBrushes.Length).ToArray();
color_nums.Randomize();
foreach (int color_num in color_nums)
{
    if (this_chair.ColorAllowed(color_num))
    {
        this_chair.BgBrushNum = color_num;
        if (FindColoring(start_chair + 1)) return true;
    }
}

This code creates an array named color_nums that contains the numbers 0, 1, 2, up to one less than the number of colors in the Chair.BgBrushes array. It then uses the Randomize extension method described shortly to randomize the color_nums array. The code then loops through the randomized colors and tries each until it finds one that works.

The following code shows the Randomize extension method.

// Randomize an array.
public static void Randomize<T>(this T[] items)
{
    // For each spot in the array, pick
    // a random item to swap into that spot.
    for (int i = 0; i < items.Length - 1; i++)
    {
        int j = Rand.Next(i, items.Length);
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }
}

This method loops through the spots in the array. For each spot, it picks a random following spot and swaps the items in those two positions. When it is done, each of the array’s items may have been swapped into any of the array’s positions with equal probability.

See the previous example and download this example’s program to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics, mathematics, recursion | Leave a comment

Tile a board with colored trominoes in C#

[example]

The post Tile a board with trominoes in C# explains how to tile a board with a missing square with trominoes. This post shows how to color the trominoes so no two adjacent ones share the same color.

The four-color theorem states that you can color any map (separation of a plane into contiguous regions) with only four colors. (The theorem was proposed in 1852 but only proven in 1976.) Unfortunately the proof is very long, complicated, and computer assisted, so it does not give a practical method for finding a coloring.

My book Essential Algorithms describes several approaches for coloring maps including a five-coloring algorithm.

Another approach is to simply iterate through every possible combination of colors until you find one that works. In general, this can be slow. If a map has N regions and you want to use C colors, then there are CN possible combinations of colors and that can be a very large number. For example, there are currently 195 countries in the world. If you want to color them with four colors, then there are 4195 ≈ 2.5×10117.

Fortunately, many maps are smaller and many are relatively easy to color so the program doesn’t need to examine every possible combination.

To color the trominoes, this program must perform two main tasks: finding the trominoes’ neighbors and finding a valid coloring.

Finding the Trominoes’ Neighbors

The previous example created an array of PointF to represent each tromino. To determine which trominoes are neighbors, you could loop through every pair of trominoes and then see if they share a common edge. To see if they share an edge, you could see if any of the adjacent pairs of points in the first tromino matched any adjacent pair in the second tromino. When you considered a pair of edges, you would need to check to see if one of the edges was backwards. You would also need to check for approximate matches caused by rounding errors.

That all seemed inelegant and a lot of work, so I took a different approach. When the program creates a chair shape, it knows which rows and columns on the board that chair occupies. To make it easier to find neighbors, I created a Chair class to represent the trominoes. The class stores a chair’s points and the squares that it occupies.

The following code shows the beginning of the Chair class.

public class Chair
{
    public static Brush[] BgBrushes =
    {
        Brushes.Red, Brushes.Blue, Brushes.Yellow,
    };

    public int Number;
    public int BgBrushNum = 0;
    public List<Point> Squares = new List<Point>();
    public PointF[] Points;
    public List<Chair> Neighbors;

The class declares a static array of brushes that it will use to color the trominoes. I believe (but have not proven formally) that you can always color a tromino tiling with only three colors, so the array only contains red, blue, and yellow.

Next, the class declares chair properties such as the chair’s number and brush index in the array. The Squares list holds the squares that the chair occupies. The X and Y coordinates of the points in the list give the squares’ rows and columns on the board.

The Points array holds the chair’s polygon points as before. The Neighbors list will indicate the Chairs that share an edge with this one.

The class also defines methods to perform such tasks as drawing the chair. Download the example and look at the code to see how that works. For now, I only want to described two more methods.

The following AreNeighbors method returns true if two squares share a common edge.

// Return true if the two squares are neighbors.
public static bool AreNeighbors(Point p1, Point p2)
{
    if ((p1.X == p2.X) && (Math.Abs(p1.Y - p2.Y) == 1)) return true;
    if ((p1.Y == p2.Y) && (Math.Abs(p1.X - p2.X) == 1)) return true;
    return false;
}

If the two squares are in the same row and their columns differ by one, then they are adjacent horizontally so the method returns true.

Similarly, if the two squares are in the same column and their rows differ by one, then they are adjacent vertically so the method returns true.

If neither of those tests makes the method return true, then the squares are not adjacent so the method returns false.

The second method is the following IsNeighbor method, which returns true if the current chair shares an edge with another chair.

// Return true if the two Chairs are neighbors.
public bool IsNeighbor(Chair other)
{
    foreach (Point p1 in Squares)
        foreach (Point p2 in other.Squares)
            if (AreNeighbors(p1, p2)) return true;
    return false;
}

This method simply loops through all of the squares occupied by both chairs and calls the AreNeighbors method to see if any of the squares are neighbors.

The main program uses the following FindNeighbors method to finds the trominoes’ neighbors.

// Find the Chairs' neighbors.
private void FindNeighbors()
{
    int num_chairs = Chairs.Count;

    // Create empty Neighbors lists.
    foreach (Chair chair in Chairs)
        chair.Neighbors = new List<Chair>();

    // Find neighbors.
    for (int i = 0; i < num_chairs - 1; i++)
    {
        for (int j = i + 1; j < num_chairs; j   ++)
        {
            if (Chairs[i].IsNeighbor(Chairs[j]))
            {
                Chairs[i].Neighbors.Add(Chairs[j]);
                Chairs[j].Neighbors.Add(Chairs[i]);
            }
        }
    }
}

The method first loops through its Chairs list, which holds a reference to each Chair object and sets each chair’s Neighbors list to an empty list.

Next, the code loops through every pair of distinct chairs and calls uses the Chair class’s IsNeighbor method to determine whether the two chairs are neighbors. If they are neighbors, the method adds each chair to the other’s Neighbors list.

Finding a Valid Coloring

After you know which trominoes are adjacent to which others, finding a coloring is just a matter of trying possibilities. The following FindColoring method starts the process.

// Find a coloring of the Chairs.
private void FindColoring()
{
    // Find the neighbors.
    FindNeighbors();

    // Give each Chair color number -1.
    foreach (Chair chair in Chairs) chair.BgBrushNum = -1;

    // Assign colors.
    if (!FindColoring(0))
        MessageBox.Show("Could not find coloring.");
}

The method first calls the FindNeighbors method described earlier. It then loops through the chairs and sets all of their BgBrushNum properties to -1 to indicate that they do not have an assigned color.

The method finishes by calling the following FindColoring method. It passes that method the parameter 0 to indicate that it should start assigning a color to the first chair.

// Find a coloring of the Chairs starting from Chair
// number start_chair Return true if we find a coloring.
private bool FindColoring(int start_chair)
{
    // If we are beyond the end of the Chairs list, we are done.
    if (start_chair == Chairs.Count) return true;

    // Try each of the colors for this Chair.
    int num_colors = Chair.BgBrushes.Length;
    Chair this_chair = Chairs[start_chair];
    for (int color_num = 0; color_num < num_colors; color_num++)
    {
        if (this_chair.ColorAllowed(color_num))
        {
            this_chair.BgBrushNum = color_num;
            if (FindColoring(start_chair + 1)) return true;
        }
    }

    // We failed to find a coloring. Backtrack.
    this_chair.BgBrushNum = -1;
    return false;
}

This method recursively assigns colors to chairs starting with chair number start_chair. The method first checks to see if it is finished. If chair_number equals the total number of chairs, then every chair has been assigned a color so the method returns true to indicate that it has found an assignment.

If the method is not finished, it loops through the numbers that represent the allowed color indices. In the example as it is written, the colors in the Chair.BgBrushes array have indices 0 through 2, so the loop ranges from 0 to 2.

Inside the loop, the code calls the current chair’s ColorAllowed method (described shortly) to see if that chair can have the color that is currently being examined by the loop. If the chair could have that color, the method tentatively assigns the chair that color and then recursively calls itself to assign the next chair’s color.

If the recursive call returns true, then it found a valid coloring so the method also returns true to unwind the recursion.

If the loop ends with none of the colors leading to a valid coloring, the method resets the chair’s color to -1 and returns false. A call higher up the call stack will change the color of the chair it is examining and the recursive calls will try again until they find a valid coloring or until all combinations have been tried unsuccessfully.

The Chair class defines the following ColorAllowed method, which returns true if the chair can have a certain color.

// Return true if this Chair could have this color number.
public bool ColorAllowed(int color_num)
{
    foreach (Chair chair in Neighbors)
        if (chair.BgBrushNum == color_num)
            return false;
    return true;
}

This method simply loops through the chair’s neighbors and returns false if any of those neighbors have the color color_num. If none of the neighbors have been assigned that color, the method returns true.

Conclusion

The program includes a few other details (such as the pieces described in the earlier post, but those are the most interesting new pieces. Download the example program to see additional details. You can also experiment with the program. See if you can find an instance where you need more than three colors to color the trominoes. Or post a comment if you figure out how to prove that three colors is enough.

In my next post, I’ll explain how you can randomize the colors. That allows the program to find more three-color arrangements or to use more than three colors.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics, mathematics, recursion | Tagged , , , , , , , , , , , , , , , , , , | 1 Comment

Tile a board with trominoes in C#

[trominoes]

Trominoes are polyominoes of order three. That means they are polygons made up of three equal sized squares joined at their edges. There only are two kinds of trominoes: three squares joined in a line and three squares joined in an L shape. L-shaped trominoes also looks sort of like chairs, so that’s what I’ll call them here.

American mathematician Solomon W. Golomb devised Golomb’s Tromino Theorem, which states that you can tile any 2n×2n (n ≥ 1) chessboard that has one square removed with chair trominoes as shown in the picture at the top of this post.

The following sections describe the proof of Golomb’s Tromino Theorem and the code that the example program uses to find tilings.

Proof

You can prove the theorem by induction, and that proof leads to a method for finding a tiling. For the base case, consider a 21×21 board with one square removed. The following picture shows the four tilings that you might need to use depending on the location of the removed square.


[trominoes]

Now suppose the theorem applies for n and consider the case of n + 1 so the board has size 2n+1×2n+1. Divide the board into four sub-boards with size 2n+1×2n+1. One of the quadrants holds the missing square. Place a tromino in the center squares of the three other sub-boards as shown in red in the following picture. Now each sub-board has one missing square. Because the theorem applies for those smaller sub-boards, we know that we can find a tiling for them, and that gives a tiling for the full 2n×2n board.


[trominoes]

By induction, that means the theorem applies for boards of size 2n×2n for all n ≥ 1.

Setup Code

The code to find the trominoes tilings is surprisingly complicated. The following code shows variables that the program uses to understand the board size.

// The quadrant that holds the square to be ignored.
private enum Quadrants { NW, NE, SE, SW };

private int SquaresPerSide = 0;
private float SquareWidth, BoardWidth, Xmin, Ymin;

// The location of the missing square.
private int MissingX, MissingY;

private List<PointF[]> Chairs;

The Quadrants enumeration defines the four directions northwest, northeast, southeast, and southwest that we can use to keep track of a board’s quadrants.

The variable SquaresPerSide holds the number of squares on each side of the board. The variables SquareWidth, BoardWidth, Xmin, and Ymin hold values used to draw the board. For example, Xmin and Ymin give the upper left corner of the board.

Values MissingX and MissingY gives the row and column (starting from 0) of the missing square.

Finally, Chairs is a list of point arrays that represent the trominoes. For example, Chairs[0] is an array of points representing the first tromino.

When the program starts or when you click the Tile button, the following code finds a tiling.

// Make and solve a new board.
private void Form1_Load(object sender, EventArgs e)
{
    MakeBoard();
}
private void btnTile_Click(object sender, EventArgs e)
{
    MakeBoard();
}
private void MakeBoard()
{
    int n = int.Parse(txtN.Text);
    SquaresPerSide = (int)Math.Pow(2, n);

    // Set board parameters.
    SquareWidth = (picBoard.ClientSize.Width - 10f) / SquaresPerSide;
    float hgt = (picBoard.ClientSize.Height - 10f) / SquaresPerSide;
    if (SquareWidth > hgt) SquareWidth = hgt;
    BoardWidth = SquareWidth * SquaresPerSide;
    Xmin = (picBoard.ClientSize.Width - BoardWidth) / 2;
    Ymin = (picBoard.ClientSize.Height - BoardWidth) / 2;

    // Pick a random empty square.
    Random rand = new Random();
    MissingX = rand.Next(SquaresPerSide);
    MissingY = rand.Next(SquaresPerSide);

    // Solve the board.
    Chairs = new List<PointF[]>();
    SolveBoard(
        0, SquaresPerSide - 1,
        0, SquaresPerSide - 1,
        MissingX, MissingY);

    // Redraw.
    picBoard.Refresh();
}

The form’s Load event handler and the Tile button’s Click event handler call the MakeBoard method. That method gets the value for n that you entered in the text box and calculates the number of squares per side (2n).

Next, the method calculates the size that a square would have if the board fills the picture box horizontally. It also calculates the size a square would have if the board fills the picture box vertically, and sets SquareWidth to the smaller of the two sizes so the board will fit. It then uses the size to calculate Xmin and Ymin to center the board in the drawing area.

The method then uses a Random object to pick a random row and column to hold the missing square. It then creates a new Chairs list and calls SolveBoard to find a tiling. The method finishes by refreshing the picBoard picture box to show the solution.

SolveBoard

The following code shows the SolveBoard method.

// Solve the board.
private void SolveBoard(int imin, int imax, int jmin, int jmax,
    int imissing, int jmissing)
{
    // See if this is a 2x2 square.
    if (imax - imin == 1)
    {
        // It is a 2x2 square. Make its chair.
        Chairs.Add(MakeChair(imin, imax, jmin, jmax,
            imissing, jmissing));
        return;
    }

    // Not a 2x2 square. Divide into 4 pieces and recurse.
    int imid = (imin + imax) / 2;
    int jmid = (jmin + jmax) / 2;
    switch (QuadrantToIgnore(imin, imax, jmin, jmax,
        imissing, jmissing))
    {
        case Quadrants.NW:
            // Make the chair in the middle.
            Chairs.Add(MakeChair(imid, imid + 1, jmid, jmid + 1, imid, jmid));

            // Recurse.
            SolveBoard(imin, imid, jmin, jmid, imissing, jmissing);         // NW
            SolveBoard(imid + 1, imax, jmin, jmid, imid + 1, jmid);         // NE
            SolveBoard(imid + 1, imax, jmid + 1, jmax, imid + 1, jmid + 1); // SE
            SolveBoard(imin, imid, jmid + 1, jmax, imid, jmid + 1);         // SW
            break;
        case Quadrants.NE:
            // Make the chair in the middle.
            Chairs.Add(MakeChair(imid, imid + 1, jmid, jmid + 1, imid + 1, jmid));

            // Recurse.
            SolveBoard(imin, imid, jmin, jmid, imid, jmid);                 // NW
            SolveBoard(imid + 1, imax, jmin, jmid, imissing, jmissing);     // NE
            SolveBoard(imid + 1, imax, jmid + 1, jmax, imid + 1, jmid + 1); // SE
            SolveBoard(imin, imid, jmid + 1, jmax, imid, jmid + 1);         // SW
            break;
        case Quadrants.SE:
            // Make the chair in the middle.
            Chairs.Add(MakeChair(imid, imid + 1, jmid, jmid + 1, imid + 1, jmid + 1));

            // Recurse.
            SolveBoard(imin, imid, jmin, jmid, imid, jmid);                 // NW
            SolveBoard(imid + 1, imax, jmin, jmid, imid + 1, jmid);         // NE
            SolveBoard(imid + 1, imax, jmid + 1, jmax, imissing, jmissing); // SE
            SolveBoard(imin, imid, jmid + 1, jmax, imid, jmid + 1);         // SW
            break;
        case Quadrants.SW:
            // Make the chair in the middle.
            Chairs.Add(MakeChair(imid, imid + 1, jmid, jmid + 1, imid, jmid + 1));

            // Recurse.
            SolveBoard(imin, imid, jmin, jmid, imid, jmid);                 // NW
            SolveBoard(imid + 1, imax, jmin, jmid, imid + 1, jmid);         // NE
            SolveBoard(imid + 1, imax, jmid + 1, jmax, imid + 1, jmid + 1); // SE
            SolveBoard(imin, imid, jmid + 1, jmax, imissing, jmissing);     // SW
            break;
    }
}

This method’s parameters indicate what part of the full board that this method call must tile with trominoes. The parameters imin and imax give the smallest and largest column numbers of interest. Similarly, jmin and jmax give the smallest and largest row numbers of interest.

The parameters imissing and jmissing give the column and row of the square that is missing from this part of the board. Note that this square might actually be missing in the full board, or it might be a square that we should ignore while tiling this part of the board because it has already been covered by the red tromino in the earlier picture.

If the method is trying to tile a 2×2 board, it simply calls the MakeChair method described later to create a tromino for the board. It adds that tromino to the Chairs list and returns.

If the board is larger than 2×2, the code finds the row and column of the middle of the board. Because this calculation uses integer division, imid and jmid give the largest column and row in the upper left quadrant.

Next, the code calls the QuadrantToIgnore method described later to see which quadrant contains the square that we should ignore. The method uses a switch statement to determine how to position the red tromino and recursively call itself as shown in the earlier picture.

This is the trickiest section of code. If the calls are incorrect, the result isn’t a tiling and figuring out exactly what went wrong is very difficult. The best approach to this code is to just walk through it very carefully and make sure there are no mistakes the first time.

For each case, the code calls the MakeChair method to make a polygon for the red tromino shown in the earlier picture and adds that polygon to the Chairs list.

The SolveBoard method then calls itself recursively to tile the four sub-boards with trominoes. It adjusts the imin, imax, jmin, and jmax arguments to make the calls visit the sub-boards. It changes the imissing and jmissing arguments to make the recursive calls ignore the appropriate squares. For the sub-board that contains the square that the original call to SolveBoard was supposed to ignore, that recursive call still ignores that square. For the other sub-boards, the recursive call ignores the square covered by the red tromino in the earlier picture.

MakeChair

The MakeChair method returns an array of points representing a tromino within a 2×2 sub-board. It took me a while to find a nice solution that can draw any of the trominoes we might need. Instead of using a switch statement to return one of four complicated lists of points, I decided to make a single set of points representing the square on the left in the following picture.


[trominoes]

Then the method moves one of the corner points to the center of the sub-board. The picture on the right above shows how it modifies the points when the square to ignore is in the northeast quadrant. (The resulting polygon includes two points that it really doesn’t need in the middle of two of the chair’s sides, but they don’t hurt much so I won’t go to the trouble of removing them.)

The following code shows the MakeChair method.

// Make a chair polygon.
private PointF[] MakeChair(int imin, int imax,
    int jmin, int jmax, int imissing, int jmissing)
{
    // Make the initial points.
    float xmin = Xmin + imin * SquareWidth;
    float ymin = Ymin + jmin * SquareWidth;
    PointF[] points =
    {
        new PointF(xmin, ymin),
        new PointF(xmin + SquareWidth, ymin),
        new PointF(xmin + SquareWidth * 2, ymin),
        new PointF(xmin + SquareWidth * 2, ymin + SquareWidth),
        new PointF(xmin + SquareWidth * 2, ymin + SquareWidth * 2),
        new PointF(xmin + SquareWidth, ymin + SquareWidth * 2),
        new PointF(xmin, ymin + SquareWidth * 2),
        new PointF(xmin, ymin + SquareWidth),
    };

    // Push in the appropriate corner.
    PointF middle = new PointF(
        xmin + SquareWidth,
        ymin + SquareWidth);
    switch (QuadrantToIgnore(imin, imax, jmin, jmax,
        imissing, jmissing))
    {
        case Quadrants.NW:
            points[0] = middle;
            break;
        case Quadrants.SW:
            points[6] = middle;
            break;
        case Quadrants.NE:
            points[2] = middle;
            break;
        case Quadrants.SE:
            points[4] = middle;
            break;
    }

    return points;
}

The method first sets xmin and ymin to the X and Y coordinates of the sub-board’s upper left point. It then creates an array holding the points shown on the left in the earlier picture.

Next, the method finds the sub-board’s midpoint. It then calls the QuadrantToIgnore method described shortly to figure out which quadrant contains the square that we should ignore. It then uses a switch statement to move the appropriate corner point to the sub-board’s midpoint.

QuadrantToIgnore

The QuadrantToIgnore method determines which quadrant of a sub-board contains the square that you should ignore when tiling the sub-board with trominoes. The following code shows the method.

// Return the quadrant holding the square to be ignored for this square.
private Quadrants QuadrantToIgnore(int imin, int imax, int jmin, int jmax,
    int imissing, int jmissing)
{
    int imid = (imin + imax) / 2;
    int jmid = (jmin + jmax) / 2;
    if (imissing <= imid)      // West.
    {
        if (jmissing <= jmid) return Quadrants.NW;
        return Quadrants.SW;
    }
    else                        // East.
    {
        if (jmissing <= jmid) return Quadrants.NE;
        return Quadrants.SE;
    }
}

The method sets imid and jmid to the column and row at the center of the sub-board. Because the calculation uses integer division, imid and jmid give the largest column and row in the upper left quadrant.

The code then uses a simple set of if statements to determine which quadrant contains the missing square.

Drawing the Trominoes

The program’s last piece is the following Paint event handler, which draws the tilings trominoes.

// Draw the board.
private void picBoard_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(picBoard.BackColor);
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    // Draw the chairs.
    foreach (PointF[] points in Chairs)
    {
        e.Graphics.DrawPolygon(Pens.Red, points);
    }

    // Draw the missing square.
    float x = Xmin + MissingX * SquareWidth;
    float y = Ymin + MissingY * SquareWidth;
    e.Graphics.FillRectangle(Brushes.Black,
        x, y, SquareWidth, SquareWidth);
}

This code clears the Graphics object and prepares it to draw smoothly. It then loops through the trominoes in the Chairs list and draws each polygon in red.

The method finishes by filling the missing square with black.

Conclusion

The proof that a tiling with trominoes is possible is relatively straightforward, at least if you’re familiar with inductive proofs. The proof leads naturally to a recursive method to finding a tiling, but the details are a bit tricky. In particular, if you make any mistakes in the SolveBoard method’s recursive calls, the result isn’t a tiling and figuring out what went wrong is hard.

In my next post, I’ll explain how you can color the trominoes so that no two adjacent trominoes have the same color.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics, mathematics, recursion | Tagged , , , , , , , , , , , , , , , , , | 1 Comment

Draw text wrapped to fit columns in C#


[draw text]

The post Draw aligned columns of data in C# shows one way to draw text in rows and columns. It uses the Graphics class’s MeasureString method to size the columns so they are big enough to display their data without wrapping.

In a comment, oghenez asked how you could wrap the data if it was too wide to fit in a column. This post explains how to do that.

For starters, the program cannot set the column widths by measuring the text. If it did that, it would make the columns wide enough to display all of the text. To avoid that, you need to somehow decide on the column widths yourself. This example uses the following code to define the column widths and text alignments.

// The column sizes and alignments.
private int[] ColWidths = { 200, 200, 90, 70, 130};
private StringAlignment[] VertAlignments =
{
    StringAlignment.Center,
    StringAlignment.Center,
    StringAlignment.Near,
    StringAlignment.Near,
    StringAlignment.Near,
};
private StringAlignment[] HorzAlignments =
{
    StringAlignment.Near,
    StringAlignment.Near,
    StringAlignment.Far,
    StringAlignment.Center,
    StringAlignment.Near,
};

The first statement initializes the ColWidths array, which indicates the widths that the columns should have in pixels. The rest of this code defines the vertical and horizontal alignments that the program should use to draw text in the different columns.

When the program’s PictureBox needs to redraw itself, the following code draws text for the values stored in the Values array.

// Display the data aligned in columns.
private void picColumns_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.White);
    e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;

    using (StringFormat sf = new StringFormat())
    {
        using (Font font = new Font("Times New Roman", 12))
        {
            const int margin = 4;
            int y = margin;

            // Display the rows.
            foreach (string[] row in Values)
            {
                // Measure the row's entry heights.
                int num_cols = row.Length;
                int max_height = 0;
                for (int col_num = 0; col_num < num_cols; col_num++)
                {
                    SizeF col_size = e.Graphics.MeasureString(
                        row[col_num], font,
                        ColWidths[col_num] - 2 * margin);
                    int new_height = (int)Math.Ceiling(col_size.Height);
                    if (max_height < new_height)
                        max_height = new_height;
                }
                max_height += 2 * margin;

                // Draw the row's entries.
                int x = margin;
                for (int col_num = 0; col_num < num_cols; col_num++)
                {
                    Rectangle box_rect = new Rectangle(x, y,
                        ColWidths[col_num], max_height);
                    Rectangle text_rect = box_rect;
                    text_rect.Inflate(-margin, -margin);

                    sf.Alignment = HorzAlignments[col_num];
                    sf.LineAlignment = VertAlignments[col_num];
                    e.Graphics.DrawString(row[col_num], font,
                        Brushes.Black, text_rect, sf);

                    e.Graphics.DrawRectangle(Pens.Blue, box_rect);

                    x += ColWidths[col_num];
                }

                y += max_height;
            }
        }
    }
}

This code creates a StringFormat object to specify the alignments used to draw text in the various columns. It also creates a font to use to draw text.

Next, the code defines a margin to place around the text. The program sets variable y to the margin’s value so the first piece of text leaves a small gap at the top of the form. The code then loops through the rows in the Values array.

Each entry in the Values array is an array containing the values that should be drawn in its row. The code initializes max_height to zero. It then loops trough the values in this row.

The code uses the Graphics object’s MeasureString method to see how much space each value needs if it is to be written with its column’s given width. When you call MeasureString and specify a width as in this code, the returned SizeF structure is as tall as necessary to draw the text with the indicated width. This code subtracts twice the margin from the column width so it can place a margin between the edges of the column and the text.

The code rounds the returned SizeF structure’s height up to the nearest integer. If the result is greater than max_height, the code updates max_height.

After it has found the largest height required to display each of the row’s pieces of text, the code adds twice the margin to place some extra space around the text.

Next, the program loops through the row’s entries a second time. For each entry, it makes a Rectangle named box_rect to represent the box around the text. This box is as wide as the column (as stored in the ColWidths array) and has the height found by the previous loop (which includes twice the margin).

The code then copies the Rectangle into a new one named text_rect and uses its Inflate method to make it smaller by the amount margin in all directions. That keeps the Rectangle centered but makes it smaller by the amount margin in the left, right, up, and down directions.

Now the program sets the column’s alignment properties in the StringFormat object that it created earlier and uses that object to draw text in text_rect. It then draws the box_rect to place a box around the text.

After drawing the current entry, the code adds the column’s width to variable x so the next column starts shifted to the right.

After it has drawn all of the text for the row, the code increases y by the row’s height to prepare for the next row.

Note that this example does not break across pages. If the data is long, the program will not stop and start a new page. instead it will simply create a very long single page. You could modify the program to display its data in some sort of scrolled window. Or, if you are printing the data, you could display it on multiple pages.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in fonts, graphics, lists | Tagged , , , , , , , , , , , , , , , , | Leave a comment

Draw a randomly colored Sierpinski pentagon in C#

[example]

The example Draw a colored Sierpinski pentagon in C# lets the user click on the parts of a Sierpinski pentagon to change their colors. Eddie Bole thought it would be interesting to color the larger pentagons in addition to the smaller ones into which each is divided. This example does that.

To make that easier, I modified the Pentagon class to more accurately reflect the recursive structure of the Sierpinski pentagon. The following code shows the new Pentagon class.

class Pentagon
{
    // Colors.
    private static Random Rand = new Random();
    private static Color[] MyColors =
    {
        Color.Black, Color.Red, Color.Orange,
        Color.Yellow, Color.Lime, Color.LightGreen,
        Color.Cyan, Color.Blue, Color.LightBlue,
        Color.Fuchsia,
    };

    public PointF[] Points = null;
    public Color FillColor = Color.Black;
    public List<Pentagon> Children = new List<Pentagon>();

    // Constructor.
    public Pentagon(PointF[] points)
    {
        Points = points;
        FillColor = MyColors[Rand.Next(0, MyColors.Length)];
        FillColor = Color.FromArgb(128, FillColor);
    }

    // Draw.
    public void Draw(Graphics gr)
    {
        // Draw this Pentagon.
        using (Brush brush = new SolidBrush(FillColor))
        {
            gr.FillPolygon(brush, Points);
        }
        gr.DrawPolygon(Pens.Black, Points);

        // Draw child Pentagons (if any).
        foreach (Pentagon child in Children) child.Draw(gr);
    }
}

The new class defines the colors that a Pentagon could have. The MyColors array is static so it is shared by all instances of the class. (There’s no point making each object take up room for its own separate copy.)

The new Children list gives the pentagons into which the current pentagon is divided to make the Sierpinski pentagon. At the deepest level of recursion, the smallest pentagons are not divided so their Children lists are empty.

The class’s constructor saves the pentagon’s points and then picks a random color from the MyColors array. It then recreates the color setting its alpha component to 128 so the color is semi-transparent. Alternatively, you could make the MyColors array contain colors with alpha is already 128, but then you would have to specify the colors by giving their red, green, blue, and alpha color components instead of using their names. The code shown here specifies the colors by name so it’s easier to know what the colors are.

The Pentagon class’s Draw method now draws the current Pentagon. It then recursively calls the Draw methods for its children.

The main program uses the following code to build its Pentagon objects.

// The root of the Pentagon object hierarchy.
private Pentagon Root = null;

// Make the Pentagon objects and redraw.
private void MakePentagons()
{
    // Build the Root.
    int depth = (int)nudDepth.Value;
    PointF center = new PointF(
        picPentagon.ClientSize.Width / 2,
        picPentagon.ClientSize.Height / 2);
    float radius = (float)Math.Min(center.X, center.Y);
    Root = MakePentagon(depth, center, radius);

    // Redraw.
    picPentagon.Refresh();
}

The program stores the largest Pentagon object in the variable Root.

The MakePentagons method builds the Sierpinski pentagon. It gets the desired depth of recursion, finds the center of the drawing area, and sets the large pentagon’s radius. It then calls the following MakePentagon method to create the largest pentagon.

// Recursively generate a Pentagon and its descendants.
private Pentagon MakePentagon(int depth, PointF center, float radius)
{
    // Make the Pentagon.
    Pentagon parent = new Pentagon(GetPentagonPoints(center, radius));

    // If we are not done recursing, make children.
    if (depth > 0)
    {
        // Find the smaller pentagons' centers.
        float d = radius - radius * size_scale;
        PointF[] centers = GetPentagonPoints(center, d);

        // Recursively draw the smaller pentagons.
        foreach (PointF point in centers)
        {
            parent.Children.Add(MakePentagon(
                depth - 1, point, radius * size_scale));
        }
    }

    return parent;
}

This method first creates a new Pentagon object.

If the depth of recursion has not reached zero, the method gets the pentagon’s vertices. The method then loops through the vertices and recursively calls itself to make smaller pentagons at each of the vertices.

When the program needs to redraw the Sierpinski pentagon, it refreshes the picPentagon PictureBox and the following Paint event handler executes.

// Draw.
private void picPentagon_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(picPentagon.BackColor);
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    if (Root == null) return;

    Root.Draw(e.Graphics);
}

This code simply prepares the Graphics object and then calls the Root pentagon’s Draw method. that method recursively calls the Draw methods for all of the smaller pentagons.

Note that the pentagons are drawn in order from largest to smallest. That ensures that the smaller pentagons are drawn on top of the larger ones.

Those are the most interesting changes to the program. Download the example to see additional details and to experiment with the program.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, fractals, graphics, recursion | Tagged , , , , , , , , , , | Leave a comment

Draw a colored Sierpinski pentagon in C#

[example]

The example Draw a Sierpinski pentagon in C# shows how to draw a Sierpinski pentagon. This example modifies that one so the user can click on pentagons to change their colors. To do that, this program takes a very different approach to managing its pentagons. It performs three main tasks: managing pentagons, defining pentagons, and changing pentagon colors.

Managing Pentagons

This example uses a Pentagon class to represent a pentagon that it will draw. The main program keeps a list of Pentagon objects that it can search when the user clicks on the program’s PictureBox.

The following code shows the Pentagon class.

class Pentagon
{
    public PointF[] Points = null;
    public Color FillColor = Color.Black;

    // Constructor.
    public Pentagon(PointF[] points, Color fill_color)
    {
        Points = points;
        FillColor = fill_color;
    }

    // Draw.
    public void Draw(Graphics gr)
    {
        using (Brush brush = new SolidBrush(FillColor))
        {
            gr.FillPolygon(brush, Points);
        }
    }

    // Return true if the pentagon inclides this point.
    public bool Contains(PointF point)
    {
        return PointInPolygon(point);
    }

    #region PointInPolygon Code
    ...
    #endregion PointInPolygon Code
}

The class has a public Points array that holds the points needed to draw the pentagon. Its public FillColor value is the color that should be used to draw the pentagon.

The constructor simply initializes the Points and FillColor values.

The Draw method creates a brush with the correct FillColor and then uses it to fill the pentagon.

The Contains method calls the PointInPolygon method to determine whether the pentagon contain the indicated point.

The PointInPolygon method and some helper methods that it uses are contained in a region that I won’t show here. You can see a full description of those methods in the post Determine whether a point is inside a polygon in C#.

Defining Pentagons

When you change the Depth value in the program’s NumericUpDown control, the program uses the following code to create the Pentagon objects.

// Redraw.
private void nudDepth_ValueChanged(object sender, EventArgs e)
{
    MakePentagons();
}

// The Pentagon objects.
private List Pentagons = new List();

// Make the Pentagon objects and redraw.
private void MakePentagons()
{
    // Build a new list of Pentagons.
    int depth = (int)nudDepth.Value;
    PointF center = new PointF(
        picPentagon.ClientSize.Width / 2,
        picPentagon.ClientSize.Height / 2);
    float radius = (float)Math.Min(center.X, center.Y);
    Pentagons = new List<Pentagon>();
    MakePentagons(depth, center, radius);

    // Redraw.
    picPentagon.Refresh();
}

The NumericUpDown control’s ValueChanged event handler simply calls the MakePentagons method. That method gets the depth selected by the user. It finds the center of the PictureBox and picks a radius that will make the pentagons fit nicely.

The method then calls an overloaded version of the same method to perform the recursion and generate the pentagons. The following code shows the new method.

// Scale factor for moving to smaller pentagons.
private float size_scale = (float)(1.0
    / (2.0 * (1 + Math.Cos(Math.PI / 180 * 72))));

// Recursively generate the Pentagons.
private void MakePentagons(int depth, PointF center, float radius)
{
    // If we are done recursing, add a new Pentagon to the list.
    if (depth <= 0)
    {
        // Find the pentagon's corners.
        Pentagons.Add(new Pentagon(
            GetPentagonPoints(center, radius),
            lblSelected.BackColor));
    }
    else
    {
        // Find the smaller pentagons' centers.
        float d = radius - radius * size_scale;
        PointF[] centers = GetPentagonPoints(center, d);

        // Recursively draw the smaller pentagons.
        foreach (PointF point in centers)
        {
            MakePentagons(depth - 1, point, radius * size_scale);
        }
    }
}

The size_scale value indicates the amount by which the pentagons are scaled as the program moves into deeper levels of recursion. See the previous example for details about that value.

This version of the MakePentagons method checks the current depth of recursion. If depth is 0, the method calls the GetPentagonPoints method to define the points for a new pentagon. It passes those points and the currently selected background color into the Pentagon class’s constructor and then adds the new Pentagon object to the Pentagons list. See the previous example for information about the GetPentagonPoints method.

If depth is greater than 0, the method recursively calls itself to generate smaller pentagons.

Changing Pentagon Colors

This program keeps track of its current color by storing it in the Label control named lblSelected. When you click on one of the other color labels on the left side of the program, the following event handler executes.

// Set the selected color.
private void colorLabel_Click(object sender, EventArgs e)
{
    Label clicked_label = sender as Label;
    lblSelected.BackColor = clicked_label.BackColor;
}

This code simply sets the lblSelected control’s BackColor equal to the clicked Label control’s BackColor.

When you click on the form’s PictureBox, the following code sets the color for the selected pentagon.

// Color the clicked Pentagon.
private void picPentagon_MouseClick(object sender, MouseEventArgs e)
{
    // Get the clicked point.
    PointF point = e.Location;

    // Find the clicked Pentagon.
    foreach (Pentagon pentagon in Pentagons)
    {
        if (pentagon.Contains(point))
        {
            // Color this pentagon and redraw.
            pentagon.FillColor = lblSelected.BackColor;
            picPentagon.Refresh();
            return;
        }
    }
}

This code loops through the Pentagon objects in the Pentagons list. It calls each object’s Contains method to see if the clicked point is inside that pentagon. If the pentagon contains the point, the code sets the Pentagon object’s FillColor equal to the currently selected color, refreshes the PictureBox to redraw with the new color, and then returns.

Summary

That’s all there is to this example. The technique of using objects to represent things drawn on the program is fairly common in drawing applications. It’s easy to loop through the objects to see which one the user has clicked.

Note that if you change the program’s depth, it rebuilds its pentagon list from scratch so any previously colored pentagons are lost. You could restructure the example so it saved earlier pentagons. For example, if you previously placed a red pentagon in the upper left corner, then when you went to a deeper level of recursion, you could make that pentagon’s children start out red.

If a program has a lot of objects, it may make sense to use a different data structure to hold the objects. For example, a quadtree divides the drawing area in a hierarchical way so it can identify clicked objects more quickly. (That’s definitely overkill for this program.)

Download the example to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, fractals, graphics, recursion | Tagged , , , , , , , , , , | 5 Comments

Initialize TextBox contents for the computer’s locale in C#

[locale]

Suppose you place numeric, currency, date, and time values in TextBox controls at run time. At design time, the program may have trouble reading those values if the computer running the program is not using the same locale that you used to write the program. For example, suppose you place the value 3.14 in a TextBox and then the program tries to parse that value. On a computer running in a United States locale, the program parses the value with no problem. However, German locales use a comma for a decimal separator, so if you run the program on a computer that uses a German locale, the program throws an exception.

One way you can avoid that problem is to use simply leave the TextBox values blank and make the user fill them in using the computer’s locale formats.

If you want to present initial values, you can also use the form’s Load event handler to initialize TextBox values. As long as you use locale-aware methods to format the values, then their format should match the one used by the computer’s locale so the program can later parse the values.

This example uses the following code to display float, currency, date, and time values.

// Set initial TextBox values.
private void Form1_Load(object sender, EventArgs e)
{
    // Uncomment to test for a German locale.
    //CultureInfo culture_info = new CultureInfo("DE-de");
    //Thread.CurrentThread.CurrentCulture = culture_info;

    // Set the TextBox values.
    txtFloat.Text = (3.14).ToString();
    txtCurrency.Text = (13.37).ToString("C");
    txtDate.Text = DateTime.Today.ToShortDateString();
    txtTime.Text = DateTime.Now.ToShortTimeString();
}

The commented code sets the computer’s locale to generic German so you can test the program in that locale. The code then places values in the form’s TextBox controls. Those statements automatically use the appropriate locale so the program can later parse the values.

When you click the program’s Parse button, the following code parses the values and displays the results in the Console window.

// Parse the values.
private void btnParse_Click(object sender, EventArgs e)
{
    float number = float.Parse(txtFloat.Text);
    decimal currency = decimal.Parse(txtCurrency.Text, NumberStyles.Any);
    DateTime date = DateTime.Parse(txtDate.Text);
    DateTime time = DateTime.Parse(txtTime.Text);

    Console.WriteLine(number);
    Console.WriteLine(currency.ToString("C"));
    Console.WriteLine(date.ToShortDateString());
    Console.WriteLine(time.ToShortTimeString());
}

This code simply parses the values in the TextBox controls. The Parse methods automatically use the computer’s locale.

Download the example to experiment with it. For example, you can test it in other locales.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in formatting, internationalization, localization | Tagged , , , , , , , , | Leave a comment

Use label controls to draw a simple labeled histogram in C#

[histogram]

The example Draw a simple labeled histogram in C# draws a labeled histogram on a PictureBox in that control’s Paint event handler. It does a fair amount of work to ensure that the histogram’s bars are in their proper positions and that the labels are correctly positioned over the bars.

This example uses a slightly simpler albeit less flexible approach. It uses Label controls with blank text and different background colors to display the histogram’s bars. It then places other Label controls above the histogram bars.

When the program loads, it uses the following code to create its Label controls.

// Make some random data.
private void Form1_Load(object sender, EventArgs e)
{
    // Do not allow the user to resize the form.
    FormBorderStyle = FormBorderStyle.FixedDialog;

    Color[] Colors = new Color[] {
        Color.Red, Color.LightGreen, Color.Blue,
        Color.Pink, Color.Green, Color.LightBlue,
        Color.Orange, Color.Yellow, Color.Purple
    };

    Random rand = new Random();

    const int num_values = 10;
    int wid = picHisto.ClientSize.Width / num_values;

    for (int i = 0; i < num_values; i++)
    {
        int value = rand.Next(5, 95);

        // Make a label to be the histogram.
        Label lbl_hist = new Label();
        lbl_hist.Parent = picHisto;
        lbl_hist.BackColor = Colors[i % Colors.Length];
        lbl_hist.BorderStyle = BorderStyle.FixedSingle;
        lbl_hist.Width = wid;
        lbl_hist.Height =
            (int)(picHisto.ClientSize.Height * value / 100f);
        lbl_hist.Left = i * wid;
        lbl_hist.Top =
            picHisto.ClientSize.Height - lbl_hist.Height;

        // Make a label to display the value.
        Label lbl_value = new Label();
        lbl_value.Parent = picHisto;
        lbl_value.BackColor = Color.Transparent;
        lbl_value.Text = value.ToString();
        lbl_value.TextAlign = ContentAlignment.TopCenter;
        lbl_value.Left = lbl_hist.Left;
        lbl_value.Width = lbl_hist.Width;
        lbl_value.Height = 15;
        lbl_value.Top = lbl_hist.Top - lbl_value.Height;
    }
}

The form’s Load event handler starts by setting the form’s border style to FixedDialog. The previous example redrew its histogram and repositioned its labels whenever the form resized. Unfortunately, there’s no easy way to make controls automatically resize to occupy a percentage of the form, so this example’s technique of using control’s to display the histogram won’t work.

(Actually, there are a couple of ways that you could do this. First, you could rearrange the control’s in the form’s Resize event handler. You could also use a TableLayoutPanel to automatically resize the controls, but that would be pretty confusing to set up. An even better solution would be to make a new container control that let you specify a control’s position and size as percentages of the container’s size. If you decide to give any of those methods a try, post a comment below.)

After setting the form’s border style, the code creates an array of colors to use in the histogram bars. It then sets variable wid equal to the width that each bar should have.

The code then enters a loop to generate the histogram values. Each time through the loop, the code creates a random value and then creates a Label control to be the value’s histogram bar. It then sets the control’s properties. These are mostly self-explanatory. The only non-trivial trick here is that label’s position gives its top, so the code needs to calculate that position to make all of the bars line up on the bottom of the histogram. (When you create a new control, it’s also easy to forget to give it a parent. In that case, the control won’t appear.)

Next, the code creates a second Label control to display the histogram bar’s numeric value and sets that control’s properties. Again, this is more or less straightforward. The only challenge here is to position the Label nicely centered above its bar.

That’s all there is to this program. Because it uses controls to create the histogram, those controls automatically display the histogram and redraw themselves if needed, so you don’t need to handle the PictureBox control’s Paint event. The downsize is that this version won’t resize its histogram if you resize the form.

Download the example program to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics, mathematics | Tagged , , , , , , , , , , , | Leave a comment

Draw a simple labeled histogram in C#

[histogram]

The example Make a simple histogram in C# shows how to draw a simple histogram on a PictureBox. This example adds Label controls above the histogram’s bars.

When the program loads, it uses the following code to generate some random data and make its Label controls. This code is similar to the code used by the previous example. The new code is highlighted in blue.

private const int MIN_VALUE = 0;
private const int MAX_VALUE = 100;

private float[] DataValues = new float[10];
private Label[] Labels = new Label[10];

// Make some random data.
private void Form1_Load(object sender, EventArgs e)
{
    Random rnd = new Random();

    // Create data.
    for (int i = 0; i < DataValues.Length; i++)
    {
        DataValues[i] = rnd.Next(MIN_VALUE + 5, MAX_VALUE - 5);

        // Make a label.
        Labels[i] = new Label();
        Labels[i].Parent = picHisto;
        Labels[i].Text = DataValues[i].ToString();
        Labels[i].ForeColor = Color.Black;
        Labels[i].BackColor = Color.Transparent;
        Labels[i].AutoSize = true;
    }
}

The new code declares an array to hold the new Label controls. The form’s Load event handler creates the random data and then makes a Label to display the data. It sets the new Label control’s parent to the picHisto PictureBox control. It also sets the control’s text and colors, and sets its AutoSize property to true so the control resizes itself to fit its contents.

In many programs, this code would also position the Label and then the code would not need to move the Label later. Unfortunately, this example redraws its histogram when you resize the form, so it also needs to move the labels so they sit over their data. The following code shows the DrawHistogram method that draws the histogram and positions the Label controls. The new code that positions the labels is highlighted in blue.

// Draw a histogram.
private void DrawHistogram(Graphics gr, Color back_color,
    float[] values, int width, int height)
{
    Color[] Colors = new Color[] {
        Color.Red, Color.LightGreen, Color.Blue,
        Color.Pink, Color.Green, Color.LightBlue,
        Color.Orange, Color.Yellow, Color.Purple
    };

    gr.Clear(back_color);

    // Make a transformation to the PictureBox.
    RectangleF data_bounds =
        new RectangleF(0, 0, values.Length, MAX_VALUE);
    PointF[] points =
    {
        new PointF(0, height),
        new PointF(width, height),
        new PointF(0, 0)
    };
    Matrix transformation = new Matrix(data_bounds, points);
    gr.Transform = transformation;

    // Draw the histogram.
    using (Pen thin_pen = new Pen(Color.Black, 0))
    {
        for (int i = 0; i < values.Length; i++)
        {
            RectangleF rect = new RectangleF(i, 0, 1, values[i]);
            using (Brush the_brush =
                 new SolidBrush(Colors[i % Colors.Length]))
            {
                gr.FillRectangle(the_brush, rect);
                gr.DrawRectangle(thin_pen, rect.X, rect.Y,
                    rect.Width, rect.Height);
            }

            // Position the value's label.
            PointF[] point =
            {
                new PointF(rect.Left + rect.Width / 2f, rect.Bottom),
            };
            transformation.TransformPoints(point);
            Labels[i].Location = new Point(
                (int)point[0].X - Labels[i].Width / 2,
                (int)point[0].Y - Labels[i].Height);
        }
    }

    gr.ResetTransform();
    gr.DrawRectangle(Pens.Black, 0, 0, width - 1, height - 1);
}

The code creates a transformation to let the program draw the histogram’s bars in a natural coordinate system. The transformation then maps those coordinates onto the PictureBox.

However, the transformation does not affect the Label controls’ positions, so the program must calculate their positions explicitly. Those positions move when the form is resized, so the code must use the transformation to translate the Label controls’ positions into their locations on the form in normal form coordinates.

To position a Label, the code creates an array of PointF holding a single point. That point gives the position of the bottom center of the histogram bar’s rectangle. It uses the bottom because Y coordinates increase downward in C#. Basically that means the bottom of the rectangle is actually above its top when drawn.

Next, the program uses the transformation to transform the point array. That maps the point from natural coordinate system to the PictureBox control’s surface so point[0] now tells us where the upper middle point on the histogram is drawn on the PictureBox. The program calculates the upper left corner where it needs to position the Label to place it centered above the bar and sets the control’s Location to that position.

See the previous example and download this example to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics, mathematics | Tagged , , , , , , , , , , | 1 Comment

Rename files after their modification dates in C#

[rename files]

Lately I’ve been creating one file a day and I wanted to rename the files after their modification dates. Something like “File 04-01-20.jpg.” So I wrote this program to do that.

Enter the directory that you want to search, a prefix for the file names, and a date format. Then click Search to see a list of the changes that the program will make. Check the boxes next to the commands that you want to execute and click Rename to rename the files.


When you click Search, the following code executes.

// List the renames that we will perform.
private void btnSearch_Click(object sender, EventArgs e)
{
    clbRenames.Items.Clear();

    Directory.SetCurrentDirectory(txtDirectory.Text);

    List<string> new_names = new List<string>();
    string prefix = txtPrefix.Text;
    string format = txtFormat.Text;
    foreach (string filename in Directory.GetFiles(txtDirectory.Text))
    {
        // Get the file's creation date.
        FileInfo file_info = new FileInfo(filename);
        DateTime date = file_info.LastWriteTime.Date;
        string new_name = prefix + date.ToString(format) +
            file_info.Extension;

        int i = 1;
        while (new_names.Contains(new_name))
        {
            new_name = prefix + date.ToString(format) +
                " (" + i.ToString() + ")" + file_info.Extension;
            i++;
        }
        new_names.Add(new_name);

        clbRenames.Items.Add(file_info.Name + " -> " + new_name);
    }
}

This code sets the program’s working directory to the directory that you entered. It then makes a list of strings to hold the new file names.

The code gets the file name prefix and date format that you entered, and then loops through the files in the directory. (You could make this more elaborate by making the search look for filenames matching a pattern.)

For each file, the program creates a FileInfo object and uses that object to get the file’s modification date. It then combines the filename prefix, the date (formatted using the format that you entered), and the file’s original extension to create the file’s new name.

The code then checks the new_names list to see if the program is already planning to give that name to a different file. If that name is already taken, the code enters a loop. Each time through the loop, the code adds a number to the end of the filename as in “File 04-01-20 (1).jpg,” “File 04-01-20 (2).jpg,” and so on until it finds a name that is not already reserved. When it finds a usable name, the program adds it to the new_names list. It then adds the file’s old and new names to the program’s CheckedListBox.

Note that this method does not detect existing files that already have the new name. If the directory starts with a file named “File 04-01-20.jpg,” then the program will try to reuse that name and will probably have problems. Ideally you will only use this program once on a given directory.

When you check some rename commands and click Rename, the following code executes.

// Rename the files.
private void btnRename_Click(object sender, EventArgs e)
{
    foreach (object command in clbRenames.CheckedItems)
    {
        string rename = command as string;
        string[] separators = { " -> " };
        string[] names = rename.Split(separators,
            StringSplitOptions.RemoveEmptyEntries);
        File.Move(names[0], names[1]);
    }

    int num_files = clbRenames.CheckedItems.Count;
    MessageBox.Show("Renamed " + num_files.ToString() + " files");
    clbRenames.Items.Clear();
}

This code loops through the items in the CheckedListBox that are checked. It splits each item’s text into the corresponding file’s old and new names, and then uses File.Move to rename the file.

The method finishes by showing the number of files that it moved.

Download the example program to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in files | Tagged , , , , , , , , | 2 Comments