1

Возникла такая проблема. У меня есть моя структура:

public struct GRIDVIEW
    {
        public List<string> kod;
        public List<string> time;
        public List<string> pers;
    }

Есть dataGridView с тремя колонками, которые я создал в конструкторе на элементе dataGridView, условно: А, В, С. Каким образом я могу привязать свою структуру как источник данных для датавьювера?

Просто dataGridView.DataSource = grid; не работает. После привязывания пустота. Даже ошибок не выдает.

aepot
  • 49,560
  • В ответах ниже не акцентировали внимание на одном важном моменте. Данные должны быть сделаны свойствами, а не полями: "public struct GRIDVIEW { public List kod { get; set; } public List time { get; set; } public List pers { get; set; } }". Поэтому и не работает. – rotabor Jun 08 '23 at 15:00

2 Answers2

4

Покажу вам настоящую привязку данных. Суть настоящей привязки в том, что вы вообще забываете о существовании DataGridView и работаете с данными.

Простой пример использования INotifyPropertyChanged и привязки данных DataBindings в Windows Forms. Обратите внимание, я нигде не использую DataSource и не обращаюсь к ячейкам, но оно работает.

public partial class Form1 : Form, INotifyPropertyChanged
{
    private BindingList<Person> _persons; // в коде не обращайтесь к полю _persons, вместо этого обращайтесь к свойству Persons
public BindingList&lt;Person&gt; Persons
{
    get =&gt; _persons;
    set
    {
        _persons = value;
        OnPropertyChanged();
    }
}

public Form1()
{
    InitializeComponent();

    // вот она, привязка данных
    // DataSource - название свойства контрола, которое надо привязать
    // this - класс, в котором находится свойство с данными
    // nameof(Persons) - то же самое что строка &quot;Persons&quot;, срока с названием самого свойства с данными
    dataGridView1.DataBindings.Add(&quot;DataSource&quot;, this, nameof(Persons));
}

private void Form1_Load(object sender, EventArgs e)
{
    Persons = new BindingList&lt;Person&gt;();

    Persons.Add(new Person { Name = &quot;Иван&quot;, SurName = &quot;Петров&quot; });
    Persons.Add(new Person { Name = &quot;Алексей&quot;, SurName = &quot;Сидоров&quot; });
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    =&gt; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

}

Класс с данными выглядит пострашнее, чем вы показали в вопросе, но по сути одна и та же методика повторяется везде.

public class Person : INotifyPropertyChanged
{
    private string _name;
    private string _surName;
// если включена автогенерация колонок, заголовки колонок берутся отсюда
[DisplayName(&quot;Имя&quot;)]
public string Name
{
    get =&gt; _name;
    set
    {
        _name = value;
        OnPropertyChanged();
    }
}

[DisplayName(&quot;Фамилия&quot;)]
public string SurName
{
    get =&gt; _surName;
    set
    {
        _surName = value;
        OnPropertyChanged();
    }
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    =&gt; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

}

Теперь можно с данными вытворять любые фокусы в совершенно любом месте кода и в любое время, например

Persons = new BindingList<Person>();
Persons.Add(new Person { Name = "Иван", SurName = "Петров" });
Persons[0].Name = "Вася";

А отображаемые данные будут меняться в табличке автоматически. И такую штуку можно провернуть с многими свойствами, например с Label.Text или Button.Top. Вы меняете данные, а контрол сам следит за их обновлением и обновляется, совершенно добровольно.

Кстати, соберетесь освоить WPF, UWP или Xamarin: там привязка данных - единственный способ выжить и писать вменяемые приложения, и работает точно так же как я выше показал. Так что не проходите мимо, это полезно и обязательно пригодится.

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

aepot
  • 49,560
  • Большое спасибо. Реализовал данный метод у себя. Но проблема есть. Сделал все по примеру, что вы указали. Но, как только доходит до dataGridView1.DataBindings.Add("DataSource", this, nameof(GRID));, выдает ошибку: Привязка к свойству или столбцу GRID для DataSource невозможна. Имя параметра dataMember. Автогенерацию колонок включил. – Алексей Шнякин Nov 05 '20 at 06:05
  • @АлексейШнякин а что такое GRID? Покажите код этого свойства. – aepot Nov 05 '20 at 06:21
  • Да, извините. Ошибся. Передавал класс, вместо свойства) Но теперь проблема в том, что в таблице появляется то ли первая, то ли последняя строка. Не пойму. Остальных данных после заполнения нет. Только одна строка. Заполняю результатом sql запроса. – Алексей Шнякин Nov 05 '20 at 06:55
  • @АлексейШнякин покажите, как заполняете. Поставьте точку останова на строчку после заполнения, посмотрите, что в отладке содержит свойство. Вероятно, привязка здесь не при чем, и проблема в заполнении. Что будет, если вы добавите строки вручную, как в примере? – aepot Nov 05 '20 at 06:56
  • MainViewGrid = new BindingList<GRID>(); MainViewGrid.Add(new GRID { _kod = reader[0].ToString().Trim(), _time = reader[1].ToString().Trim(), _pers = reader[2].ToString().Trim(), _eo_kod = reader[3].ToString().Trim(), _eo_name = reader[4].ToString().Trim(), _eo_displayName = $"({reader[3].ToString().Trim()}) {reader[4].ToString().Trim()}", _raw_kod = reader[5].ToString().Trim() }); Ну, все в цикле while (reader.Read()) – Алексей Шнякин Nov 05 '20 at 06:59
  • 1
    Да, я понял ошибку. Я в заполнении всегда обнуляю данные MainViewGrid = new BindingList<GRID>(); вынес за цикл. Спасибо большое вам за помощь! Теперь буду практиковать данный метод. – Алексей Шнякин Nov 05 '20 at 07:02
  • @АлексейШнякин _kod = reader[0].ToString().Trim() это же поле? Pаботайте с свойством вместо поля, типа Kod = .... – aepot Nov 05 '20 at 07:04
  • private string kod; [DisplayName("Код")] public string _kod { get => kod; set { kod = value; OnPropertyChanged(); } } – Алексей Шнякин Nov 05 '20 at 07:05
  • Я в попыхах чуть по другому назвал все. Не по феншую) Щас исправлю) – Алексей Шнякин Nov 05 '20 at 07:06
  • @АлексейШнякин тогда просто у вас с именованиями проблема, потому что я не понял что есть что из-за странных названий свойств. Соглашения о написании кода на C#, Правила именования – aepot Nov 05 '20 at 07:07
  • Ммм. А у меня еще вопрос. Вот, допустим, у меня 3 свойства в классе, которые я заполняю данными. Но в таблице нужно вывести только два из них. Каким образом это сделать? – Алексей Шнякин Nov 05 '20 at 07:12
  • @АлексейШнякин добавьте над свойством, которое надо скрыть из таблицы, аттрибут [Browsable(false)]. Либо по индексу колонки в самой DataGridView задайте ей ширину 0. Это в случае, если надо программно скрыть/показать. – aepot Nov 05 '20 at 07:14
  • 1
    Мое почтение!!) – Алексей Шнякин Nov 05 '20 at 07:19
  • @АлексейШнякин кстати, доки по ссылкам что я скинул, лучше читайте на английском, перевод там кривоват. – aepot Nov 05 '20 at 07:22
  • @АлексейШнякин бонус, рекомендую – aepot Nov 05 '20 at 07:29
  • Спасибо. Обязательно ознакомлюсь. У меня еще вопрос возник. Я попробовал провернуть использование экого способа для привязки данных к ComboBox'у. У меня 3 свойства, два из которых я скрыл с помощью [Browsable(false)]. Я подумал, что так как осталось одно свойство видимое, он его и будет отображать в выпадающем списку комбобокса. Данные добавились, в комбобоксе по колличеству элементов все норм. Но во всех элементах .Text = MainEdit.DataGridComboBox MainEdit- имя решения, DataGridComboBox - имя класса. – Алексей Шнякин Nov 05 '20 at 09:38
  • То есть, данные наполняются. Но, возможно, я что-то не так делаю касательно привязки к ComboBox. – Алексей Шнякин Nov 05 '20 at 09:40
  • @АлексейШнякин перед биндингом добавьте строчку comboBox.DisplayMember = nameof(DataGridComboBox.<имя вашего свойства>); Либо в дизайнере впишите туда же имя свойства без "DataGridComboBox.", просто имя. Там еще есть нюансы – aepot Nov 05 '20 at 10:37
  • Комментарии не предназначены для расширенной дискуссии; разговор перемещён в чат. – Barmaley Nov 05 '20 at 12:40
  • @АлексейШнякин полечить тормоза можно попробовать вот так. Помимо этого для отображения какой-то части коллекции, например через фильтрацию, можно воткнуть как прокси-сущность между контролом и данными BindingSource, и через него управлять отображением. – aepot Nov 21 '20 at 01:16
  • @aepot Ваше решение применять с MVVM или(и) MVP? Как ваше решение применять с MVVM или(и) MVP? – eusataf Aug 24 '23 at 07:58
  • @eusataf Winforms в последних версиях за счет доработки API контролов можно и MVVM применять полноценно, а так MVP. WPF - только MVVM. – aepot Aug 24 '23 at 11:45
  • @aepot Вы рекомендовали: Особенности реализации MVP для Windows Forms |habr.com 1. Это актуально для Framework 4.7-8? 2. Может за прошедшие годы вы наши пример проще для Framework 4.7-8? Или не ныть и прорабатывать этот пример? – eusataf Aug 30 '23 at 08:57
  • @eusataf Framework 4.x устарел, а так пробуйте, версия особо не имеет значения. Я всего-лишь писал, что в свежей версии проще применять MVVM, а MVP подходит везде. – aepot Aug 30 '23 at 09:01
  • @aepot 1. Как получить строку dataGridView1 выбранную пользователем в данном решении? dataGridView1.SelectionChanged? или можно как-то по моднее и по понтовее? 2. Можно чтобы строка попадала в свойство формы (или в VM)? – eusataf Sep 19 '23 at 15:16
  • Я не помню, 100 лет на винформах ничего не писал. – aepot Sep 19 '23 at 15:28
0

Лучше через класс.

Вам нужно свойство DataPropertyName. Пусть есть класс, описывающий ваши данные:

public class MyClass
{
    public MyClass(string a, string b, string c)
    {
        A = a;
        B = b;
        C = c;
    }
public string A { get; set; }

public string B { get; set; }

public string C { get; set; }

}

В DataGridView добавим колонки:

        dataGridView.Columns.Add(new DataGridViewTextBoxColumn
                                     {
                                         DataPropertyName = "A",
                                         HeaderText = "Заголовок 1"
                                     });
        dataGridView.Columns.Add(new DataGridViewTextBoxColumn
                                     {
                                         DataPropertyName = "B",
                                         HeaderText = "Заголовок 2"
                                     });
        dataGridView.Columns.Add(new DataGridViewTextBoxColumn
                                     {
                                         DataPropertyName = "C",
                                         HeaderText = "Заголовок 3"
                                     });

Создадим коллекцию и укажем ее как источник данных:

        var data = new List<MyClass>
                       {
                           new MyClass("1", "2", "3"),
                           new MyClass("4", "5", "6"),
                           new MyClass("7", "8", "9")
                       };
        dataGridView.DataSource = data;

https://ru.stackoverflow.com/questions/311912/datagridview-привязка-данных