WinForms ADO.NET: College Park Auto-Parts
WinForms ADO.NET: College Park Auto-Parts
Introduction
ADO.NET is primarily a concept of how to create visual databases. It is neither a technology nor a programming environment. You must use a computer language, such as C#, to make a graphical application connect to an actual database in a server, such as Microsoft SQL Server. This also means that you will follow the rules of your programming language to make your application work. Besides the language, you can also use one or more libraries to get whatever behavior you want and is possible. An example library is the .NET Framework.
Practical Learning: Introducing the Application
A Database for Auto-Parts
Our application is to simulate a business that sells auto-parts. Such a business uses an application, and that application needs a database. For this application, we will use a very simple database. To keep it simple, we will use a database with just one table, a table for auto parts. Since this is a Windows Forms application that stores its records in a database, we will create that database in Microsoft SQL Server. To manage records, we will use ADO.NET. This means that we have to install the Microsft.Data.SqlClient.dll assembly.
Practical Learning: Preparing a Database
CREATE TABLE AutoParts ( AutoPartsId int identity(1, 1), PartNumber int not null, CarYear int not null, Make nvarchar(50), Model nvarchar(150), Category nvarchar(150), PartName nvarchar(255) not null, PartImage nvarchar(150), UnitPrice money not null, CONSTRAINT PK_AutoParts PRIMARY KEY (AutoPartsId) ); GO
The Vehicles Makes
A vehicle uses many parts. The primary way to identify a vehicle part is through a vehicle manufacturer, also know as the make. In our application, we will use a form to assist the user in creating names for vehicles manufacturers.
Practical Learning: Introducing Vehicles Makes
Control | Text | Name | Other Properties | |
Label | &Make: | |||
TextBox | txtMake | Modifiers: Public | ||
Button | OK | btnOK | DialogResult: OK | |
Button | Cancel | btnCancel | DialogResult: Cancel |
Form Properties
Form Property | Value |
FormBorderStyle | FixedDialog |
Text | Make Editor |
StartPosition | CenterScreen |
AcceptButton | btnOK |
CancelButton | btnCancel |
MaximizeBox | False |
MinimizeBox | False |
ShowInTaskbar | False |
The Vehicles Models
A vehicle makes only identifies a manufacturer. A vehicle make has many models. Our applicaton will need a form to assist the user in creating names for vehicles models.
Practical Learning: Introducing Vehicles Models
Control | Text | Name | Other Properties | |
Label | &Model: | |||
TextBox | txtModel | Modifiers: Public | ||
Button | OK | btnOK | DialogResult: OK | |
Button | Cancel | btnCancel | DialogResult: Cancel |
Form Properties
Form Property | Value |
FormBorderStyle | FixedDialog |
Text | Model Editor |
StartPosition | CenterScreen |
AcceptButton | btnOK |
CancelButton | btnCancel |
MaximizeBox | False |
MinimizeBox | False |
ShowInTaskbar | False |
Parts or Items Categories
A piece of hardware, whether for vehicles or other machines, must fit in a category. Our application will need a form so the user can create names for categories of parts.
Practical Learning: Introducing Parts Categories
Control | Text | Name | Other Properties | |
Label | &Category: | |||
TextBox | txtCategory | Modifiers: Public | ||
Button | OK | btnOK | DialogResult: OK | |
Button | Cancel | btnCancel | DialogResult: Cancel |
Form Properties
Form Property | Value |
FormBorderStyle | FixedDialog |
Text | Category Editor |
StartPosition | CenterScreen |
AcceptButton | btnOK |
CancelButton | btnCancel |
MaximizeBox | False |
MinimizeBox | False |
ShowInTaskbar | False |
A Class for a Collection of Store Items
Many operations of our application require that we select records. Some of the subsequent operations may be better performed using a regular collection. Such a collection may need a class so the collection can hold a list of objects. For that reason, we will need and create a class for auto-parts.
Practical Learning: Creating a Class
namespace CollegeParkAutoParts2.Models { internal class AutoPart { public int AutoPartId { get; set; } public int PartNumber { get; set; } public int CarYear { get; set; } public string? Make { get; set; } public string? Model { get; set; } public string? Category { get; set; } public string? PartName { get; set; } public string? PartImage { get; set; } public double UnitPrice { get; set; } } }
New Store Item
The main devices of our application are auto-parts. To get information about a part, we will provide a form that a user can use to provide the pieces of information about an auto-part.
Practical Learning: Creating a New Auto-Part
Control | (Name) | Text | Additional Properties | |
Label | &Part #: | |||
TextBox | txtPartNumber | |||
Button | btnSelectPicture | &Select Picture... | ||
Label | &Year: | |||
Combo Box | cbxYears | DropDownStyle: DropDownList | ||
Picture Box | pbxPartImage | BorderStyle: FixedSingle SizeMode: AutoSize |
||
Label | &Make: | |||
Combo Box | cbxMakes | DropDownStyle: DropDownList | ||
Button | btnNewMake | New M&ke... | ||
Label | M&odel | |||
Combo Box | cbxModels | DropDownStyle: DropDownList | ||
Button | btnNewModel | New Mo&del... | ||
Label | Ca&tegory: | |||
Combo Box | cbxCategories | DropDownStyle: DropDownList | ||
Button | btnNewCategory | New Cat&egory... | ||
Label | Part Na&me: | |||
Text Box | txtPartName | ScrollBars: Vertical Multiline: True |
||
Label | &Unit Price: | |||
Text Box | txtUnitPrice | TextAlign: Right | ||
Label | ____________________ | |||
Button | btnSaveAutoPart | Sa&ve Auto-Part | ||
Button | Close | btnClose | ||
Open File Dialog | ofdPictureFile |
using System.Data; using System.Data.SqlClient; using CollegeParkAutoParts2.Models; namespace CollegeParkAutoParts2 { public partial class StoreItemNew : Form { public StoreItemNew() { InitializeComponent(); } /* This function is used to reset the form. * It can be called when necessary. */ private void InitializeAutoParts() { // When this function is called, we want to reset all windows controls on the form. cbxYears.Items.Clear(); cbxMakes.Items.Clear(); cbxModels.Items.Clear(); cbxCategories.Items.Clear(); txtPartName.Text = string.Empty; txtUnitPrice.Text = string.Empty; cbxYears.Text = ""; cbxMakes.Text = ""; cbxModels.Text = ""; cbxCategories.Text = ""; txtPartName.Text = ""; txtUnitPrice.Text = "0.00"; /* Show the years in the Years combo box. * We will consider the cars made in the last 20 years. */ for (int year = DateTime.Today.Year + 1; year >= DateTime.Today.Year - 20; year--) cbxYears.Items.Add(year); /* Normally, we will specify to the user what number to put in the part number. * As an option, we will provide a default random number for the auto-part.*/ Random rndNumber = new(); txtPartNumber.Text = rndNumber.Next(100000, 999999).ToString(); // Prepare a data set object for the parts DataSet dsParts = new DataSet("PartsSet"); // Establish a connection to the database using (SqlConnection scCollegeParkAutoParts = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { /* Although the main table of our database contains many parts, when the * New Store Item form opens, we will populate the Makes combo box with * all the names of vehicles manufacturers that currently exist in the * database. We will also populate the Categories combo box with the * Categories that the user might have created (assuming the user had * already created a few records). * As a result, for this operation, from the AutoParts table, we will * need just the Make, the Model, and the Category fields. */ SqlCommand cmdParts = new SqlCommand("SELECT Make, " + " Model, " + " Category " + "FROM AutoParts;", scCollegeParkAutoParts); cmdParts.CommandType = CommandType.Text; scCollegeParkAutoParts.Open(); // Create a data adapter that will get the values from the table SqlDataAdapter sdaParts = new SqlDataAdapter(cmdParts); // Store those values in the data set sdaParts.Fill(dsParts); // Create an auto part object AutoPart? StoreItem = null; // Create an empty list of auto parts List<AutoPart> lstAutoParts = new List<AutoPart>(); // Check each record from the (only) table in the data set foreach (DataRow row in dsParts.Tables[0].Rows) { StoreItem = new(); StoreItem.PartNumber = 0; StoreItem.CarYear = 1000; StoreItem.Make = row[0].ToString(); StoreItem.Model = row[1].ToString(); StoreItem.Category = row[2].ToString(); StoreItem.PartName = string.Empty; StoreItem.PartImage = string.Empty; StoreItem.UnitPrice = 0.00; // Once the record is ready, store it in the collection variable lstAutoParts.Add(StoreItem); } // To avoid duplicate values in the combo boxes, // we will use collection classes List<string> lstMakes = new List<string>(); List<string> lstCategories = new List<string>(); // Check the list of makes foreach (AutoPart part in lstAutoParts) { // If the list doesn't yet contain the make, add it if (!lstMakes.Contains(part.Make!)) lstMakes.Add(part.Make!); } // Once we have the list of makes, // put them in the Make combo box foreach (string strMake in lstMakes) cbxMakes.Items.Add(strMake); foreach (AutoPart part in lstAutoParts) { if (!lstCategories.Contains(part.Category!)) lstCategories.Add(part.Category!); } foreach (string strCategory in lstCategories) cbxCategories.Items.Add(strCategory); } lblPictureFile.Text = "Generic.png"; Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); } private void StoreItemNew_Load(object sender, EventArgs e) { InitializeAutoParts(); } private void btnSelectPicture_Click(object sender, EventArgs e) { FileInfo fiFilePath; if (ofdPictureFile.ShowDialog() == DialogResult.OK) { fiFilePath = new(ofdPictureFile.FileName); pbxPartImage.Image = Image.FromFile(ofdPictureFile.FileName); } else { fiFilePath = new(@"E:\College Park Auto-Parts\Generic.png"); pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); } lblPictureFile.Text = fiFilePath.Name; Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } private void cbxMakes_SelectedIndexChanged(object sender, EventArgs e) { cbxModels.Items.Clear(); DataSet dsParts = new("PartsSet"); using (SqlConnection scCollegeParkAutoParts = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdParts = new SqlCommand("SELECT CarYear, " + " Make, " + " Model " + "FROM AutoParts;", scCollegeParkAutoParts); scCollegeParkAutoParts.Open(); SqlDataAdapter sdaParts = new SqlDataAdapter(cmdParts); sdaParts.Fill(dsParts); AutoPart? StoreItem = null; List<AutoPart> lstAutoParts = new(); foreach (DataRow row in dsParts.Tables[0].Rows) { StoreItem = new AutoPart(); StoreItem.PartNumber = 0; StoreItem.CarYear = int.Parse(row[0].ToString()!); StoreItem.Make = row[1].ToString(); StoreItem.Model = row[2].ToString(); StoreItem.Category = string.Empty; StoreItem.PartName = string.Empty; StoreItem.PartImage = string.Empty; StoreItem.UnitPrice = 0; lstAutoParts.Add(StoreItem); } List<string> lstModels = new List<string>(); foreach (AutoPart part in lstAutoParts) { if ((part.CarYear == int.Parse(cbxYears.Text)) && (part.Make == cbxMakes.Text)) { if (!lstModels.Contains(part.Model!)) lstModels.Add(part.Model!); } } foreach (string strModel in lstModels) cbxModels.Items.Add(strModel); } } private void btnNewMake_Click(object sender, EventArgs e) { MakeEditor editor = new MakeEditor(); if (editor.ShowDialog() == DialogResult.OK) { if (editor.txtMake.Text.Length > 0) { string strMake = editor.txtMake.Text; // Make sure the category is not yet in the list if (cbxMakes.Items.Contains(strMake)) { MessageBox.Show(strMake + " is already in the list.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { // Since this is a new category, add it to the combox box cbxMakes.Items.Add(strMake); } cbxMakes.Text = strMake; } } } private void btnNewModel_Click(object sender, EventArgs e) { ModelEditor editor = new ModelEditor(); if (editor.ShowDialog() == DialogResult.OK) { if (editor.txtModel.Text.Length > 0) { string strModel = editor.txtModel.Text; // Make sure the category is not yet in the list if (cbxModels.Items.Contains(strModel)) { MessageBox.Show(strModel + " is already in the list.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { // Since this is a new category, add it to the combox box cbxModels.Items.Add(strModel); } cbxModels.Text = strModel; } } } private void btnNewCategory_Click(object sender, EventArgs e) { CategoryEditor editor = new CategoryEditor(); if (editor.ShowDialog() == DialogResult.OK) { if (editor.txtCategory.Text.Length > 0) { string strCategory = editor.txtCategory.Text; // Make sure the category is not yet in the list if (cbxCategories.Items.Contains(strCategory)) { MessageBox.Show(strCategory + " is already in the list.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { // Since this is a new category, add it to the combo box cbxCategories.Items.Add(strCategory); } cbxCategories.Text = strCategory; } } } private void btnSaveAutoPart_Click(object sender, EventArgs e) { double unitPrice = 0.00; if(string.IsNullOrEmpty(txtPartNumber.Text)) { MessageBox.Show("You must enter the part number of the store item.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if(string.IsNullOrEmpty(txtPartName.Text) ) { MessageBox.Show("You must enter the name of the store item.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if(string.IsNullOrEmpty(txtUnitPrice.Text) ) { MessageBox.Show("You must enter the unit price of the part.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } try { unitPrice = double.Parse(txtUnitPrice.Text); } catch (FormatException) { MessageBox.Show("Invalid Unit Price.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } using (SqlConnection cnnNewPart = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdAutoPart = new SqlCommand("INSERT INTO AutoParts(" + "PartNumber, CarYear, Make, Model, Category, " + "PartName, PartImage, UnitPrice) VALUES(" + txtPartNumber.Text + ", " + cbxYears.Text + ", N'" + cbxMakes.Text + "', N'" + cbxModels.Text + "', N'" + cbxCategories.Text + "', N'" + txtPartName.Text + "', N'" + lblPictureFile.Text + "', " + unitPrice + ");", cnnNewPart); cnnNewPart.Open(); cmdAutoPart.ExecuteNonQuery(); InitializeAutoParts(); MessageBox.Show("The new part has been added.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void btnClose_Click(object sender, EventArgs e) { Close(); } } }
A database requires maintenance, which consists of locating records, then editing or updaing them. We will use a form to assist the user in taking care of that.
Practical Learning: Editing/Updating a Record
Control | (Name) | Text | |
Button | btnFind | &Find | |
Text Box | txtMake | ||
Text Box | txtModel | ||
Text Box | txtCategory | ||
Button | btnUpdateAutoPart | Up&date Auto-Part |
using CollegeParkAutoParts2.Models; using System.Data.SqlClient; using System.Data; namespace CollegeParkAutoParts21 { public partial class StoreItemEditor : Form { public StoreItemEditor() { InitializeComponent(); } private void InitializeAutoPart() { txtMake.Text = string.Empty; txtModel.Text = string.Empty; txtCategory.Text = string.Empty; txtPartName.Text = string.Empty; txtUnitPrice.Text = string.Empty; txtPartNumber.Text = string.Empty; for (int year = DateTime.Today.Year + 1; year >= DateTime.Today.Year - 20; year--) cbxYears.Items.Add(year); lblPictureFile.Text = "Generic.png"; pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } private void StoreItemEditor_Load(object sender, EventArgs e) { InitializeAutoPart(); } private void btnFind_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(txtPartNumber.Text)) { MessageBox.Show("You must enter a (valid) number for an auto-part.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } bool foundAutoPart = false; DataSet dsParts = new("AutoPartsSet"); using (SqlConnection scCollegeParkAutoParts = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdParts = new SqlCommand("SELECT PartNumber, " + " CarYear, " + " Make, " + " Model, " + " Category, " + " PartName, " + " PartImage, " + " UnitPrice " + "FROM AutoParts;", scCollegeParkAutoParts); scCollegeParkAutoParts.Open(); SqlDataAdapter sdaParts = new SqlDataAdapter(cmdParts); sdaParts.Fill(dsParts); foreach (DataRow drPart in dsParts.Tables[0].Rows) { if(drPart[0].ToString()!.Equals(txtPartNumber.Text)) { foundAutoPart = true; cbxYears.Text = drPart[1].ToString(); txtMake.Text = drPart[2].ToString(); txtModel.Text = drPart[3].ToString(); txtCategory.Text = drPart[4].ToString(); txtPartName.Text = drPart[5].ToString(); lblPictureFile.Text = drPart[6].ToString(); pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\" + drPart[6].ToString()); txtUnitPrice.Text = double.Parse(drPart[7].ToString()!).ToString("F"); Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } } } if (foundAutoPart == false) { MessageBox.Show("There is no auto-part with that number in our records.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); lblPictureFile.Text = @"E:\College Park Auto-Parts\Generic.png"; pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); } Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } private void btnSelectPicture_Click(object sender, EventArgs e) { FileInfo fiFilePath; if (ofdPictureFile.ShowDialog() == DialogResult.OK) { fiFilePath = new(ofdPictureFile.FileName); pbxPartImage.Image = Image.FromFile(ofdPictureFile.FileName); } else { fiFilePath = new(@"E:\College Park Auto-Parts\Generic.png"); pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); } lblPictureFile.Text = fiFilePath.Name; Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } private void btnUpdateAutoPart_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(txtPartNumber.Text)) { MessageBox.Show("You must enter the part number of the store item.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } using (SqlConnection cnnNewPart = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdAutoPart = new SqlCommand("UPDATE AutoParts SET CarYear = " + cbxYears.Text + " WHERE PartNumber = " + txtPartNumber.Text + ";" + "UPDATE AutoParts SET Make = N'" + txtMake.Text + "' WHERE PartNumber = " + txtPartNumber.Text + ";" + "UPDATE AutoParts SET Model = N'" + txtModel.Text + "' WHERE PartNumber = " + txtPartNumber.Text + ";" + "UPDATE AutoParts SET Category = N'" + txtCategory.Text + "' WHERE PartNumber = " + txtPartNumber.Text + ";" + "UPDATE AutoParts SET PartName = N'" + txtPartName.Text + "' WHERE PartNumber = " + txtPartNumber.Text + ";" + "UPDATE AutoParts SET PartImage = N'" + lblPictureFile.Text + "' WHERE PartNumber = " + txtPartNumber.Text + ";" + "UPDATE AutoParts SET UnitPrice = " + txtUnitPrice.Text + " WHERE PartNumber = " + txtPartNumber.Text + ";", cnnNewPart); cnnNewPart.Open(); cmdAutoPart.ExecuteNonQuery(); InitializeAutoPart(); MessageBox.Show("The information about the auto part has been updated in the database.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void btnClose_Click(object sender, EventArgs e) { Close(); } } }
Deleting Records
A valuable operation of data management is to delete uncessairy records. We will use a form to assist the user with this type of operation.
Practical Learning: Removing Records
Control | (Name) | Text | Additional Properties | |
Text Box | txtYear | TextAlign: Right | ||
Button | btnDeleteAutoPart | Delete Auto-Part |
using System.Data; using System.Data.SqlClient; namespace CollegeParkAutoParts2 { public partial class StoreItemDelete : Form { public StoreItemDelete() { InitializeComponent(); } private void InitializeAutoPart() { txtYear.Text = string.Empty; txtMake.Text = string.Empty; txtModel.Text = string.Empty; txtCategory.Text = string.Empty; txtPartName.Text = string.Empty; txtUnitPrice.Text = string.Empty; txtPartNumber.Text = string.Empty; lblPictureFile.Text = "Generic.png"; pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } private void StoreItemDelete_Load(object sender, EventArgs e) { InitializeAutoPart(); } private void btnFind_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(txtPartNumber.Text)) { MessageBox.Show("You must enter a (valid) number for an auto-part.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } bool foundAutoPart = false; DataSet dsParts = new("AutoPartsSet"); using (SqlConnection scCollegeParkAutoParts = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdParts = new SqlCommand("SELECT PartNumber, " + " CarYear, " + " Make, " + " Model, " + " Category, " + " PartName, " + " PartImage, " + " UnitPrice " + "FROM AutoParts;", scCollegeParkAutoParts); scCollegeParkAutoParts.Open(); SqlDataAdapter sdaParts = new SqlDataAdapter(cmdParts); sdaParts.Fill(dsParts); foreach (DataRow drPart in dsParts.Tables[0].Rows) { if (drPart[0].ToString()!.Equals(txtPartNumber.Text)) { foundAutoPart = true; txtYear.Text = drPart[1].ToString(); txtMake.Text = drPart[2].ToString(); txtModel.Text = drPart[3].ToString(); txtCategory.Text = drPart[4].ToString(); txtPartName.Text = drPart[5].ToString(); lblPictureFile.Text = drPart[6].ToString(); pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\" + drPart[6].ToString()); txtUnitPrice.Text = double.Parse(drPart[7].ToString()!).ToString("F"); Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } } } if (foundAutoPart == false) { MessageBox.Show("There is no auto-part with that number in our records.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); lblPictureFile.Text = @"E:\College Park Auto-Parts\Generic.png"; pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); } Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } private void btnDeleteAutoPart_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(txtPartNumber.Text)) { MessageBox.Show("You must enter the part number of the store item.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } using (SqlConnection cnnNewPart = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdAutoPart = new SqlCommand("DELETE FROM AutoParts " + "WHERE PartNumber = " + txtPartNumber.Text + ";", cnnNewPart); cnnNewPart.Open(); cmdAutoPart.ExecuteNonQuery(); InitializeAutoPart(); MessageBox.Show("The part has been deleted from the database.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void btnClose_Click(object sender, EventArgs e) { Close(); } } }
A Central Form
A typical application uses a central object from which people can access the objects. For that object, we will the first form that was created in the project.
Practical Learning: Finalizing the Application
Control | (Name) | Text | Other Properties | ||
Label | College Park Auto-Parts | Font: Times New Roman, 20.25pt, style=Bold ForeColor: Blue |
|||
Panel | BackColor: Black Height: 2 |
||||
GroupBox | Part Identification | ||||
TreeView | tvwAutoParts | ImageList: AutoPartsImages | |||
GroupBox | Available Parts | ||||
ListView | lvwAutoParts | FullRowSelect: True GridLines: True View: Details |
|||
Columns | (Name) | Text | TextAlign | Width | |
colAvailablePartNumber | Part # | ||||
colAvailablePartName | Part Name/Description | 300 | |||
colAvailableUnitPrice | Unit Price | Right | 80 | ||
GroupBox | Customer Order - Selected Parts | ||||
Label | Part # | ||||
Label | Part Name | ||||
Label | Unit Price | ||||
Label | Qty | ||||
Label | Sub Total | ||||
TextBox | txtPartNumber | ||||
TextBox | txtPartName | ||||
TextBox | txtUnitPrice | TextAlign: Right | |||
TextBox | txtQuantity | TextAlign: Right | |||
TextBox | txtSubTotal | TextAlign: Right | |||
Button | btnAdd | Add/Select | |||
ListView | lvwSelectedParts | FullRowSelect: True GridLines: True View: Details |
|||
Columns | (Name) | Text | TextAlign | Width | |
colSelectedPartNumber | Part # | 45 | |||
colSelectedPartName | Part Name/Description | 274 | |||
colSelectedUnitPrice | Unit Price | Right | 58 | ||
colSelectedQuantity | Qty | Right | 28 | ||
colSelectedSubTotal | Sub-Total | Right | 58 | ||
GroupBox | Order Summary | ||||
Button | btnNewAutoPart | New Au&to Part... | |||
Button | btnUpdateAutoPart | Update Auto Part... | |||
Label | Selected Parts Total: | ||||
TextBox | txtSelectedPartsTotal | 0.00 | TextAlign: Right | ||
Label | Tax Rate: | ||||
TextBox | txtTaxRate | 7.75 | TextAlign: Right | ||
Label | % | ||||
Label | Tax Amount: | ||||
TextBox | txtTaxAmount | TextAlign: Right | |||
Label | Order Total: | ||||
TextBox | txtOrderTotal | 0.00 | TextAlign: Right | ||
Button | btnDeleteAutoPart | Delete Auto-Part ... | |||
Button | btnClose | Close |
using System.Data; using System.Data.SqlClient; using CollegeParkAutoParts2.Models; namespace CollegeParkAutoParts2 { public partial class CollegeParkAutoParts : Form { public CollegeParkAutoParts() { InitializeComponent(); } // This function is used to reset the form void InitializeAutoParts() { // When the form must be reset, removes all nodes from the tree view tvwAutoParts.Nodes.Clear(); // Create the root node of the tree view TreeNode nodRoot = tvwAutoParts.Nodes.Add("College Park Auto-Parts", "College Park Auto-Parts", 0, 1); /* Add the cars years to the tree view. * Our application will deal only with the cars in the last 21 years. */ for (int years = DateTime.Today.Year + 1; years >= DateTime.Today.Year - 20; years--) nodRoot.Nodes.Add(years.ToString(), years.ToString(), 2, 3); // Select the root node tvwAutoParts.SelectedNode = nodRoot; // Expand the root node tvwAutoParts.ExpandAll(); // Create an empty data set DataSet dsParts = new DataSet("PartsSet"); // Create a connection to the database using (SqlConnection scCollegeParkAutoParts = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { // Use a command to specify what action we want to take SqlCommand cmdParts = new SqlCommand("SELECT CarYear, " + " Make, " + " Model, " + " Category " + "FROM AutoParts;", scCollegeParkAutoParts); scCollegeParkAutoParts.Open(); SqlDataAdapter sdaParts = new(cmdParts); sdaParts.Fill(dsParts); AutoPart? StoreItem = null; List<AutoPart> lstAutoParts = new(); foreach (DataRow drPart in dsParts.Tables[0].Rows) { StoreItem = new AutoPart(); StoreItem.PartNumber = 0; StoreItem.CarYear = int.Parse(drPart[0].ToString()!); StoreItem.Make = drPart[1].ToString(); StoreItem.Model = drPart[2].ToString(); StoreItem.Category = drPart[3].ToString(); StoreItem.PartName = string.Empty; StoreItem.UnitPrice = 0; lstAutoParts.Add(StoreItem); } foreach (TreeNode nodYear in nodRoot.Nodes) { List<string> lstMakes = new List<string>(); foreach (AutoPart part in lstAutoParts) { if (nodYear.Text == part.CarYear.ToString()) { if (!lstMakes.Contains(part.Make!)) lstMakes.Add(part.Make!); } } foreach (string strMake in lstMakes) nodYear.Nodes.Add(strMake, strMake, 4, 5); } foreach (TreeNode nodYear in nodRoot.Nodes) { foreach (TreeNode nodMake in nodYear.Nodes) { List<string> lstModels = new(); foreach (AutoPart part in lstAutoParts) { if ((nodYear.Text == part.CarYear.ToString()) && (nodMake.Text == part.Make)) { if (!lstModels.Contains(part.Model!)) lstModels.Add(part.Model!); } } foreach (string strModel in lstModels) nodMake.Nodes.Add(strModel, strModel, 6, 7); } } foreach (TreeNode nodYear in nodRoot.Nodes) { foreach (TreeNode nodMake in nodYear.Nodes) { foreach (TreeNode nodModel in nodMake.Nodes) { var lstCategories = new List<string>(); foreach (AutoPart part in lstAutoParts) { if ((nodYear.Text == part.CarYear.ToString()) && (nodMake.Text == part.Make) && (nodModel.Text == part.Model)) { if (!lstCategories.Contains(part.Category!)) lstCategories.Add(part.Category!); } } foreach (string strCategory in lstCategories) nodModel.Nodes.Add(strCategory, strCategory, 8, 9); } } } } lvwSelectedParts.Items.Clear(); lvwAvailableParts.Items.Clear(); txtPartName.Text = string.Empty; txtQuantity.Text = string.Empty; txtSubTotal.Text = string.Empty; txtUnitPrice.Text = string.Empty; txtTaxAmount.Text = string.Empty; txtOrderTotal.Text = string.Empty; txtPartNumber.Text = string.Empty; txtSelectedPartsTotal.Text = string.Empty; pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); } private void CollegeParkAutoParts_Load(object sender, EventArgs e) { InitializeAutoParts(); } private void tvwAutoParts_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { TreeNode nodClicked = e.Node; if (nodClicked.Level == 4) lvwAvailableParts.Items.Clear(); DataSet dsParts = new DataSet("PartsSet"); using (SqlConnection scCollegeParkAutoParts = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdParts = new SqlCommand("SELECT PartNumber, " + " CarYear, " + " Make, " + " Model, " + " Category, " + " PartName, " + " PartImage, " + " UnitPrice " + "FROM AutoParts; ", scCollegeParkAutoParts); scCollegeParkAutoParts.Open(); SqlDataAdapter sdaParts = new SqlDataAdapter(cmdParts); sdaParts.Fill(dsParts); AutoPart? storeItem = null; List<AutoPart> lstAutoParts = new List<AutoPart>(); foreach (DataRow drPart in dsParts.Tables[0].Rows) { storeItem = new AutoPart(); storeItem.PartNumber = int.Parse(drPart[0].ToString()!); storeItem.CarYear = int.Parse(drPart[1].ToString()!); storeItem.Make = drPart[2].ToString(); storeItem.Model = drPart[3].ToString(); storeItem.Category = drPart[4].ToString(); storeItem.PartName = drPart[5].ToString(); storeItem.PartImage = drPart[6].ToString(); storeItem.UnitPrice = double.Parse(drPart[7].ToString()!); lstAutoParts.Add(storeItem); } try { foreach (AutoPart part in lstAutoParts) { if ((part.Category == nodClicked.Text) && (part.Model == nodClicked.Parent.Text) && (part.Make == nodClicked.Parent.Parent.Text) && (part.CarYear.ToString() == nodClicked.Parent.Parent.Parent.Text)) { ListViewItem lviAutoPart = new ListViewItem(part.PartNumber.ToString()); lviAutoPart.SubItems.Add(part.PartName); lviAutoPart.SubItems.Add(part.UnitPrice.ToString("F")); lvwAvailableParts.Items.Add(lviAutoPart); } } } catch (NullReferenceException) { } } } private void lvwAvailableParts_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e) { bool pictureFound = false; DataSet dsParts = new("PartsSet"); using (SqlConnection scCollegeParkAutoParts = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdParts = new SqlCommand("SELECT PartNumber, " + " PartImage " + "FROM AutoParts;", scCollegeParkAutoParts); scCollegeParkAutoParts.Open(); SqlDataAdapter sdaParts = new SqlDataAdapter(cmdParts); sdaParts.Fill(dsParts); AutoPart? storeItem = null; List<AutoPart> lstAutoParts = new(); foreach (DataRow drPart in dsParts.Tables[0].Rows) { storeItem = new AutoPart(); storeItem.PartNumber = int.Parse(drPart[0].ToString()!); storeItem.CarYear = 0; storeItem.Make = string.Empty; storeItem.Model = string.Empty; storeItem.Category = string.Empty; storeItem.PartName = string.Empty; storeItem.PartImage = drPart[1].ToString(); storeItem.UnitPrice = 0.00; lstAutoParts.Add(storeItem); } foreach (AutoPart part in lstAutoParts) { if (part.PartNumber == long.Parse(e.Item!.SubItems[0].Text)) { pictureFound = true; pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\" + part.PartImage!); break; } } } if (pictureFound == false) { pbxPartImage.Image = Image.FromFile(@"E:\College Park Auto-Parts\Generic.png"); } Width = pbxPartImage.Right + 40; Height = pbxPartImage.Bottom + 75; } private void lvwAvailableParts_DoubleClick(object sender, EventArgs e) { ListViewItem lviAutoPart = lvwAvailableParts.SelectedItems[0]; if ((lvwAvailableParts.SelectedItems.Count == 0) || (lvwAvailableParts.SelectedItems.Count > 1)) return; txtPartNumber.Text = lviAutoPart.Text; txtPartName.Text = lviAutoPart.SubItems[1].Text; txtUnitPrice.Text = lviAutoPart.SubItems[2].Text; txtQuantity.Text = "1"; txtSubTotal.Text = lviAutoPart.SubItems[2].Text; txtQuantity.Focus(); } private void txtUnitPrice_Leave(object sender, EventArgs e) { double subTotal; double unitPrice = 0D; double quantity = 0.00d; try { unitPrice = double.Parse(txtUnitPrice.Text); } catch (FormatException) { MessageBox.Show("Invalid Unit Price!", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } try { quantity = int.Parse(txtQuantity.Text); } catch (FormatException) { MessageBox.Show("Invalid Quandtity!", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } subTotal = unitPrice * quantity; txtSubTotal.Text = subTotal.ToString("F"); } internal void CalculateOrder() { // Calculate the current total order and update the order double partsTotal = 0.00D; double taxRate = 0.00D; double taxAmount, orderTotal; if (string.IsNullOrEmpty(txtTaxRate.Text)) txtTaxRate.Text = "7.25"; foreach (ListViewItem lviPart in lvwSelectedParts.Items) { ListViewItem.ListViewSubItem SubItem = lviPart.SubItems[4]; partsTotal += double.Parse(SubItem.Text); } try { taxRate = double.Parse(txtTaxRate.Text) / 100; } catch (FormatException) { MessageBox.Show("Invalid Tax Rate", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } taxAmount = partsTotal * taxRate; orderTotal = partsTotal + taxAmount; txtSelectedPartsTotal.Text = partsTotal.ToString("F"); txtTaxAmount.Text = taxAmount.ToString("F"); txtOrderTotal.Text = orderTotal.ToString("F"); } private void txtPartNumber_Leave(object sender, EventArgs e) { bool found = false; DataSet dsParts = new("PartsSet"); using (SqlConnection scCollegeParkAutoParts = new SqlConnection("Data Source=(local);" + "Database=CollegeParkAutoParts2;" + "Integrated Security=SSPI;")) { SqlCommand cmdParts = new SqlCommand("SELECT PartNumber, " + " PartName, " + " UnitPrice " + "FROM AutoParts;", scCollegeParkAutoParts); scCollegeParkAutoParts.Open(); SqlDataAdapter sdaParts = new SqlDataAdapter(cmdParts); sdaParts.Fill(dsParts); /* After the user had entered a part number, * check the whole list of parts */ foreach (DataRow drPart in dsParts.Tables[0].Rows) { // If you find a part that holds the number the user had entered if (drPart[0].ToString()!.Equals(txtPartNumber.Text)) { // Show the corresponding part name and unit price txtPartName.Text = drPart[1].ToString()!; txtUnitPrice.Text = double.Parse(drPart[2].ToString()!).ToString("F"); if (string.IsNullOrEmpty(txtQuantity.Text)) txtQuantity.Text = "1"; txtSubTotal.Text = double.Parse(drPart[2].ToString()!).ToString("F"); // Give focus to the quantity in case the user was to increase it txtQuantity.Focus(); // And update the flag that specifies that the part has been found found = true; break; } // If the part number was not found, check the next } // If no part has that number, the found flag is marked as false } // If no part with that number was found... if (found == false) { // Since no part with that number was found, // reset the text boxes txtPartName.Text = ""; txtUnitPrice.Text = "0.00"; txtQuantity.Text = "0"; txtSubTotal.Text = "0.00"; // Let the user know that the part number that // was entered is not in the list MessageBox.Show("There is no part with that number.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); } } private void btnAdd_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(txtPartNumber.Text)) { MessageBox.Show("There is no part to be added to the order.", "College Park Auto-Parts", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } ListViewItem lviSelectedPart = new ListViewItem(txtPartNumber.Text); lviSelectedPart.SubItems.Add(txtPartName.Text); lviSelectedPart.SubItems.Add(txtUnitPrice.Text); lviSelectedPart.SubItems.Add(txtQuantity.Text); lviSelectedPart.SubItems.Add(txtSubTotal.Text); lvwSelectedParts.Items.Add(lviSelectedPart); txtPartNumber.Text = txtPartName.Text = txtUnitPrice.Text = txtQuantity.Text = txtSubTotal.Text = string.Empty; CalculateOrder(); } private void lvwSelectedParts_DoubleClick(object sender, EventArgs e) { ListViewItem lviSelectedPart = lvwSelectedParts.SelectedItems[0]; if ((lvwSelectedParts.SelectedItems.Count == 0) || (lvwSelectedParts.SelectedItems.Count > 1)) return; txtPartNumber.Text = lviSelectedPart.Text; txtPartName.Text = lviSelectedPart.SubItems[1].Text; txtUnitPrice.Text = lviSelectedPart.SubItems[2].Text; txtQuantity.Text = lviSelectedPart.SubItems[3].Text; txtSubTotal.Text = lviSelectedPart.SubItems[4].Text; lvwSelectedParts.Items.Remove(lviSelectedPart); } private void btnNewAutoPart_Click(object sender, EventArgs e) { StoreItemNew sin = new StoreItemNew(); sin.ShowDialog(); InitializeAutoParts(); } private void btnUpdateAutoPart_Click(object sender, EventArgs e) { StoreItemEditor sie = new StoreItemEditor(); sie.ShowDialog(); InitializeAutoParts(); } private void btnDeleteAutoPart_Click(object sender, EventArgs e) { StoreItemDelete sid = new StoreItemDelete(); sid.ShowDialog(); InitializeAutoParts(); } private void btnClose_Click(object sender, EventArgs e) { Close(); } } }
Executing and Testing the Application
After creating and application, you can execute it. You can then test it with sample values.
Practical Learning: Executing and Testing the Application
Part # | Year | Make | Model | Category | Part Name | Unit Price | Part Image |
393795 | 2015 | Buick | Regal | Alternators & Generators | DB Electrical Alternator | 218.74 | 928037 |
928374 | 2018 | Chevrolet | Express 3500 | Shocks, Struts & Suspension | Suspension Kit (Front; with 3 Groove Pitman Arm) | 142.44 | 304031 |
730283 | 2020 | Jeep | Wrangler Unlimited Sahara | Oil Filters | Hydraulic Cylinder Timing Belt Tensioner | 14.15 | 730283 |
290741 | 2015 | Ford | F-150 XL 3.5L V6 Flex Regular Cab 2 Full-Size Doors | Shocks, Struts & Suspension | Front Strut and Coil Spring Assembly - Set of 2 | 245.68 | 290741 |
740248 | 2013 | Chevrolet | Equinox | Bearings & Seals | Wheel hub bearing Assembly | 99.95 | 740248 |
283759 | 2012 | Dodge | Charger 3.6L | Starters | DB Electrical SND0787 Starter | 212.58 | 283759 |
799428 | 2012 | Cadillac | XTS | Bearings & Seals | Front/Rear Wheel Hub Bearing Assembly 5 Lugs w/ABS | 79.97 | 799428 |
648203 | 2018 | Honda | CRV | Alternators | Alternator | 202.47 | 593804 |
502853 | 2014 | GMC | Terrain | Bearings & Seals | Wheel Hub Bearing Assembly | 48.85 | 927944 |
520384 | 2020 | Jeep | Wrangler Unlimited Sahara | Drum Brake | Rear Dynamic Friction Company True-Arc Brake Shoes | 42.22 | 520384 |
727394 | 2018 | Toyota | Corolla SE 1.8L L4 Gas | Alternators | DB Electrical 400-40169 Alternator Compatible With/Replacement For 125 Internal Fan Type Decoupler Pulley Type Internal Regulator CW Rotation | 215.84 | 727394 |
927944 | 2017 | Chevrolet | Equinox | Bearings & Seals | Wheel Hub Bearing Assembly | 48.85 | 927944 |
749471 | 2019 | Toyota | Prius | Shocks, Struts & Suspension | 2-Piece Suspension Strut and Coil Spring Kit | 299.97 | 593024 |
927307 | 2014 | Buick | Regal | Alternators & Generators | DB Electrical Alternator | 218.74 | 928037 |
304031 | 2017 | Chevrolet | Express 2500 | Shocks, Struts & Suspension | Suspension Kit (Front; with 3 Groove Pitman Arm) | 142.44 | 304031 |
497249 | 2013 | GMC | Sierra 1500 | Drum Brake | ACDelco Gold 17960BF1 Bonded Rear Drum Brake Shoe Set | 58.92 | 497249 |
973947 | 2012 | Honda | Accord | Brake Kits | R1 Concepts Front Rear Brakes and Rotors Kit |Front Rear Brake Pads| Brake Rotors and Pads| Ceramic Brake Pads and Rotors | 292.84 | 973947 |
182694 | 2016 | Chevrolet | Impala | Bearings & Seals | Wheel Hub Bearing Assembly | 48.85 | 927944 |
497249 | 2013 | Chevrolet | Silverado 1500 | Drum Brake | ACDelco Gold 17960BF1 Bonded Rear Drum Brake Shoe Set | 58.92 | 497249 |
297149 | 2020 | Jeep | Wrangler | Air Filters | ACDelco Gold A3408C Air Filter | 22.83 | 297149 |
927397 | 2016 | Chevrolet | Impala | Bearings & Seals | Front/Rear Wheel Hub Bearing Assembly 5 Lugs w/ABS | 79.97 | 799428 |
392972 | 2020 | Toyota | Prius AWD-e | Shocks, Struts & Suspension | 2-Piece Suspension Strut and Coil Spring Kit (593024) | 299.97 | 593024 |
928037 | 2017 | Buick | Regal | Alternators & Generators | DB Electrical Alternator | 218.74 | 928037 |
502481 | 2016 | Chevrolet | Equinox | Bearings & Seals | Wheel hub bearing Assembly | 99.95 | 740248 |
593804 | 2019 | Honda | Accord LX 1.5L L4 Gas | Alternator | Alternator | 202.47 | 593804 |
293748 | 2014 | Toyota | Corolla SE 1.8L L4 Gas | Alternators | DB Electrical 400-40169 Alternator Compatible With/Replacement For 125 Internal Fan Type Decoupler Pulley Type Internal Regulator CW Rotation | 215.84 | 727394 |
639704 | 2021 | Kia | Sorento | Brake Kits | Rear Brakes and Rotors Kit |Rear Brake Pads| Brake Rotors and Pads| Optimum OEp Brake Pads and Rotors | 125.15 | 639704 |
829385 | 2020 | Jeep | Wrangler Unlimited Sahara | Drum Brake | Centric Brake Shoe | 22.05 | 829385 |
484695 | 2014 | GMC | Terrain | Bearings & Seals | Front/Rear Wheel Hub Bearing Assembly 5 Lugs w/ABS | 79.97 | 799428 |
807204 | 2016 | Chevrolet | Camaro | Alternators & Generators | DB Electrical Alternator | 218.74 | 928037 |
939283 | 2015 | Chevrolet | Equinox | Bearings & Seals | Wheel Hub Bearing Assembly | 48.85 | 927944 |
738628 | 2021 | Toyota | Prius AWD-e | Shocks, Struts & Suspension | 2-Piece Suspension Strut and Coil Spring Kit | 299.97 | 593024 |
186950 | 2017 | Honda | CRV | Alternator | Alternator | 202.47 | 593804 |
329573 | 2012 | Chevrolet | Equinox | Bearings & Seals | Front/Rear Wheel Hub Bearing Assembly 5 Lugs w/ABS | 79.97 | 799428 |
594085 | 2015 | Buick | Regal | Bearings & Seals | Front/Rear Wheel Hub Bearing Assembly 5 Lugs w/ABS | 79.97 | 799428 |
928405 | 2018 | Chevrolet | Camaro | Alternators & Generators | DB Electrical Alternator | 218.74 | 928037 |
927937 | 2012 | Ford | Focus SE | Starters | Duralast Starter 19481 | 188.88 | 927937 |
283948 | 2018 | GMC | Savana 3500 | Shocks, Struts & Suspension | Suspension Kit (Front; with 3 Groove Pitman Arm) | 142.44 | 304031 |
495116 | 2020 | Chrysler | Voyager | Brake Kits | Power Stop K7845 Rear Z23 Carbon Fiber Brake Pads with Drilled & Slotted Brake Rotors Kit | 269.75 | 293748 |
180400 | 2012 | Cadillac | CTS FWD | Bearings & Seals | Front/Rear Wheel Hub Bearing Assembly 5 Lugs w/ABS | 79.97 | 799428 |
593024 | 2021 | Toyota | Prius | Shocks, Struts & Suspension | 2-Piece Suspension Strut and Coil Spring Kit (593024) | 299.97 | 593024 |
302839 | 2014 | Chevrolet | Equinox | Bearings & Seals | Wheel Hub Bearing Assembly | 48.85 | 927944 |
649394 | 2020 | Jeep | Wrangler Unlimited Sahara | Brake Kits | Power Stop K7940 Front Z23 Evolution Sport Brake Upgrade Kit | 354.46 | 495116 |
820684 | 2015 | Buick | LaCrosse | Bearings & Seals | Front/Rear Wheel Hub Bearing Assembly 5 Lugs w/ABS | 79.97 | 799428 |
602839 | 2017 | GMC | Savana 2500 | Shocks, Struts & Suspension | Suspension Kit (Front; with 3 Groove Pitman Arm) | 142.44 | 304031 |
|
|||
Home | Copyright © 2003-2024, FunctionX | Sunday 01 April 2023 | |
|