Continue your completed 0.5 First Day application. Create a CSV file containing course numbers, course names, and credits. Add a button that opens the file and displays its contents in a table, then sort the table by clicking its column headings.
Goal: Create structured data in a file, load it into an application, and organize it in different ways.
Close Visual Studio. Copy the entire completed solution folder and name the backup First Day 0.5 Backup LastName. Open LastName.sln from your original working folder. Keep the working folder and solution names unchanged.
Check the shopping-list save and load buttons and the weather feature from 0.5. Close the running application before editing.
CSV means comma-separated values. Each row represents one course, and commas separate its three values. The first row names the columns.
Use Notepad to create a plain-text file. Paste the complete example, including its first row. These sample courses let you check text sorting, decimal credits, and numeric sorting.
Course Number,Course Name,Credits
CS 102,Object-Oriented Programming,4
ENG 101,College Writing,3
CS 001,Introduction to Computing,2
ART 110,"Art, Design and Technology",1.5
CAP 200,Capstone Studio,10Choose File > Save As. Browse to the working LastName folder containing LastName.sln. Set Save as type to All files, enter Courses.csv, choose UTF-8 encoding, and save. Confirm the filename is Courses.csv, not Courses.csv.txt.
Keep the header exactly as shown and in the same order. Each course must have a number, name, and nonnegative numeric credit value. Use a period for decimal credits. A course name containing a comma must be enclosed in double quotes, as shown for Art, Design and Technology.
Course numbers are text identifiers: CS 001 should keep its leading zeros. Credits are numbers: 10 should sort after 4 in ascending order. You will add a course of your own after testing the example.
In Solution Explorer, open FirstDayScenario/MainWindow.xaml and switch to its XAML source view. Select all existing XAML, from the opening Window tag through the closing Window tag. Replace it with the complete code below. Paste into MainWindow.xaml, not MainWindow.xaml.cs.
This layout keeps the 0.5 controls, increases the window height, and adds a Courses section with a load button and a sortable table.
<Window x:Class="FirstDayScenario.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:FirstDayScenario" mc:Ignorable="d" Title="MainWindow" Height="850" Width="435" MinHeight="800" MinWidth="435"> <Grid Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Top"> <Button x:Name="newStudentButton" Content="New student" Width="125" Margin="5" Click="NewStudentButton_Click" /> <Button x:Name="haveBreakfastButton" Content="Have breakfast" Width="125" Margin="5" Click="HaveBreakfastButton_Click" /> <Button x:Name="fillBackpackButton" Content="Fill backpack" Width="125" Margin="5" Click="FillBackpackButton_Click" /> </StackPanel> <StackPanel Grid.Row="1"> <TextBlock Text="Weather" FontWeight="Bold" Margin="5" /> <StackPanel Orientation="Horizontal"> <CheckBox x:Name="rainCheckBox" Content="It is raining" Width="125" Margin="5" HorizontalAlignment="Left" /> <Button x:Name="chooseClothingButton" Content="Choose clothing" Width="125" Margin="5" Click="ChooseClothingButton_Click" /> </StackPanel> </StackPanel> <StackPanel Grid.Row="4" HorizontalAlignment="Stretch" VerticalAlignment="Top"> <TextBlock Text="School Shopping List" FontWeight="Bold" Margin="5" /> <TextBox x:Name="shoppingListTextBox" Height="90" Margin="5" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" /> <StackPanel Orientation="Horizontal"> <Button x:Name="saveShoppingListButton" Width="125" Content="Save shopping list" Margin="5" Click="SaveShoppingListButton_Click" /> <Button x:Name="loadShoppingListButton" Width="125" Content="Load shopping list" Margin="5" Click="LoadShoppingListButton_Click" /> </StackPanel> </StackPanel> <StackPanel Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Top"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Longitude:" Width="125" Margin="5,5,5,0"/> <TextBlock Text="Latitude:" Width="125" Margin="5,5,5,0"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBox x:Name="longitudeTextBox" Width="125" Margin="5"/> <TextBox x:Name="latitudeTextBox" Width="125" Margin="5"/> <Button x:Name="loadWeatherButton" HorizontalAlignment="Left" Content="Load weather" Width="125" Margin="5" Click="LoadWeatherButton_Click" /> </StackPanel> <Border Height="125" BorderThickness="1" BorderBrush="Black"> <WebBrowser x:Name="BrowserControl"/> </Border> </StackPanel> <Grid Grid.Row="5" Margin="5" MinHeight="190"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBlock Text="Courses" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,15,0" /> <Button x:Name="loadCoursesButton" Content="Load courses CSV" Width="125" Margin="5" Click="LoadCoursesButton_Click" /> </StackPanel> <TextBlock Grid.Row="1" Text="Click a column heading to sort; click again to reverse." TextWrapping="Wrap" Margin="0,0,0,5" /> <DataGrid Grid.Row="2" x:Name="coursesDataGrid" AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="True"> <DataGrid.Columns> <DataGridTextColumn Header="Course number" Binding="{Binding CourseNumber}" SortMemberPath="CourseNumber" Width="110" /> <DataGridTextColumn Header="Course name" Binding="{Binding CourseName}" SortMemberPath="CourseName" Width="*" /> <DataGridTextColumn Header="Credits" Binding="{Binding Credits}" SortMemberPath="Credits" Width="65" /> </DataGrid.Columns> </DataGrid> </Grid> <TextBox Grid.Row="6" x:Name="textOutput" HorizontalAlignment="Stretch" Height="90" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" /> </Grid> </Window>
Save the file. The LoadCoursesButton_Click method has not been added yet. Complete the next task before building the solution.
In Solution Explorer, right-click References under the FirstDayScenario project and choose Add Reference. In Assemblies > Framework, find Microsoft.VisualBasic and select its checkbox. Also confirm System.Data is selected. Click OK. If either reference is already present, keep it; do not add a duplicate.
The supplied C# code uses a CSV reader provided by Microsoft.VisualBasic. Your project remains a C# project.
Right-click the FirstDayScenario project, choose Add > Class, name the file CourseCsv.cs, and click Add. Open that file, select all generated contents, and replace them with the complete code below. Include the using statements, namespace, class, and all braces. This code goes in the new CourseCsv.cs file, not MainWindow.xaml.cs.
using System;
using System.Data;
using System.Globalization;
using Microsoft.VisualBasic.FileIO;
namespace FirstDayScenario
{
// File parsing is kept separate from the window and its controls.
public static class CourseCsv
{
public static DataTable Load(string fileName)
{
DataTable courses = new DataTable();
courses.Columns.Add("CourseNumber", typeof(string));
courses.Columns.Add("CourseName", typeof(string));
courses.Columns.Add("Credits", typeof(decimal));
using (TextFieldParser parser = new TextFieldParser(fileName))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
parser.HasFieldsEnclosedInQuotes = true;
parser.TrimWhiteSpace = true;
try
{
if (parser.EndOfData)
{
throw new FormatException("The CSV file is empty.");
}
string[] header = parser.ReadFields();
if (header.Length != 3 ||
!string.Equals(header[0], "Course Number", StringComparison.OrdinalIgnoreCase) ||
!string.Equals(header[1], "Course Name", StringComparison.OrdinalIgnoreCase) ||
!string.Equals(header[2], "Credits", StringComparison.OrdinalIgnoreCase))
{
throw new FormatException("The first row must be: Course Number,Course Name,Credits");
}
while (!parser.EndOfData)
{
long line = parser.LineNumber;
string[] fields = parser.ReadFields();
decimal credits;
if (fields.Length != 3 || string.IsNullOrWhiteSpace(fields[0]) ||
string.IsNullOrWhiteSpace(fields[1]) ||
!decimal.TryParse(fields[2], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out credits) ||
credits < 0)
{
throw new FormatException("Check the course beginning near line " + line +
". Each row needs a course number, course name, and nonnegative numeric credits (for example, 3 or 1.5).");
}
courses.Rows.Add(fields[0], fields[1], credits);
}
}
catch (MalformedLineException ex)
{
throw new FormatException("Invalid CSV formatting near line " + parser.ErrorLineNumber +
". Check commas and quotation marks.", ex);
}
}
if (courses.Rows.Count == 0)
{
throw new FormatException("Add at least one course beneath the CSV header.");
}
return courses;
}
}
}The supplied reader checks the header and each row before returning the table. It handles quoted course names and stores credits as numbers so they sort correctly.
Expand MainWindow.xaml in Solution Explorer and open MainWindow.xaml.cs. At the very top of the file, with its existing using statements and above the namespace line, add any of these lines that are missing. Keep the other using statements. Do not duplicate lines already present.
using System;
using System.IO;
using System.Windows;In MainWindow.xaml.cs, find the declaration of the existing LoadWeatherButton_Click method. Place the cursor on a blank line immediately above its declaration and paste the complete method below. It belongs inside the MainWindow class, outside every other method.
Keep LoadWeatherButton_Click and its entire body. Keep the constructor, GetForecastAsync, and every existing event handler. If you already created an empty LoadCoursesButton_Click method, replace that empty method with this one instead of adding a second copy.
private void LoadCoursesButton_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
dialog.Title = "Choose a course CSV file";
dialog.Filter = "CSV files (*.csv)|*.csv";
if (dialog.ShowDialog(this) == true)
{
try
{
// Only replace the current table after the entire file is valid.
System.Data.DataTable courses = CourseCsv.Load(dialog.FileName);
this.coursesDataGrid.ItemsSource = courses.DefaultView;
this.textOutput.Text = courses.Rows.Count + " courses loaded. Click a column heading to sort.";
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is FormatException)
{
MessageBox.Show(this, ex.Message, "Unable to load courses", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}The button opens a file picker. After a valid file is read, ItemsSource connects the table to the display. If the file is invalid, a message explains the problem and the previous table stays in place. Canceling the picker leaves the current table unchanged.
Choose Build > Build Solution. If Microsoft.VisualBasic.FileIO or TextFieldParser cannot be found, check the Microsoft.VisualBasic reference. If CourseCsv cannot be found, check that CourseCsv.cs was added to the project and contains the complete supplied code. Check that the new button's Click value and C# method are both spelled LoadCoursesButton_Click.
Select the Courses.csv file saved in your solution folder and click Open. Confirm that five courses appear. The course name Art, Design and Technology should occupy one cell even though it contains a comma. The course number CS 001 should retain its zeros.
Click Course number to sort by course number, then click it again to reverse the order. Repeat with Course name. Click Credits: ascending order should be 1.5, 2, 3, 4, 10. Click it again to reverse the order.
Sorting rearranges the displayed rows. It does not change the CSV file. You do not need to write a separate sorting method; the supplied DataGrid provides column sorting.
Open Courses.csv in Notepad, add a new row with a course number, course name, and numeric credits, and save. Return to the application, click Load courses CSV, and select the same file. Confirm that six courses appear. Loading again replaces the table rather than adding duplicate rows. Edit courses in the CSV file; the displayed table is read-only.
In Notepad, temporarily change one credit value to three and save. Try loading the file. Confirm that an error message appears and the six previously loaded courses remain visible. Change the value back to a number, save, and load successfully again. Open the picker once more and click Cancel; the table should remain unchanged.
Click New student before Have breakfast or Fill backpack. Check both clothing choices and the shopping-list save and load buttons. Load weather using Chicago's coordinates from 0.5: -87.6298 for Longitude and 41.8781 for Latitude. Confirm the JSON still appears in the browser area.
Confirm the solution builds, your six-course CSV loads, all three columns sort, and the earlier features still work. Restore valid data in Courses.csv before submitting.
Close Visual Studio. Create a ZIP of the working LastName folder. Open the ZIP and confirm it includes LastName.sln, the FirstDayScenario project folder, the new CourseCsv.cs file inside that project, and Courses.csv in the solution folder.
Upload the ZIP to First Day 0.6 in the Feedback System. Review the results and correct any required issues. Submit the Feedback System submission URL to the 0.6 First Day Assignment in Canvas.