DataGridViewComboBoxColumn не сохраняется в источнике данных, если не нажата клавиша ввода

У меня есть DataGridView, свойство DataSource которого привязано к DataTable. Указанная таблица данных заполняется вручную (не из базы данных). Я не буду вдаваться в подробности, как именно это происходит, потому что все работает безупречно, за исключением того, что я собираюсь описать. После создания DataTable он привязывается к DataGridView с помощью следующего кода:

DgvResults.DataSource = _data.ResultsData;

Последний столбец этого DataGridView — это DataGridViewComboBoxColumn, созданный с помощью следующего кода:

var col = new DataGridViewComboBoxColumn
        {
            Name = "newMedId",
            HeaderText = @"Med ID (NEW PIS)",
            DataPropertyName = "newMedId",
            DisplayMember = "ItemId",
            ValueMember = "ItemId",
            ReadOnly = false,
            Resizable = DataGridViewTriState.False,
            SortMode = DataGridViewColumnSortMode.Programmatic
        };

        col.Items.Add("");
        col.Items.Add("DELETE");
        _data.NewFormularyData.AsEnumerable().Select(r => r.Field<string>("ItemId")).ToList()
            .ForEach(m => col.Items.Add(m));

        DgvResults.Columns.Add(col);

По сути, это заполняет каждый ComboBox в этом столбце всеми идентификаторами из другого DataTable и добавляет пустое значение и значение DELETE сверху.

У меня также есть пользовательская программная сортировка в этом DataGridView. Я не буду публиковать код, так как не думаю, что он уместен, но, по сути, он запускается через событие ColumnHeaderMouseClick и просто сортирует базовый DataTable по любому столбцу, на который вы нажали, аннулирует свойство DataSource DataGridView и повторно привязывает его.

Все это отлично работает, ЗА ИСКЛЮЧЕНИЕМ этого: скажем, одно из раскрывающихся списков в DataGridViewComboBoxColumn пусто, и я обновляю значение на что-то другое вручную. Если я сортирую DataGridView сразу после выбора указанного значения, значение не обновляет базовую таблицу данных и теряется. Если я делаю то же самое, НО нажимаю клавишу ввода после выбора значения, оно обновляется правильно.

В моем приложении есть функция, которая экспортирует указанную таблицу данных в электронную таблицу Excel. Нажатие этой кнопки приводит к тому, что электронная таблица содержит значение, которое я обновил, даже если я не нажал Enter после его выбора. Он ведет себя так, как будто DataGridViewComboBoxCell должен потерять фокус, прежде чем он обновит DataTable, к которому он привязан. Немедленный щелчок по заголовку столбца для сортировки, по-видимому, не обеспечивает потерю фокуса, и значение не обновляется.

Как я могу немедленно обновить это значение в DataTable?

Вот MRE, воспроизводящий проблему

using System;
using System.ComponentModel;
using System.Data;
using System.Windows.Forms;

namespace MRE
{
    public partial class Form1 : Form
    {
        private DataTable dt;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        SetupDgv();
        CreateDT();

        Dgv.DataSource = dt;
    }

    private void CreateDT()
    {
        dt = new DataTable();

        dt.Columns.Add("test1", typeof(string));
        dt.Columns[0].ColumnName = "test1";
        dt.Columns[0].Caption = "test1";

        dt.Columns.Add("test2", typeof(string));
        dt.Columns[1].ColumnName = "test2";
        dt.Columns[1].Caption = "test2";

        var row = dt.NewRow();
        row[0] = 1;
        row[1] = 1;

        dt.Rows.Add(row);

        row = dt.NewRow();
        row[0] = 2;
        row[1] = 2;

        dt.Rows.Add(row);

        row = dt.NewRow();
        row[0] = 3;
        row[1] = 3;

        dt.Rows.Add(row);
    }

    private void SetupDgv()
    {
        Dgv.Columns.Add("test1", "test1");
        Dgv.Columns[0].DataPropertyName = "test1";
        Dgv.Columns[0].ReadOnly = true;
        Dgv.Columns[0].Resizable = DataGridViewTriState.False;
        Dgv.Columns[0].SortMode = DataGridViewColumnSortMode.Programmatic;

        var col = new DataGridViewComboBoxColumn
        {
            Name = "test2",
            HeaderText = "test2",
            DataPropertyName = "test2",
            ReadOnly = false,
            Resizable = DataGridViewTriState.False,
            SortMode = DataGridViewColumnSortMode.Programmatic
        };

        col.Items.Add("");
        col.Items.Add("DELETE");
        col.Items.Add("1");
        col.Items.Add("2");
        col.Items.Add("3");

        Dgv.Columns.Add(col);
    }

    private void Dgv_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
        var newColumn = Dgv.Columns[e.ColumnIndex];
        var oldColumn = Dgv.GetSortedColumnFromDataTable(dt);

        ListSortDirection direction;

        if (oldColumn != null)
        {
            if (oldColumn == newColumn && dt.GetDataTableSortOrder() == "ASC")
                direction = ListSortDirection.Descending;
            else
            {
                direction = ListSortDirection.Ascending;
                oldColumn.HeaderCell.SortGlyphDirection = SortOrder.None;
            }
        }
        else
            direction = ListSortDirection.Ascending;

        Dgv.ClearSortGlyphInAllColumnsExcept(e.ColumnIndex);

        dt.DefaultView.Sort = direction == ListSortDirection.Ascending ?
            Dgv.Columns[e.ColumnIndex].Name + " ASC" : Dgv.Columns[e.ColumnIndex].Name + " DESC";

        newColumn.HeaderCell.SortGlyphDirection =
            direction == ListSortDirection.Ascending ? SortOrder.Ascending : SortOrder.Descending;
    }
}

public static class DGVExtensions
{
    public static DataGridViewColumn GetSortedColumnFromDataTable(this DataGridView dgv, DataTable dt)
    {
        var dtSort = dt.DefaultView.Sort;

        string colName;
        if (dtSort.IndexOf(" ") != -1)
            colName = dtSort.Substring(0, dtSort.IndexOf(" "));
        else
            colName = dtSort;

        return dgv.Columns[colName];
    }

    public static void ClearSortGlyphInAllColumnsExcept(this DataGridView dgv, int index)
    {
        foreach (DataGridViewColumn col in dgv.Columns)
        {
            if (col.Index != index)
                col.HeaderCell.SortGlyphDirection = SortOrder.None;
        }
    }
}

public static class DTExtensions
{
    public static string GetDataTableSortOrder(this DataTable dt)
    {
        if (dt.DefaultView.Sort.IndexOf(" ") == -1)
            return "ASC";
        else
        {
            var startIndex = dt.DefaultView.Sort.IndexOf(" ") + 1;

            return dt.DefaultView.Sort.Substring(startIndex, dt.DefaultView.Sort.Length - startIndex).ToUpper();
        }
    }
}
}

person Martin    schedule 06.05.2021    source источник
comment
Можете ли вы сделать минимально воспроизводимый пример, демонстрирующий это? В моих тестах, если вы выбираете и изменяете одно из значений в одном из полей со списком, затем либо щелкаете заголовок столбца для сортировки, либо нажимаете какую-либо другую кнопку для экспорта в Excel, тогда значение в поле со списком обновляется как ожидается в источнике данных сетки.   -  person JohnG    schedule 06.05.2021
comment
Вы установили программный режим SortMode для всех столбцов и обрабатывали сортировку непосредственно в базовом источнике данных?   -  person Martin    schedule 07.05.2021
comment
Для EditMode установлено значение EditOnEnter, может ли это быть связано?   -  person Martin    schedule 07.05.2021
comment
Я не думаю, что это имело бы значение. Как только пользователь щелкнет заголовок столбца для сортировки, событие grids EndEdit сработает и обновит базовый источник данных еще до того, как будет вызван код сортировки. Обновляется ли базовый источник данных, если для сортировки задано значение, отличное от программного? Если это так, то может показаться, что проблема связана с сортировкой. Я хочу сказать… я уверен, что ячейка обновит базовый источник данных, как только фокус покинет ячейку.   -  person JohnG    schedule 07.05.2021
comment
У меня есть MRE для вас, но это 150 строк, а также код дизайнера. Как опубликовать?   -  person Martin    schedule 07.05.2021
comment
Разве вы не можете просто отредактировать свой вопрос и опубликовать сообщение? Я не знаком с какими-либо ограничениями. В моем тесте, чтобы попытаться воспроизвести описанную вами проблему; мой код составляет менее 200 строк кода… включая код дизайнера. О каком количестве строк кода вы говорите?   -  person JohnG    schedule 07.05.2021


Ответы (1)


В событии Dgv_ColumnHeaderMouseClick добавьте следующую строку кода в качестве первой строки кода в событии...

Dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
person JohnG    schedule 07.05.2021