C# отнимание столбцов DataGridView
Уже дня два ищу информацию, но так ничего и не нашёл. Помогите отнять время в последний двух столбцах.
public Form1()
{
InitializeComponent();
}
DataTable table = new DataTable();
private void Form1_Load(object sender, EventArgs e)
{
textBoxAGE.Format = DateTimePickerFormat.Time;
textBoxID.Format = DateTimePickerFormat.Time;
table.Columns.Add("Data", typeof(string));
table.Columns.Add("FIO", typeof(string));
table.Columns.Add("Dolzhnost", typeof(string));
table.Columns.Add("Приход", typeof(string));
table.Columns.Add("Уход", typeof(string));
dataGridView1.DataSource = table;
}
private void button1_Click(object sender, EventArgs e)
{
table.Rows.Add(kalendar.Value.ToString("dd.MM.yyyy"), textBoxFN.Text, textBoxLN.Text, textBoxAGE.Value.ToString("t"), textBoxID.Value.ToString("t"));
dataGridView1.DataSource = table;
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
button2_Click(sender, e);
}
private void button2_Click(object sender, EventArgs e)
{
string[] lines = File.ReadAllLines(@"G:\db.txt");
string[] values;
for (int i = 0; i < lines.Length; i++)
{
values = lines[i].ToString().Split('|');
string[] row = new string[values.Length];
for (int j = 0; j < values.Length; j++)
{
row[j] = values[j].Trim();
}
table.Rows.Add(row);
}
}
private void button3_Click(object sender, EventArgs e)
{
TextWriter writer = new StreamWriter(@"G:\db.txt");
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
for (int j = 0; j < dataGridView1.Columns.Count; j++)
{
writer.Write(dataGridView1.Rows[i].Cells[j].Value.ToString()+"|");
}
writer.WriteLine("");
writer.WriteLine("");
}
writer.Close();
MessageBox.Show("Data Exported");
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
button3_Click(sender, e);
}
private void button4_Click(object sender, EventArgs e)
{
}
Данные из текстового файла
21.05.2022|Алексеев П. А.|Инженер|13:23:00|20:23:00
21.05.2022|Мельников Л. Б.|Сис.Админ|12:51:00|18:51:00
21.05.2022|Захарова В. М.|Уборщица|10:01:00|22:05:00
21.05.2022|Шаров C.T|Зам|8:56:00|21:25:00
21.05.2022|Жарко И.И|Директор|7:59:00|20:53:00
Ответы (2 шт):
Для начала, хотелось-бы отметить, что данные такого типа лучше хранить в базе данных. Нооо, раз уж у вас текстовый файл, то ладно.
Итак, рефакторинг вашего кода:
Паттерн, который использовал за основу MVVM - MVVM информация.
Создам несколько классов, которые в дальнейшем нам будут помогать.Что такое класс:
1 - класс Person, в котором будет храниться информация о пользователях из вашего текстового файла.
public class Person : ObservableObject
{
private DateTime _createDateTime;
private string _fullName;
private RoleEnum _role;
private DateTime _comingTime;
private DateTime _goingTime;
[DisplayName("Дата создания")]
public DateTime CreateDateTime
{
get => _createDateTime;
private set
{
_createDateTime = value;
OnPropertyChanged(nameof(CreateDateTime));
}
}
[DisplayName("ФИО")]
public string FullName
{
get => _fullName;
private set
{
_fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
[DisplayName("Должность")]
public RoleEnum Role
{
get => _role;
private set
{
_role = value;
OnPropertyChanged(nameof(Role));
}
}
[DisplayName("Время прихода")]
public DateTime ComingTime
{
get => _comingTime;
private set
{
_comingTime = value;
OnPropertyChanged(nameof(ComingTime));
}
}
[DisplayName("Время ухода")]
public DateTime GoingTime
{
get => _goingTime;
private set
{
_goingTime = value;
OnPropertyChanged(nameof(GoingTime));
}
}
public Person(DateTime createDateTime, string fullName, RoleEnum role, DateTime comingTime, DateTime goingTime)
{
CreateDateTime = createDateTime;
FullName = fullName;
Role = role;
ComingTime = CreateDateTimeByOnlyTime(comingTime);
GoingTime = CreateDateTimeByOnlyTime(goingTime);
}
private DateTime CreateDateTimeByOnlyTime(DateTime time) =>
new(CreateDateTime.Year, CreateDateTime.Month, CreateDateTime.Day, time.Hour, time.Minute, time.Second);
}
2 - класс PersonAnalysis, в котором будет храниться обработанная информация. Над названием стоит поработать и чуть подправить, чтобы оно корректнее отображало истинное его предназначение. Оставлю это на вас
public class PersonAnalysis : ObservableObject
{
private string _fullName;
private TimeSpan _timeAtWork;
[DisplayName("ФИО")]
public string FullName
{
get => _fullName;
private set
{
_fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
[DisplayName("Время на работе")]
public TimeSpan TimeAtWork
{
get => _timeAtWork;
private set
{
_timeAtWork = value;
OnPropertyChanged(nameof(TimeAtWork));
}
}
public PersonAnalysis(string fullName, TimeSpan _timeAtWork)
{
FullName = fullName;
TimeAtWork = _timeAtWork;
}
}
Далее, создам перечисление, в котором буду хранить доступные должности Enum:
public enum RoleEnum
{
Инженер = 0,
Директор = 1,
СисАдмин = 2,
Уборщица = 3
}
Да, я знаю, что должности можно храниться прямо с строке (string), но мне гораздо удобнее использовать. Этот момент является не обязательным. Его вполне можно выпилить и оставить просто тип string
Далее, реализую класс ObservableObject, который будет являться дочерним интерфейса INotifyPropertyChanged и реализовывать его Абстрактные классы INotifyPropertyChanged:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Далее, создам ViewModel, в котором будут храниться и изменяться данные:
internal class Form1ViewModel : ObservableObject
{
private readonly IEnumerable<Person> _personsList;
private BindingList<Person> _persons;
private BindingList<PersonAnalysis> _personAnalyses;
public BindingList<Person> Persons
{
get => _persons;
set
{
_persons = value;
OnPropertyChanged(nameof(Persons));
}
}
public BindingList<PersonAnalysis> PersonAnalyses
{
get => _personAnalyses;
set
{
_personAnalyses = value;
OnPropertyChanged(nameof(PersonAnalyses));
}
}
public Form1ViewModel(IEnumerable<Person> persons)
{
_personsList = persons;
Persons = new BindingList<Person>();
PersonAnalyses = new BindingList<PersonAnalysis>();
LoadPerson();
var comingTime = new DateTime(2022, 05, 21, 0, 0, 0);
var goingTime = new DateTime(2022, 05, 21, 22, 5, 0);
UpdatePersonAnalysesByDate(comingTime, goingTime);
}
private void LoadPerson()
{
foreach (var person in _personsList)
Persons.Add(person);
}
private void UpdatePersonAnalysesByDate(DateTime comingTime, DateTime goingTime) //привяжите кнопку к данному методу и всё будет работать
{
PersonAnalyses.Clear();
foreach (var person in _personsList)
if (person.ComingTime.Ticks > comingTime.Ticks && person.GoingTime.Ticks < goingTime.Ticks)
PersonAnalyses.Add(new PersonAnalysis(person.FullName, person.GoingTime.Subtract(person.ComingTime)));
}
}
Далее, напишу конвертер из текстового файла в класс Person:
static IEnumerable<Person> ConvertToObject(IEnumerable<string> list)
{
foreach (var item in list)
{
var properties = item.Split('|');
yield return new Person(DateTime.Parse(properties[0]),
properties[1],
(RoleEnum)Enum.Parse(typeof(RoleEnum), properties[2]),
DateTime.ParseExact(properties[3], "HH:mm:ss", CultureInfo.InvariantCulture),
DateTime.ParseExact(properties[4], "HH:mm:ss", CultureInfo.InvariantCulture));
}
}
Честно, не знал, как правильнее это можно было-бы сделать, поэтому решил оставить так. Думал через Regex, но всё равно бы всё сводилось к подобной конструкции. Не карайте касаемо этого метода. По-хорошему, данные в IEnumerable должны попадать из базы данных, или на крайняк из сериализованного json файла.
Далее, создадим привязки на самой форме Ещё один пример привязок к dgv:
public partial class Form1 : Form
{
private readonly Form1ViewModel _viewModel;
public Form1(IEnumerable<Person> persons)
{
InitializeComponent();
_viewModel = new Form1ViewModel(persons);
dataGridView1.DataBindings.Add(nameof(DataGridView.DataSource), _viewModel, nameof(_viewModel.Persons));
dataGridView2.DataBindings.Add(nameof(DataGridView.DataSource), _viewModel, nameof(_viewModel.PersonAnalyses));
}
}
Итак, логика программы следующая:
Данные (из _viewModel) привязываются к свойству Свойства DataSource у DataGridView (далее, dgv). Таким образом, DGV лишь отображает данные, которые хранятся в указанном свойстве. Сами же данные мы изменяем из Form1ViewModel.
P.S. Я не реализовывал абсолютно всё, что есть у вас на форме. Вам нужно добавить кнопку и добавить некоторую логику обработки dateTimePicker для метода UpdatePersonAnalysesByDate. Под капотом он сам всё сделает. Вам нужно лишь дать ему нужные данные. Я для этого создал 2 переменные.
P.S.S. Изначально создавал консольное приложение, но потом решил сделать полностью, но пересоздавать проект было лень, поэтому в Program существует некоторый костыль :)
Ссылка на проект: https://dropmefiles.com/aed3B . Будет доступен только 14 дней. Если знаете файлообменики получше, укажите, пожалуйста, ссылку
Результат:
Так как вы не приняли другой ответ, значит он вас чем-то не устраивает. Хотя он показывает правильный способ - использование типизированных коллекций. Но вам, вероятно, пока это кажется слишком сложным.
Попробую дай свой ответ, основанный на слегка переделанном вашем коде.
Для работы с датой/временем следует использовать тип DateTime вместо string. Тогда операции со временем станут элементарными.
При этом зададим соответствующим столбцам в DataGridView желаемые форматы отображения: DefaultCellStyle.Format (и, при необходимости, DefaultCellStyle.FormatProvider).
public partial class Form1 : Form
{
DoubleBufferedDataGridView dataGridView;
Button openButton;
Button saveButton;
Button calculateWorkingTimeButton;
DataTable dataTable = new DataTable();
public Form1()
{
//InitializeComponent();
Size = new Size(1000, 500);
dataGridView = new DoubleBufferedDataGridView { Parent = this, Dock = DockStyle.Right, Width = 700 };
openButton = new Button { Parent = this, Text = "Открыть" };
saveButton = new Button { Parent = this, Text = "Сохранить", Top = openButton.Bottom + 20 };
calculateWorkingTimeButton = new Button { Parent = this, Text = "Посчитать время работы", Top = saveButton.Bottom + 20 };
this.Load += Form1_Load;
openButton.Click += OpenButton_Click;
saveButton.Click += SaveButton_Click;
calculateWorkingTimeButton.Click += CalculateWorkingTimeButton_Click;
}
private void CalculateWorkingTimeButton_Click(object? sender, EventArgs e)
{
foreach (DataRow row in dataTable.Rows)
{
row["WorkingTime"] = row.Field<DateTime>("Departure") - row.Field<DateTime>("Arrival");
}
}
private void Form1_Load(object? sender, EventArgs e)
{
dataTable.Columns.Add("Date", typeof(DateTime));
dataTable.Columns.Add("Name", typeof(string));
dataTable.Columns.Add("Position", typeof(string));
dataTable.Columns.Add("Arrival", typeof(DateTime));
dataTable.Columns.Add("Departure", typeof(DateTime));
dataTable.Columns.Add("WorkingTime", typeof(TimeSpan));
dataGridView.DataSource = dataTable;
dataGridView.Columns["Date"].HeaderText = "Дата";
dataGridView.Columns["Name"].HeaderText = "Имя";
dataGridView.Columns["Position"].HeaderText = "Должность";
dataGridView.Columns["Arrival"].HeaderText = "Приход";
dataGridView.Columns["Departure"].HeaderText = "Уход";
dataGridView.Columns["WorkingTime"].HeaderText = "Время работы";
dataGridView.Columns["Arrival"].DefaultCellStyle.Format = "H:mm";
dataGridView.Columns["Departure"].DefaultCellStyle.Format = "H:mm";
dataGridView.Columns["WorkingTime"].DefaultCellStyle.Format = @"h\:mm";
}
private void OpenButton_Click(object? sender, EventArgs e)
{
LoadData();
}
private void SaveButton_Click(object? sender, EventArgs e)
{
SaveData();
}
private void SaveToolStripMenuItem_Click(object sender, EventArgs e)
{
SaveData();
}
private void LoadData()
{
foreach (var line in File.ReadLines("test.txt"))
{
var values = line.Split('|');
var row = dataTable.NewRow();
row["Date"] = DateTime.Parse(values[0]);
row["Name"] = values[1];
row["Position"] = values[2];
row["Arrival"] = DateTime.Parse(values[3]);
row["Departure"] = DateTime.Parse(values[4]);
dataTable.Rows.Add(row);
}
}
private void SaveData()
{
using var writer = new StreamWriter("test.txt");
foreach (DataRow row in dataTable.Rows)
{
writer.Write(row.Field<DateTime>("Date").ToString("dd.MM.yyyy") + "|");
writer.Write(row["Name"] + "|");
writer.Write(row["Position"] + "|");
writer.Write(row.Field<DateTime>("Arrival").ToString("H:mm:ss") + "|");
writer.WriteLine(row.Field<DateTime>("Departure").ToString("H:mm:ss"));
}
}
}
public class DoubleBufferedDataGridView : DataGridView
{
protected override bool DoubleBuffered => true;
}
Вы не сказали главного: где, в каком месте нужно отнять время и что делать с результатом.
Я сделал подсчёт по нажатию на отдельную кнопку. Результат помещаю в дополнительную колонку.

