17 Commits

Author SHA1 Message Date
e431a05124 chore: created README.md 2026-01-23 13:05:10 +01:00
b20e0a94dd chore: partially document MainForm 2026-01-23 12:46:57 +01:00
afa98b2681 chore: document StateManager 2026-01-23 12:44:00 +01:00
6d50c28c02 chore: document SafeManager 2026-01-23 12:38:17 +01:00
8be81812c1 chore: document ImageManager 2026-01-23 12:34:54 +01:00
de603b5cef chore: document Car and remove getter setter 2026-01-23 12:30:28 +01:00
640d9acbf6 chore: renamed solution 2026-01-23 12:16:38 +01:00
e511610052 Initial commit 2026-01-23 12:09:56 +01:00
Frozd
d17e1183f9 feat: keybinds 2025-11-28 13:08:18 +01:00
Frozd
bb71806a22 fix: better Image loading 2025-11-28 13:00:32 +01:00
Frozd
e89fc79ebb fix: more performant loading 2025-11-28 12:53:29 +01:00
Frozd
5b56d76cd7 fix: ordering 2025-11-28 12:41:45 +01:00
Frozd
0c8b10cfe6 fix: ordering with search v0 2025-11-28 12:37:22 +01:00
Frozd
d4f3ac8776 fix: search 2025-11-28 12:22:56 +01:00
Frozd
746f8c4039 fix: Update instead of re-fill 2025-11-28 12:09:07 +01:00
Frozd
cca7c37ffe chore: async loading 2025-11-28 10:46:06 +01:00
Frozd
306994542e fix: async car update 2025-11-28 10:35:59 +01:00
32 changed files with 11936 additions and 8954 deletions

4
.gitignore vendored
View File

@@ -361,4 +361,6 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
IAF42_Kaulmann_CarmanagerV2.zip
# Submissions folder (contains submission ZIPs)
submissions/

View File

@@ -1,60 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CarManagerV2
{
public class Car
{
int id;
string make;
string model;
int year;
string color;
int mileage;
decimal price;
public Car(int id, string make, string model, int year, string color, int mileage, decimal price)
{
this.id = id;
this.make = make;
this.model = model;
this.year = year;
this.color = color;
this.mileage = mileage;
this.price = price;
}
public int Id { get { return id; } set { id = value; } }
public string Make { get { return make; } set { make = value; } }
public string Model { get { return model; } set { model = value; } }
public int Year { get { return year; } set { year = value; } }
public string Color { get { return color; } set { color = value; } }
public int Mileage { get { return mileage; } set { mileage = value; } }
public decimal Price { get { return price; } set { price = value; } }
public override string ToString()
{
return $"{make} {model} ({year})";
}
public string ToCsvString()
{
return $"{id};{make};{model};{year};{color};{mileage};{price}";
}
public static Car FromCsvString(string csv)
{
string[] parts = csv.Split(';');
return new Car(int.Parse(parts[0]), parts[1], parts[2], int.Parse(parts[3]), parts[4], int.Parse(parts[5]), decimal.Parse(parts[6]));
}
public bool IsChanged(Car other)
{
return make != other.make || model != other.model || year != other.year || color != other.color || mileage != other.mileage || price != other.price;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,150 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CarManagerV2
{
public partial class MainForm : Form
{
List<Car> cars = new List<Car>();
public MainForm()
{
InitializeComponent();
SafeManager.InitializeFile("cars.csv");
List<Car> _cars = SafeManager.ReadCars("cars.csv");
refreshCars(_cars);
}
private void refreshCars(List<Car> _cars)
{
//if cars havent changed, dont refresh
//bool changed = false;
//foreach (Car car in _cars)
//{
// Car existing = cars.FirstOrDefault(c => c.Id == car.Id);
// if (existing == null)
// {
// Console.WriteLine($"Added: {car.Id}");
// changed = true;
// break;
// }
// else if (existing.isChanged(car))
// {
// Console.WriteLine($"Modified: {car.Id} / {existing.Id}");
// changed = true;
// break;
// }
//}
//if(!changed && cars.Count == _cars.Count)
//{
// Console.WriteLine("Cars are the same, not refreshing.");
// cars = _cars;
// return;
//}
cars = _cars;
//if(cars.SequenceEqual(_cars))
//{
// Console.WriteLine("Cars are the same, not refreshing.");
// return;
//}
//else
//{
// Console.WriteLine("Cars have changed, refreshing.");
// Console.WriteLine($"Old cars count: {cars.Count}, New cars count: {_cars.Count}");
// //find differences
// Console.WriteLine("Saved:");
// foreach (var car in cars)
// {
// Console.Write($"{car.Id}, {car.Make} {car.Model}, {car.Year} | ");
// }
// Console.WriteLine("-------------------");
// Console.WriteLine("New:");
// foreach (var car in _cars)
// {
// Console.Write($"{car.Id}, {car.Make} {car.Model}, {car.Year} | ");
// }
// Console.WriteLine();
// var addedCars = _cars.Except(cars).ToList();
// var removedCars = cars.Except(_cars).ToList();
// var modifiedCars = _cars.Where(c => cars.Any(c2 => c2.Id == c.Id && !c2.Equals(c))).ToList();
// Console.WriteLine($"Added cars: {addedCars.Count}, Removed cars: {removedCars.Count}, Modified cars: {modifiedCars.Count}");
//}
flpCars.Controls.Clear();
foreach (Car car in _cars)
{
CarCard card = new CarCard();
card.CarName = $"{car.Make} {car.Model}";
card.CarDetails = $"{car.Year}, {car.Mileage} km, ${car.Price}";
card.CarImage = ImageManager.GetImage(car);
card.CardClicked += (s, e) =>
{
Console.WriteLine($"Card clicked: {car.Id}");
CarDetailsForm detailsForm = new CarDetailsForm(car);
detailsForm.FormClosed += (s2, e2) =>
{
// refresh cars
Application.DoEvents();
Console.WriteLine("Refreshing cars...");
List<Car> __cars = SafeManager.ReadCars("cars.csv");
refreshCars(__cars);
};
detailsForm.ShowDialog();
};
flpCars.Controls.Add(card);
}
}
private void btnNewCar_Click(object sender, EventArgs e)
{
Car foocar = StateManager.CreateCar("New", "Car", 2020, "White", 0, 20000);
CarDetailsForm detailsForm = new CarDetailsForm(foocar);
detailsForm.FormClosed += (s2, e2) =>
{
// refresh cars
Console.WriteLine("Refreshing cars...");
cars = SafeManager.ReadCars("cars.csv");
refreshCars(cars);
};
detailsForm.ShowDialog();
}
void searchList(string query)
{
List<Car> results = new List<Car>();
foreach (Car car in cars)
{
if (car.Make.ToLower().Contains(query.ToLower()) || car.Model.ToLower().Contains(query.ToLower()) || car.Year.ToString().Contains(query) || car.Mileage.ToString().Contains(query) || car.Price.ToString().Contains(query))
{
results.Add(car);
}
}
refreshCars(results);
}
private void tbxSearch_TextChanged(object sender, EventArgs e)
{
string query = tbxSearch.Text;
if(string.IsNullOrWhiteSpace(query))
{
refreshCars(cars);
}
else
{
searchList(query);
}
}
}
}

View File

@@ -1,52 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CarManagerV2
{
internal class SafeManager
{
public static void InitializeFile(string path)
{
if (!File.Exists(@path))
{
using (StreamWriter writer = new StreamWriter(@path))
{
// Create the file, empty
writer.WriteLine();
}
}
}
public static List<Car> ReadCars(string path)
{
List<Car> cars = new List<Car>();
using (StreamReader reader = new StreamReader(@path))
{
string line;
while ((line = reader.ReadLine()) != null)
{
// Process the line
if (line == "") continue;
cars.Add(Car.FromCsvString(line));
}
}
return cars;
}
public static void SaveCars(string path, List<Car> cars)
{
using (StreamWriter writer = new StreamWriter(@path))
{
foreach (Car car in cars)
{
writer.WriteLine(car.ToCsvString());
}
}
}
}
}

View File

@@ -1,59 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CarManagerV2
{
internal class StateManager
{
static List<Car> cars = new List<Car>();
public static Car GetCarById(int id)
{
cars = SafeManager.ReadCars("cars.csv");
return cars.FirstOrDefault(c => c.Id == id);
}
public static List<Car> Cars { get { return cars; } set { cars = value; } }
public static void AddCar(Car car)
{
cars = SafeManager.ReadCars("cars.csv");
cars.Add(car);
SafeManager.SaveCars("cars.csv", cars);
}
public static void RemoveCar(Car car)
{
cars = SafeManager.ReadCars("cars.csv");
Car existingCar = GetCarById(car.Id);
if (existingCar == null) return;
cars.Remove(existingCar);
SafeManager.SaveCars("cars.csv", cars);
}
public static void UpdateCar(Car car)
{
Car existingCar = GetCarById(car.Id);
if (existingCar != null)
{
int index = cars.IndexOf(existingCar);
cars[index] = car;
Console.WriteLine("Updated car: " + existingCar.Id);
SafeManager.SaveCars("cars.csv", cars);
}
}
public static Car CreateCar(string make, string model, int year, string color, int mileage, decimal price)
{
cars = SafeManager.ReadCars("cars.csv");
int newId = cars.Count > 0 ? cars.Max(c => c.Id) + 1 : 1;
Car newCar = new Car(newId, make, model, year, color, mileage, price);
AddCar(newCar);
return newCar;
}
}
}

View File

@@ -1,9 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36414.22 d17.14
VisualStudioVersion = 17.14.36414.22
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CarManagerV2", "CarManagerV2\CarManagerV2.csproj", "{93CA258B-A645-41A8-A24F-59036ABC173F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CarManagerV3", "CarManagerV3\CarManagerV3.csproj", "{93CA258B-A645-41A8-A24F-59036ABC173F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

114
CarManagerV3/Car.cs Normal file
View File

@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CarManagerV3
{
/// <summary>
/// Class <c>Car</c> represents a car with various attributes such as make, model, year, color, mileage, and price.
/// </summary>
public class Car
{
public int Id;
public string Make;
public string Model;
public int Year;
public string Color;
public int Mileage;
public decimal Price;
public int Order;
//TODO: Make Id read-only CUID. Allows for better integrity, especially when merging.
//TODO: Add buying price and automatic calculation of profit/loss with selling price suggestion.
//TODO: Add Buying Date.
//TODO: Add sold boolean and Sold Date.
//TODO: Add "hidden" attribute for cars that are not for sale anymore but should be kept in the database for records.
/// <summary>
/// Initializes a new instance of the <see cref="Car"/> class.
/// </summary>
/// <param name="id">The unique identifier as an Integer.</param>
/// <param name="make">The make / manufacturer of the car.</param>
/// <param name="model">The model.</param>
/// <param name="year">The year the car was built.</param>
/// <param name="color">The color of the car.</param>
/// <param name="mileage">The current mileage on the car.</param>
/// <param name="price">The selling-price of the car.</param>
/// <param name="order">The order.</param>
public Car(int id, string make, string model, int year, string color, int mileage, decimal price, int order = 0)
{
this.Id = id;
this.Make = make;
this.Model = model;
this.Year = year;
this.Color = color;
this.Mileage = mileage;
this.Price = price;
this.Order = order;
}
/// <summary>
/// Converts to string in a custom readable format.
/// <example>
/// For Example:
/// Skoda Fabia (2015)
/// </example>
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
return $"{this.Make} {this.Model} ({this.Year})";
}
/// <summary>
/// Converts to default CSV savable string.
/// </summary>
/// <returns></returns>
public string ToCsvString()
{
return $"{this.Id};{this.Make};{this.Model};{this.Year};{this.Color};{this.Mileage};{this.Price}";
}
//TODO: Add error handling for malformed CSV strings and detection for missing fields.
//TODO: Add support for different CSV formats (e.g., different delimiters, quoted fields, etc.).
//TODO: Add support for nil or optional fields.
//TODO: Add detectin for invalid data / nonsensical values (e.g., negative mileage or year in the future). / Validate it actually is a car.
/// <summary>
/// Creates a <see cref="Car"/> instance from a CSV string in the default format.
/// </summary>
/// <param name="csv">The CSV Line.</param>
/// <returns>
/// A new <see cref="Car"/> instance.
/// </returns>
public static Car FromCsvString(string csv)
{
string[] parts = csv.Split(';');
return new Car(int.Parse(parts[0]), parts[1], parts[2], int.Parse(parts[3]), parts[4], int.Parse(parts[5]), decimal.Parse(parts[6]));
}
/// <summary>
/// Determines whether this <see cref="Car"/> is any different from the provided <see cref="Car" />.
/// </summary>
/// <param name="other">The <see cref="Car"/> to check against</param>
/// <returns>
/// <c>true</c> if the specified other is changed; otherwise, <c>false</c>.
/// </returns>
public bool IsChanged(Car other)
{
return this.Make != other.Make || this.Model != other.Model || this.Year != other.Year || this.Color != other.Color || this.Mileage != other.Mileage || this.Price != other.Price || this.Color != other.Color;
}
/// <summary>
/// Clones this instance.
/// </summary>
/// <returns>An identical but seperate <see cref="Car"/></returns>
public Car Clone()
{
return new Car(this.Id, this.Make, this.Model, this.Year, this.Color, this.Mileage, this.Price);
}
}
}

View File

@@ -1,4 +1,4 @@
namespace CarManagerV2
namespace CarManagerV3
{
partial class CarCard
{
@@ -82,6 +82,7 @@
this.pbxCar.Dock = System.Windows.Forms.DockStyle.Fill;
this.pbxCar.Image = ((System.Drawing.Image)(resources.GetObject("pbxCar.Image")));
this.pbxCar.ImageLocation = "";
this.pbxCar.InitialImage = ((System.Drawing.Image)(resources.GetObject("pbxCar.InitialImage")));
this.pbxCar.Location = new System.Drawing.Point(3, 3);
this.pbxCar.Name = "pbxCar";
this.pbxCar.Size = new System.Drawing.Size(204, 124);

View File

@@ -8,10 +8,12 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CarManagerV2
namespace CarManagerV3
{
public partial class CarCard : UserControl
{
public Car Car;
public string CarName
{
get { return lblCarName.Text; }
@@ -44,6 +46,19 @@ namespace CarManagerV2
this.Click += (s, e) => this.OnCardClicked();
}
public async void LoadImage()
{
this.CarImage = pbxCar.InitialImage; // Set to loading image
await Task.Run(() =>
{
Image img = ImageManager.GetImage(this.Car);
if (img != null)
{
this.CarImage = img;
}
});
}
private void ForwardClick(object sender, EventArgs e)
{
// Raise your CardClicked event no matter what got clicked
@@ -58,5 +73,16 @@ namespace CarManagerV2
this.CardClicked(this, EventArgs.Empty);
}
}
public void ClearCardClickedHandlers()
{
if (this.CardClicked != null)
{
foreach (Delegate d in this.CardClicked.GetInvocationList())
{
this.CardClicked -= (EventHandler)d;
}
}
}
}
}

10889
CarManagerV3/CarCard.resx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
namespace CarManagerV2
namespace CarManagerV3
{
partial class CarDetailsForm
{
@@ -118,7 +118,7 @@
this.tbxMake.Location = new System.Drawing.Point(68, 203);
this.tbxMake.Name = "tbxMake";
this.tbxMake.Size = new System.Drawing.Size(473, 22);
this.tbxMake.TabIndex = 2;
this.tbxMake.TabIndex = 1;
this.tbxMake.TextChanged += new System.EventHandler(this.tbxMake_TextChanged);
//
// label2
@@ -136,7 +136,7 @@
this.tbxModel.Location = new System.Drawing.Point(68, 233);
this.tbxModel.Name = "tbxModel";
this.tbxModel.Size = new System.Drawing.Size(473, 22);
this.tbxModel.TabIndex = 4;
this.tbxModel.TabIndex = 2;
this.tbxModel.TextChanged += new System.EventHandler(this.tbxModel_TextChanged);
//
// label3
@@ -194,7 +194,7 @@
this.btnSave.Location = new System.Drawing.Point(450, 8);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(75, 23);
this.btnSave.TabIndex = 0;
this.btnSave.TabIndex = 7;
this.btnSave.Text = "Save";
this.btnSave.UseVisualStyleBackColor = true;
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
@@ -204,7 +204,7 @@
this.btnDelete.Location = new System.Drawing.Point(369, 8);
this.btnDelete.Name = "btnDelete";
this.btnDelete.Size = new System.Drawing.Size(75, 23);
this.btnDelete.TabIndex = 1;
this.btnDelete.TabIndex = 8;
this.btnDelete.Text = "Delete";
this.btnDelete.UseVisualStyleBackColor = true;
this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click);
@@ -228,7 +228,7 @@
0});
this.nudYear.Name = "nudYear";
this.nudYear.Size = new System.Drawing.Size(120, 22);
this.nudYear.TabIndex = 10;
this.nudYear.TabIndex = 3;
this.nudYear.ValueChanged += new System.EventHandler(this.nudYear_ValueChanged);
//
// tbxColor
@@ -237,7 +237,7 @@
this.tbxColor.Location = new System.Drawing.Point(68, 293);
this.tbxColor.Name = "tbxColor";
this.tbxColor.Size = new System.Drawing.Size(473, 22);
this.tbxColor.TabIndex = 11;
this.tbxColor.TabIndex = 4;
this.tbxColor.TextChanged += new System.EventHandler(this.tbxColor_TextChanged);
//
// nudMileage
@@ -250,7 +250,7 @@
0});
this.nudMileage.Name = "nudMileage";
this.nudMileage.Size = new System.Drawing.Size(120, 22);
this.nudMileage.TabIndex = 12;
this.nudMileage.TabIndex = 5;
this.nudMileage.ThousandsSeparator = true;
this.nudMileage.ValueChanged += new System.EventHandler(this.nudMileage_ValueChanged);
//
@@ -265,7 +265,7 @@
0});
this.nudPrice.Name = "nudPrice";
this.nudPrice.Size = new System.Drawing.Size(120, 22);
this.nudPrice.TabIndex = 13;
this.nudPrice.TabIndex = 6;
this.nudPrice.ThousandsSeparator = true;
this.nudPrice.ValueChanged += new System.EventHandler(this.nudPrice_ValueChanged);
//

View File

@@ -8,7 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CarManagerV2
namespace CarManagerV3
{
public partial class CarDetailsForm : Form
{
@@ -59,17 +59,26 @@ namespace CarManagerV2
car.Price = nudPrice.Value;
}
private void btnSave_Click(object sender, EventArgs e)
private async void btnSave_Click(object sender, EventArgs e)
{
// save car to cars.csv and close form
//saving car... popuo
btnSave.Enabled = false;
btnDelete.Enabled = false;
var msgbox = new PleaseWait();
msgbox.Show();
Application.DoEvents();
await Task.Run(() =>
{
StateManager.UpdateCar(car);
Image fooimg = ImageManager.GetImage(car);
msgbox.Close();
Console.WriteLine("Saved car: " + car.Id);
});
Console.WriteLine("Car saved. " + car.Id);
btnSave.Enabled = true;
btnDelete.Enabled = true;
this.Close();
msgbox.Close();
}
@@ -85,5 +94,24 @@ namespace CarManagerV2
}
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Enter)
{
btnSave.PerformClick();
return true; // Indicate that the key has been handled
}
if (keyData == Keys.Escape)
{
this.Close();
return true; // Indicate that the key has been handled
}
if (keyData == Keys.Delete)
{
btnDelete.PerformClick();
return true; // Indicate that the key has been handled
}
return base.ProcessCmdKey(ref msg, keyData);
}
}
}

View File

@@ -6,12 +6,27 @@
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{93CA258B-A645-41A8-A24F-59036ABC173F}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>CarManagerV2</RootNamespace>
<AssemblyName>CarManagerV2</AssemblyName>
<RootNamespace>CarManagerV3</RootNamespace>
<AssemblyName>CarManagerV3</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -96,6 +111,7 @@
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
@@ -110,5 +126,17 @@
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -5,11 +5,16 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CarManagerV2
namespace CarManagerV3
{
/// <summary>
/// The <c>ImageManager</c> class is responsible for managing car images, including fetching and storing them locally.
/// </summary>
internal class ImageManager
{
/// <summary>
/// Initializes the image folder by creating it if it does not exist.
/// </summary>
public static void InitializeImageFolder()
{
string path = "images";
@@ -19,6 +24,11 @@ namespace CarManagerV2
}
}
/// <summary>
/// Generates the image path for a given car based on its attributes.
/// </summary>
/// <param name="car">The car.</param>
/// <returns>The image path for this Car.</returns>
public static string GetImagePath(Car car)
{
string basePath = "images/";
@@ -26,6 +36,11 @@ namespace CarManagerV2
return basePath + fileName;
}
/// <summary>
/// Gets the image for a given car, fetching it if necessary.
/// </summary>
/// <param name="car">The car.</param>
/// <returns>The <see cref="Image"/> object for the car.</returns>
public static Image GetImage(Car car)
{
InitializeImageFolder();
@@ -43,11 +58,16 @@ namespace CarManagerV2
}
/// <summary>
/// Fetches an image for a Car from https://cdn.imagin.studio/getimage if it does not already exist locally and saves it to images/<c>Make_Model_Year_Color.webp</>.
/// If the image cannot be fetched, a placeholder image is used instead.
/// Uses example customer id "hrjavascript-mastery", because they wouldn't give me one. Stole this from a tutorial page. :)
/// </summary>
/// <param name="car">The car.</param>
public static void FetchImage(Car car)
{
// Fetch the image from https://cdn.imagin.studio/getimage and save it to images/Make_Model_Year.webp
// use params like this: ?customer=hrjavascript-mastery&zoomType=fullscreen&make={make}&modelFamily={model}&modelYear={year}&angle=front&paintDescription={color}&fileType=png
// check if the image already exists
string path = GetImagePath(car);
if (System.IO.File.Exists(path))
@@ -55,7 +75,7 @@ namespace CarManagerV2
return;
}
string url = $"https://cdn.imagin.studio/getimage?customer=hrjavascript-mastery&zoomType=fullscreen&make={car.Make}&modelFamily={car.Model}&modelYear={car.Year}&angle=front&paintDescription={car.Color}&fileType=png";
//add Referer header
//add Referer header to avoid 403 error
using (var client = new System.Net.WebClient())
{
client.Headers.Add("Referer", "http://localhost");
@@ -65,7 +85,7 @@ namespace CarManagerV2
}
catch
{
// if error, use no_image_available.png
// if error, use fallback image no_image_available.png
System.IO.File.Copy("images/no_image_available.png", path);
}
}

View File

@@ -1,4 +1,4 @@
namespace CarManagerV2
namespace CarManagerV3
{
partial class MainForm
{
@@ -34,33 +34,45 @@
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
this.tbxSearch = new System.Windows.Forms.TextBox();
this.btnNewCar = new System.Windows.Forms.Button();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.recentFilesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.revealInFileExplorerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.tableLayoutPanel1.SuspendLayout();
this.tableLayoutPanel2.SuspendLayout();
this.menuStrip1.SuspendLayout();
this.SuspendLayout();
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 1;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.Controls.Add(this.flpCars, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.flpCars, 0, 2);
this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.menuStrip1, 0, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 2;
this.tableLayoutPanel1.RowCount = 3;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.Size = new System.Drawing.Size(800, 450);
this.tableLayoutPanel1.Size = new System.Drawing.Size(802, 458);
this.tableLayoutPanel1.TabIndex = 0;
this.tableLayoutPanel1.Paint += new System.Windows.Forms.PaintEventHandler(this.tableLayoutPanel1_Paint);
//
// flpCars
//
this.flpCars.AutoScroll = true;
this.flpCars.AutoScrollMargin = new System.Drawing.Size(0, 200);
this.flpCars.Dock = System.Windows.Forms.DockStyle.Fill;
this.flpCars.Location = new System.Drawing.Point(3, 43);
this.flpCars.Location = new System.Drawing.Point(3, 67);
this.flpCars.Name = "flpCars";
this.flpCars.Size = new System.Drawing.Size(794, 404);
this.flpCars.Size = new System.Drawing.Size(796, 412);
this.flpCars.TabIndex = 1;
//
// tableLayoutPanel2
@@ -71,12 +83,12 @@
this.tableLayoutPanel2.Controls.Add(this.tbxSearch, 0, 0);
this.tableLayoutPanel2.Controls.Add(this.btnNewCar, 1, 0);
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 3);
this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 27);
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
this.tableLayoutPanel2.RowCount = 1;
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 34F));
this.tableLayoutPanel2.Size = new System.Drawing.Size(794, 34);
this.tableLayoutPanel2.Size = new System.Drawing.Size(796, 34);
this.tableLayoutPanel2.TabIndex = 2;
//
// tbxSearch
@@ -84,13 +96,13 @@
this.tbxSearch.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbxSearch.Location = new System.Drawing.Point(3, 3);
this.tbxSearch.Name = "tbxSearch";
this.tbxSearch.Size = new System.Drawing.Size(391, 22);
this.tbxSearch.Size = new System.Drawing.Size(392, 22);
this.tbxSearch.TabIndex = 3;
this.tbxSearch.TextChanged += new System.EventHandler(this.tbxSearch_TextChanged);
//
// btnNewCar
//
this.btnNewCar.Location = new System.Drawing.Point(400, 3);
this.btnNewCar.Location = new System.Drawing.Point(401, 3);
this.btnNewCar.Name = "btnNewCar";
this.btnNewCar.Size = new System.Drawing.Size(75, 23);
this.btnNewCar.TabIndex = 4;
@@ -98,19 +110,88 @@
this.btnNewCar.UseVisualStyleBackColor = true;
this.btnNewCar.Click += new System.EventHandler(this.btnNewCar_Click);
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.fileToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(802, 24);
this.menuStrip1.TabIndex = 3;
this.menuStrip1.Text = "menuStrip1";
//
// fileToolStripMenuItem
//
this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.openToolStripMenuItem,
this.saveToolStripMenuItem,
this.saveAsToolStripMenuItem,
this.importToolStripMenuItem,
this.recentFilesToolStripMenuItem,
this.revealInFileExplorerToolStripMenuItem});
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
this.fileToolStripMenuItem.Text = "File";
//
// openToolStripMenuItem
//
this.openToolStripMenuItem.Name = "openToolStripMenuItem";
this.openToolStripMenuItem.Size = new System.Drawing.Size(187, 22);
this.openToolStripMenuItem.Text = "Open";
this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click);
//
// saveToolStripMenuItem
//
this.saveToolStripMenuItem.Name = "saveToolStripMenuItem";
this.saveToolStripMenuItem.Size = new System.Drawing.Size(187, 22);
this.saveToolStripMenuItem.Text = "Save";
this.saveToolStripMenuItem.Click += new System.EventHandler(this.saveToolStripMenuItem_Click);
//
// saveAsToolStripMenuItem
//
this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem";
this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(187, 22);
this.saveAsToolStripMenuItem.Text = "Save as";
this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.saveAsToolStripMenuItem_Click);
//
// importToolStripMenuItem
//
this.importToolStripMenuItem.Name = "importToolStripMenuItem";
this.importToolStripMenuItem.Size = new System.Drawing.Size(187, 22);
this.importToolStripMenuItem.Text = "Import";
this.importToolStripMenuItem.Click += new System.EventHandler(this.importToolStripMenuItem_Click);
//
// recentFilesToolStripMenuItem
//
this.recentFilesToolStripMenuItem.Name = "recentFilesToolStripMenuItem";
this.recentFilesToolStripMenuItem.Size = new System.Drawing.Size(187, 22);
this.recentFilesToolStripMenuItem.Text = "Recent Files";
this.recentFilesToolStripMenuItem.Click += new System.EventHandler(this.recentFilesToolStripMenuItem_Click);
//
// revealInFileExplorerToolStripMenuItem
//
this.revealInFileExplorerToolStripMenuItem.Name = "revealInFileExplorerToolStripMenuItem";
this.revealInFileExplorerToolStripMenuItem.Size = new System.Drawing.Size(187, 22);
this.revealInFileExplorerToolStripMenuItem.Text = "Reveal in File Explorer";
this.revealInFileExplorerToolStripMenuItem.Click += new System.EventHandler(this.revealInFileExplorerToolStripMenuItem_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.ClientSize = new System.Drawing.Size(802, 458);
this.Controls.Add(this.tableLayoutPanel1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MainMenuStrip = this.menuStrip1;
this.MinimumSize = new System.Drawing.Size(818, 497);
this.Name = "MainForm";
this.Text = "Carmanager 2";
this.Text = "Carmanager 3";
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.tableLayoutPanel2.ResumeLayout(false);
this.tableLayoutPanel2.PerformLayout();
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.ResumeLayout(false);
}
@@ -122,5 +203,13 @@
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
private System.Windows.Forms.TextBox tbxSearch;
private System.Windows.Forms.Button btnNewCar;
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem recentFilesToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem revealInFileExplorerToolStripMenuItem;
}
}

404
CarManagerV3/MainForm.cs Normal file
View File

@@ -0,0 +1,404 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CarManagerV3
{
public partial class MainForm : Form
{
List<Car> cars = new List<Car>();
string filepath = "cars.csv";
public MainForm()
{
InitializeComponent();
// Open the most recent file if it exists. Otherwise, use default filepath.
List<string> recentFiles = SafeManager.GetRecentPaths();
if(recentFiles.Count > 0)
{
filepath = recentFiles[0];
}
SafeManager.InitializeFile(filepath);
List<Car> _cars = SafeManager.ReadCars(filepath);
refreshCars(_cars);
refreshRecents();
}
/// <summary>
/// Refreshes the cars displayed in the flow layout panel.
/// </summary>
/// <param name="_cars">The cars.</param>
/// <param name="updateGlobal">if set to <c>true</c> [update global].</param>
private async void refreshCars(List<Car> _cars, bool updateGlobal = true)
{
this.Text = "Car Manager - " + System.IO.Path.GetFileName(filepath);
// Sort by Car.Order. If equal, sort by ID
_cars = _cars.Count > 0 ? _cars.OrderBy(c => c.Order).ThenBy(c => c.Id).ToList() : _cars;
if (updateGlobal)
{
cars = _cars;
}
foreach (Car car in _cars)
{
// not in list? add it
bool isNew = flpCars.Controls.OfType<CarCard>().All(c => c.Car.Id != car.Id);
// existing but changed? update it
CarCard card = new CarCard();
if (!isNew)
{
CarCard existing = flpCars.Controls.OfType<CarCard>().First(c => c.Car.Id == car.Id);
Car existingCar = existing.Car;
if (existing == null)
{
Console.Error.WriteLine($"[L] Error: Existing car card not found for car ID: {car.Id}");
continue;
}
// compare details
Console.WriteLine($"[L] Checking car: {car.Id} | Car Color: {car.Color} | Ex Color: {existingCar.Color}");
if (existingCar.IsChanged(car))
{
Console.WriteLine($"[L] Updating car: {car.Id}");
// changes
card = existing;
}
else
{
// no changes
Console.WriteLine($"[L] No changes for car: {car.Id}");
flpCars.Controls.SetChildIndex(existing, _cars.IndexOf(car));
continue;
}
}
card.CarName = $"{car.Make} {car.Model}";
card.CarDetails = $"{car.Year}, {car.Mileage} km, ${car.Price}";
card.Car = car.Clone();
card.LoadImage();
// clear existing event handlers to prevent multiple subscriptions
card.ClearCardClickedHandlers();
card.CardClicked += (s, e) =>
{
Console.WriteLine($"Card clicked: {car.Id}");
CarDetailsForm detailsForm = new CarDetailsForm(car);
detailsForm.FormClosed += async (s2, e2) =>
{
Console.WriteLine("Car details form closed.");
// refresh cars
Console.WriteLine("Refreshing cars...");
List<Car> __cars = await Task.Run(() => SafeManager.ReadCars(filepath));
if (tbxSearch.Text.Length > 0)
{
Console.WriteLine("Search box has text, applying search filter.");
cars = __cars;
searchList(tbxSearch.Text);
return;
}
refreshCars(__cars);
};
detailsForm.ShowDialog();
};
if (isNew)
{
flpCars.Controls.Add(card);
}
flpCars.Controls.SetChildIndex(card, _cars.IndexOf(car));
}
// Remove cards that are no longer in _cars
var carIds = _cars.Select(c => c.Id).ToList();
var cardsToRemove = flpCars.Controls.OfType<CarCard>().Where(c => !carIds.Contains(c.Car.Id)).ToList();
foreach (var card in cardsToRemove)
{
flpCars.Controls.Remove(card);
}
flpCars.Refresh();
flpCars.Invalidate();
flpCars.Update();
}
private void btnNewCar_Click(object sender, EventArgs e)
{
Car foocar = StateManager.CreateCar("New", "Car", 2020, "White", 0, 20000);
CarDetailsForm detailsForm = new CarDetailsForm(foocar);
detailsForm.FormClosed += (s2, e2) =>
{
// refresh cars
Console.WriteLine("Refreshing cars...");
List<Car> cars_ = SafeManager.ReadCars(filepath);
refreshCars(cars_, false);
};
detailsForm.ShowDialog();
}
/// <summary>
/// Filters the cars by a search query.
/// </summary>
/// <param name="query">The search query.</param>
/// <returns>
/// A list of <see cref="Car"/> objects that match the query.
/// </returns>
List<Car> filterCarsByQuery(string query)
{
List<Car> results = new List<Car>();
foreach (Car car in cars)
{
if (car.Make.ToLower().Contains(query.ToLower()) || car.Model.ToLower().Contains(query.ToLower()) || car.Year.ToString().Contains(query) || car.Mileage.ToString().Contains(query) || car.Price.ToString().Contains(query))
{
results.Add(car);
}
}
return results;
}
/// <summary>
/// Searches the list of cars by a query and refreshes the display.
/// </summary>
/// <param name="query">The query.</param>
void searchList(string query)
{
List<Car> results = filterCarsByQuery(query);
refreshCars(results, false);
}
private async void tbxSearch_TextChanged(object sender, EventArgs e)
{
string query = tbxSearch.Text;
await Task.Delay(100); // debounce
if (query != tbxSearch.Text) return; // text changed during delay
//flpCars.Controls.Clear();
if (string.IsNullOrWhiteSpace(query))
{
refreshCars(cars);
}
else
{
searchList(query);
}
}
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog dlgOpen = new OpenFileDialog();
dlgOpen.Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*";
dlgOpen.Title = "Open Car Data File";
// Default to users documents
dlgOpen.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
DialogResult result = dlgOpen.ShowDialog();
if (result == DialogResult.OK)
{
try
{
List<Car> importedCars = SafeManager.ReadCars(dlgOpen.FileName);
if(importedCars.Count == 0)
{
throw new Exception("File doesn't contain valid Cars.");
}
filepath = dlgOpen.FileName;
cars = importedCars;
StateManager.setFilePath(filepath);
// Refresh display
refreshCars(cars);
MessageBox.Show("File loaded successfully.", "Load File", MessageBoxButtons.OK, MessageBoxIcon.Information);
SafeManager.AddRecentPath(filepath);
refreshRecents();
}
catch (Exception ex)
{
MessageBox.Show("Error loading file: " + ex.Message);
}
}
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
refreshCars(cars);
SafeManager.SaveCars(filepath, cars);
MessageBox.Show("File saved successfully.", "Save File", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
{
SaveFileDialog dlgSave = new SaveFileDialog();
dlgSave.Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*";
dlgSave.Title = "Save Car Data File As";
// Default to users documents
dlgSave.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
DialogResult result = dlgSave.ShowDialog();
if (result == DialogResult.OK)
{
// does file already exist?
/*if (System.IO.File.Exists(dlgSave.FileName))
{
var overwriteResult = MessageBox.Show("File already exists. Overwrite?", "Overwrite File", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (overwriteResult != DialogResult.Yes)
{
return;
}
}*/
// Windows already handles this lmao
filepath = dlgSave.FileName;
this.Text = "Car Manager - " + System.IO.Path.GetFileName(filepath);
StateManager.setFilePath(filepath);
SafeManager.SaveCars(filepath, cars);
SafeManager.AddRecentPath(filepath);
}
refreshRecents();
}
private void importToolStripMenuItem_Click(object sender, EventArgs e)
{
DialogResult result = MessageBox.Show("Importing will add cars from another file to this file. This action cannot be undone. Continue?", "Import Cars", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
{
OpenFileDialog dlgOpen = new OpenFileDialog();
dlgOpen.Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*";
dlgOpen.Title = "Import Car Data File";
// Default to users documents
dlgOpen.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
DialogResult dlgResult = dlgOpen.ShowDialog();
if (dlgResult == DialogResult.OK)
{
try
{
Console.WriteLine("Starting merge...");
List<Car> importedCars = SafeManager.ReadCars(dlgOpen.FileName);
if(importedCars.Count == 0)
{
throw new Exception("File doesn't contain valid Cars.");
}
// merge cars
foreach (Car car in importedCars)
{
// check if car with same ID exists
if (cars.Any(c => c.Id == car.Id))
{
// assign new ID
int newId = cars.Count > 0 ? cars.Max(c => c.Id) + 1 : 1;
car.Id = newId;
}
cars.Add(car);
}
DialogResult mergeAsNewFileResult = MessageBox.Show("Do you want to save the merged cars as a new file?", "Save As New File", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (mergeAsNewFileResult == DialogResult.Yes)
{
SaveFileDialog dlgSave = new SaveFileDialog();
dlgSave.Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*";
dlgSave.Title = "Save Merged Car Data File As";
// Default to users documents
dlgSave.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
DialogResult saveResult = dlgSave.ShowDialog();
if (saveResult == DialogResult.OK)
{
filepath = dlgSave.FileName;
StateManager.setFilePath(filepath);
SafeManager.SaveCars(filepath, cars);
SafeManager.AddRecentPath(filepath);
refreshRecents();
}
}
else
{
// save to current file
SafeManager.SaveCars(filepath, cars);
}
// Refresh display
refreshCars(cars);
MessageBox.Show("File imported successfully.", "Import File", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show("Error importing file: " + ex.Message);
}
}
else
{
Console.WriteLine("Import cancelled.");
}
}
}
// TODO: Unbind and remove this.
private void recentFilesToolStripMenuItem_Click(object sender, EventArgs e)
{
}
/// <summary>
/// Refreshes the recently opened files menu.
/// </summary>
private void refreshRecents()
{
recentFilesToolStripMenuItem.DropDownItems.Clear();
List<string> recentFiles = SafeManager.GetRecentPaths();
recentFilesToolStripMenuItem.Enabled = recentFiles.Count > 0;
recentFilesToolStripMenuItem.ToolTipText = recentFiles.Count > 0 ? "" : "No recent files.";
foreach (string path in recentFiles)
{
ToolStripMenuItem item = new ToolStripMenuItem(path);
item.Click += (s, e2) =>
{
try
{
List<Car> importedCars = SafeManager.ReadCars(path);
if(importedCars.Count == 0)
{
throw new Exception("File doesn't contain valid Cars.");
}
filepath = path;
cars = importedCars;
StateManager.setFilePath(filepath);
// Refresh display
refreshCars(cars);
MessageBox.Show("File loaded successfully.", "Load File", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show("Error loading file: " + ex.Message);
}
};
recentFilesToolStripMenuItem.DropDownItems.Add(item);
}
}
private void revealInFileExplorerToolStripMenuItem_Click(object sender, EventArgs e)
{
// Open File Explorer at the location of the current filepath
if (System.IO.File.Exists(filepath))
{
System.Diagnostics.Process.Start("explorer.exe", "/select,\"" + filepath + "\"");
}
else
{
MessageBox.Show("File does not exist.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}

View File

@@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>

View File

@@ -1,4 +1,4 @@
namespace CarManagerV2
namespace CarManagerV3
{
partial class PleaseWait
{

View File

@@ -8,7 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CarManagerV2
namespace CarManagerV3
{
public partial class PleaseWait : Form
{

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CarManagerV2
namespace CarManagerV3
{
internal static class Program
{

View File

@@ -5,11 +5,11 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CarManagerV2")]
[assembly: AssemblyTitle("CarManagerV3")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CarManagerV2")]
[assembly: AssemblyProduct("CarManagerV3")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@@ -8,8 +8,8 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace CarManagerV2.Properties
{
namespace CarManagerV3.Properties {
using System;
/// <summary>
@@ -19,32 +19,27 @@ namespace CarManagerV2.Properties
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CarManagerV2.Properties.Resources", typeof(Resources).Assembly);
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CarManagerV3.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -56,14 +51,11 @@ namespace CarManagerV2.Properties
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set
{
set {
resourceCulture = value;
}
}

View File

@@ -8,21 +8,17 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace CarManagerV2.Properties
{
namespace CarManagerV3.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
public static Settings Default {
get {
return defaultInstance;
}
}

135
CarManagerV3/SafeManager.cs Normal file
View File

@@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CarManagerV3
{
/// <summary>
/// Handles safe reading and writing of car data to files.
/// </summary>
internal class SafeManager
{
/// <summary>
/// The path of the txt file that contains recently opened file paths.
/// </summary>
private static readonly string recentPathsFile = "recent_paths.txt";
/// <summary>
/// Initializes a file at a specified path if it does not already exist.
/// </summary>
/// <param name="path">The path.</param>
public static void InitializeFile(string path)
{
if (!File.Exists(@path))
{
using (StreamWriter writer = new StreamWriter(@path))
{
// Create the file, empty
writer.WriteLine();
}
}
}
/// <summary>
/// Reads cars from a specified file path.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>
/// A <see cref="List{Car}"/> containing the cars read from the file.
/// </returns>
public static List<Car> ReadCars(string path)
{
List<Car> cars = new List<Car>();
using (StreamReader reader = new StreamReader(@path))
{
string line;
while ((line = reader.ReadLine()) != null)
{
// Process the line
if (line == "") continue;
cars.Add(Car.FromCsvString(line));
}
}
return cars;
}
/// <summary>
/// Saves the cars to a specified file path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="cars">A <see cref="List{Car}"/> containing all cars to save.</param>
public static void SaveCars(string path, List<Car> cars)
{
using (StreamWriter writer = new StreamWriter(@path))
{
foreach (Car car in cars)
{
writer.WriteLine(car.ToCsvString());
}
}
}
/// <summary>
/// Adds a file path to the recent paths list.
/// If a path is already in the list, it is moved to the top.
/// Otherwise, it is added to the top.
/// Keeps only the 5 most recent paths.
/// </summary>
/// <param name="path">The path.</param>
public static void AddRecentPath(string path)
{
List<string> paths = new List<string>();
if (File.Exists(recentPathsFile))
{
using (StreamReader reader = new StreamReader(recentPathsFile))
{
string line;
while ((line = reader.ReadLine()) != null)
{
paths.Add(line);
}
}
}
paths.Remove(path);
paths.Insert(0, path);
if (paths.Count > 5)
{
paths = paths.Take(5).ToList();
}
using (StreamWriter writer = new StreamWriter(recentPathsFile))
{
foreach (string p in paths)
{
writer.WriteLine(p);
}
}
}
/// <summary>
/// Gets the recently opened file paths.
/// </summary>
/// <returns>
/// A <see cref="List{String}"/> containing the recent file paths.
/// </returns>
public static List<string> GetRecentPaths()
{
List<string> paths = new List<string>();
if (File.Exists(recentPathsFile))
{
using (StreamReader reader = new StreamReader(recentPathsFile))
{
string line;
while ((line = reader.ReadLine()) != null)
{
paths.Add(line);
}
}
}
return paths;
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CarManagerV3
{
// Could be made non-static / non-singleton if multiple collections are needed in the future. Not likely though.
/// <summary>
/// The <c>StateManager</c> class is responsible for managing the state of the car collection, including adding, removing, updating, and retrieving cars.
/// Fully static / singleton at the moment, as only one collection is needed.
/// </summary>
internal class StateManager
{
// Initialize global static list of cars
static List<Car> cars = new List<Car>();
// Initialize default file path for car data.
// TODO: If no recent file paths are found, prompt user to select a file path instead of using a hardcoded default in the program folder.
static string filePath = "cars.csv";
/// <summary>
/// Gets a car by its identifier.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>
/// A <see cref="Car"/> object if found; otherwise, null.
/// </returns>
public static Car GetCarById(int id)
{
cars = SafeManager.ReadCars(filePath);
return cars.FirstOrDefault(c => c.Id == id);
}
/// <summary>
/// Public getter and setter for the cars list. Used to have better control over the list.
/// </summary>
/// <value>
/// The cars.
/// </value>
public static List<Car> Cars { get { return cars; } set { cars = value; } }
/// <summary>
/// Adds a car to the collection.
/// </summary>
/// <param name="car">The car to add.</param>
public static void AddCar(Car car)
{
cars = SafeManager.ReadCars(filePath);
cars.Add(car);
SafeManager.SaveCars(filePath, cars);
}
/// <summary>
/// Removes a car from the collection.
/// </summary>
/// <param name="car">The car to remove.</param>
public static void RemoveCar(Car car)
{
cars = SafeManager.ReadCars(filePath);
Car existingCar = GetCarById(car.Id);
if (existingCar == null) return;
cars.Remove(existingCar);
SafeManager.SaveCars(filePath, cars);
}
/// <summary>
/// Updates a car in the collection. Identifies itself by its Id.
/// </summary>
/// <remarks>
/// If the car's Id has changed during editing, this will not work correctly. Keep Id immutable!
/// </remarks>
/// <param name="car">The car to update.</param>
public static void UpdateCar(Car car)
{
Car existingCar = GetCarById(car.Id);
if (existingCar != null)
{
int index = cars.IndexOf(existingCar);
cars[index] = car;
Console.WriteLine("Updated car: " + existingCar.Id);
SafeManager.SaveCars(filePath, cars);
}
}
/// <summary>
/// Creates a new car and adds it to the collection.
/// </summary>
/// <param name="make">The make.</param>
/// <param name="model">The model.</param>
/// <param name="year">The year.</param>
/// <param name="color">The color.</param>
/// <param name="mileage">The mileage.</param>
/// <param name="price">The price.</param>
/// <returns>
/// The newly created <see cref="Car"/> object.
/// </returns>
public static Car CreateCar(string make, string model, int year, string color, int mileage, decimal price)
{
cars = SafeManager.ReadCars(filePath);
int newId = cars.Count > 0 ? cars.Max(c => c.Id) + 1 : 1;
Car newCar = new Car(newId, make, model, year, color, mileage, price);
AddCar(newCar);
return newCar;
}
/// <summary>
/// Sets the file path used for saving and loading car data.
/// Called when user selects a new file path.
/// </summary>
/// <param name="path">The path.</param>
public static void setFilePath(string path)
{
filePath = path;
}
}
}

7
README.md Normal file
View File

@@ -0,0 +1,7 @@
# Car Manager 3
This is a simple school project in C# - Honestly you shouldn't be looking at this if you don't know me.
Hosted on [my own git server](https://git.jaro.digital/frozd/carmanager-3)
Documentation is in the works.