Проблема события DevExpress XtraGrid FocusedRowChanged при смене источника данных

Эта проблема беспокоила меня несколько лет, и, возможно, кто-то здесь знает простое решение, так как я только что снова столкнулся с этим.

ВОПРОС: Есть ли способ заставить XtraGrid «забыть» текущий сфокусированный индекс строки до того, как сетке будет назначен новый (другой) источник данных?

ПРЕДПОСЫЛКИ Мы используем XtraGrid в качестве своего рода контроллера для того, что отображается на другой панели мультипанели Winform.

Теперь представьте себе гипотетический сценарий, в котором источник данных XtraGrid продолжает меняться в зависимости от пунктов меню. Пункт меню 1 заполняет сетку списком сегодняшних блюд в столовой: Id, Name. Пункт меню 2 заполняет сетку списком Клиентов, которым пользователь должен позвонить в этот день: ID, Имя. Важно то, что это отдельные отдельные источники данных, а источник данных сетки назначается и переназначается.

ВАЖНЫЙ ФАКТ ДЛЯ ЭТОГО ВОПРОСА: Мы хотим, чтобы событие сетки FocusedRowChanged было единственным местом, где мы фиксируем выбор пользователя в сетке контроллера. Мы являемся магазином «без спагетти-кода». FocusedRowChanged лучше, чем событие щелчка, поскольку он также обрабатывает навигацию с помощью клавиатуры. Строка с фокусом содержит идентификатор записи детали, которую нам нужно извлечь из базы данных для отображения в панели №2. Это работает в большинстве случаев.

Вот как это не работает: допустим, в данный день список клиентов, с которыми должен связаться пользователь, содержит только одну строку. Таким образом, первая (и единственная) строка в сетке — это сфокусированная строка. Теперь предположим, что пользователь подходит к меню и выбирает пункт меню, чтобы отобразить основные блюда в столовой за день. Когда пользователь щелкает первый элемент в списке Entrees, событие FocusedRowChanged НЕ срабатывает, потому что сетка сохранила память индекса сфокусированной строки из предыдущего источника данных. Строка index в фокусе не изменилась. Таким образом, выбор пользователя ничего не вызывает.

Я попытался заставить DevExpress предложить еще один режим, ориентированный на объекты строк (в отличие от подхода, ориентированного на индексы строк), при котором каждая строка в сетке будет иметь GUID, а событие FocusedRowChanged будет срабатывать всякий раз, когда GUID текущего сфокусированная строка отличалась от идентификатора GUID ранее сфокусированной строки, независимо от того, совпадал ли индекс сфокусированной строки. Это позволит динамически изменять источник данных и обеспечит желаемое поведение. Но они возражали.

Итак, я снова задам свой вопрос: есть ли способ заставить XtraGrid «забыть» текущий сфокусированный индекс строки до того, как сетке будет назначен новый источник данных?


person Tim    schedule 05.05.2011    source источник


Ответы (4)


Я думаю, что лучшим решением этой проблемы является создание нового объекта GridView и переопределение его метода DoChangeFocusedRowInternal. Ниже вы найдете реализацию этого метода по умолчанию. Все, что вам нужно сделать, это изменить отмеченную строку в соответствии с вашими потребностями. Кроме того, взгляните на Как создать класс-потомок GridView и зарегистрировать его. для использования во время разработки, она содержит некоторую полезную информацию.

public class MyGridView : GridView {
        protected override void DoChangeFocusedRowInternal(int newRowHandle, bool updateCurrentRow) {
            if(this.lockFocusedRowChange != 0) return;
            if(!IsValidRowHandle(newRowHandle))
                newRowHandle = DevExpress.Data.DataController.InvalidRow;
            if(FocusedRowHandle == newRowHandle) return; // <<<<<<
            int currentRowHandle = FocusedRowHandle;
            BeginLockFocusedRowChange();
            try {
                DoChangeFocusedRow(FocusedRowHandle, newRowHandle, updateCurrentRow);
            }
            finally {
                EndLockFocusedRowChange();
            }
            RaiseFocusedRowChanged(currentRowHandle, newRowHandle);
        }
    }

ОБНОВЛЕНИЕ

Мой код:

namespace MyXtraGrid {

        public class MyGridControl : GridControl {
            protected override BaseView CreateDefaultView() {
                return CreateView("MyGridView");
            }
            protected override void RegisterAvailableViewsCore(InfoCollection collection) {
                base.RegisterAvailableViewsCore(collection);
                collection.Add(new MyGridViewInfoRegistrator());
            }
        }

        public class MyGridViewInfoRegistrator : GridInfoRegistrator {
            public override string ViewName { get { return "MyGridView"; } }
            public override BaseView CreateView(GridControl grid) {
                return new MyGridView(grid as GridControl);
            }
        }
        public class MyGridView : GridView {
            public MyGridView(GridControl ownerGrid) : base(ownerGrid) { }
            public MyGridView() { }


            protected virtual bool RowEqual(int focusedRowHandle, int newRowHandle) {
                if(IsDesignMode)
                    return focusedRowHandle == newRowHandle;
                DataRow row1 = GetDataRow(focusedRowHandle);
                DataRow row2 = GetDataRow(newRowHandle);
                return row1 == row2;
            }

            protected override void DoChangeFocusedRowInternal(int newRowHandle, bool updateCurrentRow) {
                if(this.lockFocusedRowChange != 0) return;
                if(!IsValidRowHandle(newRowHandle))
                    newRowHandle = DevExpress.Data.DataController.InvalidRow;
                if(RowEqual(FocusedRowHandle, newRowHandle))
                    return;
                int currentRowHandle = FocusedRowHandle;
                BeginLockFocusedRowChange();
                try {
                    DoChangeFocusedRow(FocusedRowHandle, newRowHandle, updateCurrentRow);
                }
                finally {
                    EndLockFocusedRowChange();
                }
                RaiseFocusedRowChanged(currentRowHandle, newRowHandle);
            }
        }
    }
person DevExpress Team    schedule 05.05.2011
comment
Следуя инструкциям из статьи базы знаний, я могу ввести событие DoChangeFocusedRowInternal (см. выше) в отладчике и выполнить код пошагово. Но моя сетка никогда не визуализируется визуально. Что мне не хватает? - person Tim; 11.05.2011
comment
И когда я пытаюсь сбросить MyGrid (наследующий согласно КБ) из панели инструментов на форму, VS2010 дает сбой. Я бы хотел, чтобы DevExpress мог просто вызвать этот небольшой возврат оптимизации (FocusedRowHandle == newRowHandle);) по умолчанию, если только он не отключен с помощью некоторого свойства времени разработки, которое разработчик может установить, когда источники данных будут изменены, в соответствии с исходным вопросом. - person Tim; 11.05.2011
comment
Я не знаю код, который вы используете, поэтому я не могу его комментировать. в любом случае, я обновил свой ответ всеми необходимыми классами. Я надеюсь, это поможет вам. - person DevExpress Team; 11.05.2011
comment
Изменения позволяют перетаскивать производный элемент управления на форму. Но код в методе RowEqual не работает (и логически НЕ МОЖЕТ работать), потому что focusRowHandle равен 0 и newRowHandle также равен 0, поэтому метод GetDataRow() должен возвращать ТУ ЖЕ строку. Помните, что исходный источник данных был заменен новым, и на этом этапе кода вы можете ссылаться только на строку из ТЕКУЩЕГО источника данных. Должно быть что-то вроде RowGUID в дополнение к (int) RowHandle. - person Tim; 12.05.2011
comment
@ Тим, мой код - всего лишь пример того, как можно реализовать эту функцию. Вы лучше знаете задачу, и поэтому, если этот код работает, не должно быть проблем с его изменением в соответствии с вашими требованиями... - person DevExpress Team; 12.05.2011
comment
Я думаю, вы умываете руки. Это ограничение дизайна сетки. В рамках метода DoChangeFocusedRowInternal невозможно узнать, что FocusedRowHandle указывает на строку, которая принадлежала предыдущему источнику данных. Когда оба этих дескриптора содержат одно и то же значение, т. е. 0, событие FocusedRowChanged должно срабатывать, ЕСЛИ источник данных изменился. Если вы создаете экземпляры двух источников данных, каждый из которых содержит одну строку, и поочередно назначаете эти источники данных сетке, нет простого способа использовать событие FocusedRowChanged, поскольку дескриптор сфокусированной строки никогда не меняется. - person Tim; 12.05.2011
comment
Должен быть способ узнать (в рамках метода DoChangeFocusedRowInternal и без использования какой-то дрянной глобальной переменной), что FocusedRowHandle и newRowHandle указывают на строки, принадлежащие совершенно разным источникам данных. Либо так, либо вместо целочисленных дескрипторов строк дескрипторы строк должны быть GUID. - person Tim; 12.05.2011
comment
На самом деле, я просто хотел, чтобы вы показали наилучший способ реализации требуемой функциональности самостоятельно с меньшими усилиями. Мне кажется, создание производного элемента управления — лучшее решение. Наконец, обратите внимание, что GridControl также предоставляет событие DataSourceChanged. Может быть, это то, что вы ищете? - person DevExpress Team; 13.05.2011
comment
Привет Тим, я нашел метод, который должен идеально работать для вас - OnCurrentControllerRowObjectChanged. Он защищен виртуальным в классе BaseView. Итак, все, что вам нужно сделать, это создать производное представление и использовать свой код в этом методе. Он должен выполняться только при изменении объекта записи данных... - person DevExpress Team; 16.05.2011
comment
Спасибо за эту дополнительную информацию. Я это попробую! - person Tim; 08.09.2011

Тим, у меня была точно такая же проблема, когда в сетке была только одна строка данных, а затем менялись источники данных. Я решил это, установив gridview.FocusedRowHandle = -1 после установки нового источника данных.

person rob    schedule 12.05.2011

В похожей ситуации подписываюсь на

FocusedRowObjectChanged

событие (с использованием DevExpress 16.1).

person Thomas853    schedule 19.06.2017
comment
Еще одна причина для нас, чтобы обновить. Это поможет мне сделать дело. Спасибо. - person Tim; 01.07.2017

Вы можете подписаться на событие DataSourceChanged, которое будет срабатывать при изменении источника данных (как вы уже догадались!), чтобы затем вы могли использовать объект GetFocusedObject() и отображать соответствующие элементы для другой сетки...

person DevExpresser    schedule 04.11.2011
comment
Да, но DataSourceChanged приводит к спагетти. Суть в том, чтобы сделать это в ОДНОМ месте, ОДНИМ способом. ‹‹K.I.S.S.›› Я хочу задать простой вопрос: смотрю ли я на другой ряд? DevExpress задает более сложный вопрос: занимает ли эта строка другую позицию в массиве? Поскольку базовый источник данных может измениться, использование позиции массива в качестве индикатора одинаковости/различия не так хорошо, как использование чего-то ВНУТРЕННЕГО для объекта строки, например GUID строки. - person Tim; 11.11.2011