Programming the Food Cost Calculator

This blog post describes some of the design decisions I made, and programming challenges I encountered, while writing the food cost calculator application. This post is probably too detailed for anyone to read. Consider yourself warned.

Language

I decided to write this program in C#. I find it easy to create GUIs with Visual Studio and .NET, and I have already written database and data display code for the MediaDB and PriceTracker in C#. My original food program is in C++, so I considered using C++ .NET, but I really dislike the look of managed C++ code.

Entity Data Model

Some features have been added to the C# language since the last time I did a large project, and I wanted to try out the Entity Data Model and the built in database designer. I was limited by not owning the full version of Visual Studio and by not wanting to have to run a full database server on the client machines. I first tried to make an SQL Express database, then I tried to use the LINQ version of SQLite. After much trial and error with Visual Studio and different versions of the SQLite.NET wrappers, I was able to get it to work; but I found that it was very difficult to manipulate the displayed data independent from the database. The DataSet makes it very easy to pull some or all the rows from a table and display them on a DataGridView. Edits on the GUI are replicated in the DB automatically. But if you want to filter or sort, you have to go back to the database. If you want to have derived fields on the display you have to write a bunch of connector code. So I gave up and manually built a small SQLite database accessed with straight SQL commands.

Building the food database

I use the USDA’s SR and FNDDS databases to populate my database of foods. The SR database is the “National Nutrient Database for Standard Reference” and contains the nutrient values for thousands of raw foods. The FNDDS database is the “Food and Nutrient Database for Dietary Studies” and has some packaged foods and more human-readable food names for things in the SR database. These databases have data for more nutrients and foods than I care about, so the DB loader screen lets me pick a sub-set of nutrients/foods. For the FoodDiary program, I used the USDA SR database, so I was able to re-use some of the code from that project.

Ribbon

I wanted to try out Microsoft’s ribbon UI style in this program. It is a separate download and has to be manually added to the Visual Studio GUI builder. Once this was done, I tried to use it and found that the visual style looked like Vista instead of Windows 8. After doing some research online, it seems that Microsoft has given up development of the .NET ribbon for non WPF applications. I briefly tried the WPF version. I worked better, but getting the appearance correct was still a very manual process. If Microsoft really wants the ribbon to be adopted by third parties, they need to make it easier for programmers to use.

Data Display

My plan for the program’s main GUI is to display a searchable and sortable list of foods and a separate list of recipes. There will also be a toolbar with buttons to bring up dialogs for creating foods/recipes, editing the food scoring algorithm and editing the energy costs.

To display the list of foods and recipes I use the DataGridView. It is one of the most flexible and useful classes in .NET. It links to any List<T> and lets you display the fields of T as columns in the grid. User edits/adds/deletes are replicated back to the underlying data. There are lots of events that you can listen for, and you can make your own custom columns or cells. I have used this class in every C# project that I have built. However, I have never needed to do quite as much customization as I did for this project. For this project, I wanted to use a combo box to display the possible foods and measures. The problem that I encountered was the limitation that a column that displays data as a ComboBox has to have the same set of pull-down options for the whole grid. This is fine in a lot of cases (like month, state, sex, etc). But I wanted to do two unsupported things: One was to filter the list of foods based on what has been typed so far. The second was to display the list of measures valid for just the food on the current row.

IngredientEntryForm

To get the list of available measures to match the food on that row, I created a custom column and cell that updated its items any time the row’s food changed. I was able to use the built in ComboBoxCellEditor for this column with the slight wrinkle of converting the ComboBoxCellEditor’s string to my Measure object when the edit is complete.

The food column was more complicated. As the user types in the food column, I wanted to filter the list of foods and color the foods that the user has used before. To get this to work, I had to create a custom column, cell and cell editor. The cell editor is a panel with a text box for the user to type in and a list view showing the foods that match.

I spent a lot of time on these two columns. The .NET documentation is missing some key information about how the DataGridView works underneath. A timeline of which events fire when the user does different thing to the grid would have saved me lots of time. It also doesn’t help that there are three different data grids in .NET:

  • System.Web.UI.WebControls.DataGrid – introduced in 1.0
  • System.Windows.Forms.DataGridView – introduced in 2.0
  • System.Windows.Controls.DataGrid – introduced in 4.0

This is a problem with the whole .NET framework. They have built three full GUI systems and still support all three in the same framework. I was mostly using System.Windows.Forms.*, but some things (common enumerations, utility stuff) comes from the old 1.0 systems. Another big problem with having multiple systems in .NET is that it is difficult to find help online without finding answers for the other two systems.

Generic accessors and functional programming

Two features of C# that didn’t exist when I learned the language (.NET 2.0) are very nice. The first is generic accessors where C# will assume a hidden member and a basic accessor. Here is example code in C++, C# 2.0 and C# 3.0:

// C++
private: int myCount;
public:
int count() const { return myCount; }
void count(int value) { myCount = value; }
// C# 2.0
private int myCount;
public int Count { get { return myCount; } set { myCount = value; } }

// C# 3.0
public int Count { get; set; }

The second is the LINQ system of functional programming. I didn’t really get to use its capabilities for database queries, but I really appreciated the lambda expressions and extension methods. Here are two examples of how they save a bunch of typing:

// Old
List<NutrientAmount> setNutrients = new List<NutrientAmount>();
foreach ( Nutrient n in displayedNutrients )
  if ( n.Amount > 0 )
    setNutrients.Add(n);

double totalScore = 0;
foreach ( int key in Nutrient.allNutrients.Keys )
  totalScore += nutrientScore(key);
totalScore /= 100;

// New
var setNutrients = displayedNutrients.Where(n => n.Amount > 0).ToList();
double totalScore = Nutrient.allNutrients.Keys.Sum(key => nutrientScore(key))/100);

Searching foods

Searching for a food is a very common task in this program and I wanted it to work as well as possible. As the user types into the search box, the displayed lists updates automatically. Only foods that match at least one of the words the user has entered are displayed and the sort order takes into account how many of the words match, how many times the food has been used in a recipe, whether the food has been edited or created by the user, and the length of the name of the food. I am pretty happy with the results and can usually find the right food quickly.

Leave a Reply