Visual C# 2005 (Windows) Guide
Sudoku: File Handling

Now that we have the basis for a Sudoku application, we could do with features allowing us to save and reopen puzzles. The file format can be relatively simple - all we need is the numbers or a zero if the cell has not yet been assigned a value. We have to decide on an order for our numbers though.

At this stage, we might bear in mind how we have iterated through the entire grid in the program so far. We have tended to use 2 loops, the second nested inside the first. The first loop counter represents the row and the second the column. We should make the file in that format if we want to process it easily.

So, the file for the puzzle shown on the previous page would be as follows,

070000000900021000000004702000003004080000500013509800005037018000000207008600340

Use the pile of numbers above to create a text file with a .sud extension so that we can write a basic open method.

Opening The File

We have to consider the best place to put our code for opening the file. We could put it in the form class or in the SudokuGrid class or place a little bit in each. I think that the best thing to do is to place the code that relates to the changing puzzle in the SudokuGrid class and the code that relates to the reading of the file in the form.

Changes To The SudokuGrid Class

Go to the code window for this class. We are going to create 2 new methods. The first is to clear the grid before adding the new values. It does seem easier to simply repeat the line of code that generates a fresh grid in the form's load event. However, this is a complex open-ended project - we may want the class to have the functionality later - particularly if we want to use the class in a console application.

public void ClearGrid()
{
   for (int i = 1; i <= 9; i++)
   {
      for (int j = 1; j <= 9; j++)
      {
         GridCell[i, j] = new GridSquare(i, j);
      }
   }
}

This code is exactly the same as the constructor method. We go through the process of creating it here in case we, for any reason, want to modify the way the grid is cleared. For example, we may want to clear a grid back to the values you started with, rather than getting rid of everything.

The next method is going to accept a string as a parameter. The string will be the entire contents of the *.sud file.

public void ReadValues(string ValueString)
{
   //To do: Add code to check format of the string

   ClearGrid();
   int numCount = 0;
   int newValue = 0;
   for (int i = 1; i <= 9; i++)
   {
      for (int j = 1; j <= 9; j++)
      {
         newValue = System.Convert.ToInt32(ValueString.Substring(numCount,1));
         if (newValue != 0)
         {
            EnterItem(i, j, newValue);
         }
         numCount++;
      }
   }
}

The comment at the top is to remind you that you will need to write some code to check that the string contains exactly 81 characters, all of which are digits. Notice that this method calls the ClearGrid() method next. The numCount variable is used to keep track of which item we are looking at in the string.

Changes To The Form

Start by adding a MenuStrip to the top left corner of the form. Click and name the first item &File. The ampersand is placed before the letter that you want to press after the ALT key when accessing the menu via the keyboard. Add &Open as the second item and, with this item selected on the menu, go to the Properties window and change its shortcut to CTRL + O which is standard in most applications.

Double click the Open menu item to go to the event handler for this event. The following code should be added,

private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog od = new OpenFileDialog();
od.Title = "Open A Sudoku Puzzle";
od.Filter = "Sudoku Puzzle|*.sud";
if (od.ShowDialog() == DialogResult.OK)
{
grid.ReadValues(File.ReadAllText(od.FileName));
UpdateDisplay();
}
}

The format of the file could be checked here too. You can save the application and test to see if it will open the file you created. If not, and you are struggling to work through the error, make sure to double check that you haven't done anything silly like add a space at the end of the file.

What's Missing?

Apart from adding code to check the file format and the format of the string passed to the ReadValues() method, there a few other things you could do here.

One thing you might consider is checking if the puzzle is empty before clearing the grid. That way the user doesn't overwrite the grid. Have a look in the MSDN for an example of how to use the MessageBox class to make prompts of the type you might expect.

Another thing you might consider is that a user may wish to enter a puzzle from a newspaper. They could do this by right clicking each square or by entering the values in the format you have used for the file. This would allow you to reuse the ReadValues() method and may save you some time when testing the application with puzzles later on.

Saving A File

It is slightly easier to save a file since there is less work to do with the grid. We start by adding another method to the SudokuGrid class. This will create the string of numbers that we want to write to the file.

public string CreateNumberString()
{
   string numString = "";
   for (int i = 1; i <= 9; i++)
   {
      for (int j = 1; j <= 9; j++)
      {
         numString += GridCell[i, j].Value;
      }
      }
   return numString;
}

Now return to the form designer and add another item to the menustrip. This item is &Save and should have the shortcut key CTRL + S. Double click to open its click event handler and add the following,

private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
   SaveFileDialog sd = new SaveFileDialog();
   sd.Title = "Save A Sudoku Puzzle";
   sd.Filter = "Sudoku Puzzle|*.sud";
   if (sd.ShowDialog() == DialogResult.OK)
   {
      File.WriteAllText(sd.FileName, grid.CreateNumberString());
   }
}

What's Missing Now?

All we have done here is add a Save method. Most applications differentiate between save and save as. To implement this feature, your application needs to keep track of which file was opened and be able to overwrite that file with new data on the user's request. It is also usual to prompt the user to save any unsaved data before closing the application.

Drag And Drop

One feature that exists in some applications is the ability to drag and drop a file onto the form and have that file loaded into the application. We can do that too.

Start by selecting the form in the designer window and then looking in the Properties window. Click to view the events and select the DragEnter event. This event takes place when the user drags something over the form.

private void frmMain_DragEnter(object sender, DragEventArgs e)
{
   if (e.Data.GetDataPresent(DataFormats.FileDrop))
   {
      e.Effect = DragDropEffects.Copy;
   }
}

This starts off the dragdrop process and means that the file will be copied. Now add a DragDrop event. This event takes place when the user releases the dragged file onto the form. We need access a data structure called a StringCollection and we need to add the statement, using System.Collections.Specialized; to the top of the form's code window. We don't need to do much with the StringCollection - you can read more about it in the MSDN.

private void frmMain_DragDrop(object sender, DragEventArgs e)
{
   DataObject myData = new DataObject();
   myData.SetData(DataFormats.FileDrop, e.Data.GetData(DataFormats.FileDrop));
   StringCollection sFiles = myData.GetFileDropList();
   grid.ReadValues(File.ReadAllText(sFiles[0]));
   UpdateDisplay();
}

Now save and test everything. You have a working application that can open and save files as well as accept a file dropped onto the form. There is a little tidying up to do. You could change the DragEnter code so that only the correct file formats can be dropped as well as make sure that validation takes place at other times in the procedures that we have added.