0

Уже дня два ищу информацию, но так ничего и не нашёл. Помогите отнять время в последний двух столбцах.

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

  • 1
    Что значит отнять? Удалить эти столбцы? Вычести из данных какое-то число? – Frehzy May 22 '22 at 19:12
  • 2
    typeof(string) в столбцах "Data", "Приход", "Уход" замените на typeof(DateTime). Тогда операции со временем станут элементарными. – Alexander Petrov May 22 '22 at 19:51
  • При этом задайте соответствующим столбцам в DataGridView желаемые форматы отображения: DefaultCellStyle.Format (и, при необходимости, DefaultCellStyle.FormatProvider). – Alexander Petrov May 22 '22 at 19:52
  • 2
    Ещё лучше вместо DataTable использовать типизированную коллекцию. – Alexander Petrov May 22 '22 at 19:59
  • @AlexanderPetrov можете пожалуйста подсказать, как их всё таки отнять?) – Saint Human May 22 '22 at 20:57
  • Что отнять, от чего отнять? – aepot May 22 '22 at 23:18
  • @aepot отнять столбец "уход" от столбца "приход", чтобы узнать сколько человек пробыл на работе – Saint Human May 22 '22 at 23:33
  • 1
    Сделать их DateTime и просто вычесть из одного другое используя обычный минус -. Кстати, DataTable вроде чуть ли не автоматом может по формулам колонки вычислять прям как Excel, но я не пробовал. – aepot May 22 '22 at 23:35
  • @aepot Я попробовал так dataGridView2[0, i].Value = dataGridView1[0, i].Value; dataGridView2[1, i].Value = Convert.ToDateTime(dataGridView1[4, i].Value) - Convert.ToDateTime(dataGridView1[3, i].Value); но мне даёт ошибку "System.ArgumentOutOfRangeException" – Saint Human May 23 '22 at 09:53
  • Сделайте нормальную привязку данных https://ru.stackoverflow.com/questions/1199457/%d0%9f%d1%80%d0%b8%d0%b2%d1%8f%d0%b7%d0%ba%d0%b0-%d0%b4%d0%b0%d0%bd%d0%bd%d1%8b%d1%85-%d0%b2-datagridview/1199692#1199692 и работайте с классами, а не с ячейками в dgv – Frehzy May 23 '22 at 10:09
  • Смотрю на ваш код и хочется сказать "Зачем вы сами себе палки в колёса втыкаете?". Создать DataTable, которую привязать к DGV. Зачем? Вы Иксзибит - вставляете таблицу в таблицу? Если вы планируете дальше работать с данными, то используете привязку, ссылку на пример которой я привёл выше. То, как вставляете вы, подойдёт, если данные просто нужно показать пользователю и забить на то, что с ними там дальше будет – Frehzy May 23 '22 at 10:12
  • @Frehzy мне данные надо брать с txt и туда же их сохранять – Saint Human May 23 '22 at 10:37
  • А в чём проблема десериализовать данные, если они в json хранятся? Да и каким-либо другим способом эти данные конвертировать в классы. Обновите вопрос и добавьте данные, которые у вас хранятся в текстовом файле – Frehzy May 23 '22 at 10:43
  • @Frehzy добавил – Saint Human May 23 '22 at 10:56
  • @SaintHuman Добавил ответ. Если ответ помог, поставьте, пожалуйста, галочку и стрелку вверх. Если будут вопросы, задавайте под ответом в комментариях – Frehzy May 23 '22 at 12:45

2 Answers2

0

Для начала, хотелось-бы отметить, что данные такого типа лучше хранить в базе данных. Нооо, раз уж у вас текстовый файл, то ладно.

Итак, рефакторинг вашего кода:

Паттерн, который использовал за основу 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&lt;Person&gt; Persons
{
    get =&gt; _persons;
    set
    {
        _persons = value;
        OnPropertyChanged(nameof(Persons));
    }
}

public BindingList&lt;PersonAnalysis&gt; PersonAnalyses
{
    get =&gt; _personAnalyses;
    set
    {
        _personAnalyses = value;
        OnPropertyChanged(nameof(PersonAnalyses));
    }
}

public Form1ViewModel(IEnumerable&lt;Person&gt; persons)
{
    _personsList = persons;
    Persons = new BindingList&lt;Person&gt;();
    PersonAnalyses = new BindingList&lt;PersonAnalysis&gt;();
    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 &gt; comingTime.Ticks &amp;&amp; person.GoingTime.Ticks &lt; 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&lt;Person&gt; 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 дней. Если знаете файлообменики получше, укажите, пожалуйста, ссылку

Результат:

Результат

Frehzy
  • 1,316
  • 1
    RoleEnum - неоправданно. Должности в конторе могут появляться новые с течением времени. Что тогда, перекомпилировать приложение? / Проект можно хранить на GitHub или Bitbucket. – Alexander Petrov May 23 '22 at 12:56
  • @AlexanderPetrov Касаемо RoleEnum я уже в ответе написал. Тем не менее, как я уже сказал выше, в идеале хранить эти данные в базе данных. Как вариант, можно всё-таки хранить данные в виде строки, но создать публичный метод в классе Person TryGetRoleEnum, который бы пытался вернуть Enum. Если-бы у него не получилось, то default. Если можете предложить другую идея - готов с радостью выслушать, т.к. сам полностью понимаю вашу претензию, но уйти от неё не могу уже из проекта в проект и найти альтернативу. Касаемо github - не хочу замусоривать его Я недавно только почистил от старых репозиториев – Frehzy May 23 '22 at 13:04
0

Так как вы не приняли другой ответ, значит он вас чем-то не устраивает. Хотя он показывает правильный способ - использование типизированных коллекций. Но вам, вероятно, пока это кажется слишком сложным.

Попробую дай свой ответ, основанный на слегка переделанном вашем коде.


Для работы с датой/временем следует использовать тип 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 = &quot;Открыть&quot; };
    saveButton = new Button { Parent = this, Text = &quot;Сохранить&quot;, Top = openButton.Bottom + 20 };
    calculateWorkingTimeButton = new Button { Parent = this, Text = &quot;Посчитать время работы&quot;, 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[&quot;WorkingTime&quot;] = row.Field&lt;DateTime&gt;(&quot;Departure&quot;) - row.Field&lt;DateTime&gt;(&quot;Arrival&quot;);
    }
}

private void Form1_Load(object? sender, EventArgs e)
{
    dataTable.Columns.Add(&quot;Date&quot;, typeof(DateTime));
    dataTable.Columns.Add(&quot;Name&quot;, typeof(string));
    dataTable.Columns.Add(&quot;Position&quot;, typeof(string));
    dataTable.Columns.Add(&quot;Arrival&quot;, typeof(DateTime));
    dataTable.Columns.Add(&quot;Departure&quot;, typeof(DateTime));
    dataTable.Columns.Add(&quot;WorkingTime&quot;, typeof(TimeSpan));

    dataGridView.DataSource = dataTable;

    dataGridView.Columns[&quot;Date&quot;].HeaderText = &quot;Дата&quot;;
    dataGridView.Columns[&quot;Name&quot;].HeaderText = &quot;Имя&quot;;
    dataGridView.Columns[&quot;Position&quot;].HeaderText = &quot;Должность&quot;;
    dataGridView.Columns[&quot;Arrival&quot;].HeaderText = &quot;Приход&quot;;
    dataGridView.Columns[&quot;Departure&quot;].HeaderText = &quot;Уход&quot;;
    dataGridView.Columns[&quot;WorkingTime&quot;].HeaderText = &quot;Время работы&quot;;

    dataGridView.Columns[&quot;Arrival&quot;].DefaultCellStyle.Format = &quot;H:mm&quot;;
    dataGridView.Columns[&quot;Departure&quot;].DefaultCellStyle.Format = &quot;H:mm&quot;;
    dataGridView.Columns[&quot;WorkingTime&quot;].DefaultCellStyle.Format = @&quot;h\:mm&quot;;
}

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(&quot;test.txt&quot;))
    {
        var values = line.Split('|');
        var row = dataTable.NewRow();

        row[&quot;Date&quot;] = DateTime.Parse(values[0]);
        row[&quot;Name&quot;] = values[1];
        row[&quot;Position&quot;] = values[2];
        row[&quot;Arrival&quot;] = DateTime.Parse(values[3]);
        row[&quot;Departure&quot;] = DateTime.Parse(values[4]);

        dataTable.Rows.Add(row);
    }
}

private void SaveData()
{
    using var writer = new StreamWriter(&quot;test.txt&quot;);

    foreach (DataRow row in dataTable.Rows)
    {
        writer.Write(row.Field&lt;DateTime&gt;(&quot;Date&quot;).ToString(&quot;dd.MM.yyyy&quot;) + &quot;|&quot;);
        writer.Write(row[&quot;Name&quot;] + &quot;|&quot;);
        writer.Write(row[&quot;Position&quot;] + &quot;|&quot;);
        writer.Write(row.Field&lt;DateTime&gt;(&quot;Arrival&quot;).ToString(&quot;H:mm:ss&quot;) + &quot;|&quot;);
        writer.WriteLine(row.Field&lt;DateTime&gt;(&quot;Departure&quot;).ToString(&quot;H:mm:ss&quot;));
    }
}

}

public class DoubleBufferedDataGridView : DataGridView
{
    protected override bool DoubleBuffered => true;
}

Вы не сказали главного: где, в каком месте нужно отнять время и что делать с результатом.
Я сделал подсчёт по нажатию на отдельную кнопку. Результат помещаю в дополнительную колонку.