none
WPF DataGrid + TabControl = самопроизвольный скроллинг RRS feed

  • Вопрос

  • Всем доброго времени суток.

    Столкнулся с одной проблемой в DataGrid. Не соображу в чем её причина. Помогите, пожалуйста, разобраться!

    Есть TabControl с несколькими вкладками.
    На одной из них находится DataGrid с привязанным к нему списком объектов. Выбрана одна из строк.
    Когда переходишь на другую вкладку и возвращаешься назад, то если затем перевести фокус на DataGrid с помощью клавиатуры (Tab'ом), то первая отображаемая строка смещается вверх.
    Тоже самое происходит, если по возвращении на прежнюю вкладку DataGrid сразу получает фокус. Если нажать стрелку вверх/вниз, то происходит смещение скроллинга и за выбранную принимается первая отображаемая (после смещения) строка, а не та, что была выбрана изначально.

    Класс "список объектов" унаследован от ObservableCollection<Объект>.
    Объекты в списке - от INotifyPropertyChanged.


    22 марта 2011 г. 12:26

Ответы

  • Здравстуйте, посмотрите топик на анлийском форуме - TabNavigation into, inside and out of the WPF DataGrid. Там как раз разбирается как сохранить в фокусе выбранный элемент.


    Для связи [mail]
    • Помечено в качестве ответа Abolmasov Dmitry 27 марта 2011 г. 17:09
    23 марта 2011 г. 10:36
  •  

    Спасибо за ссылку!

    Пока только взглядом пробежался. Там используется DataGridCellsPresenter, который (если я не ошибаюсь) есть только в .NET 4. Я же пишу под версию 3.5. Но в целом проблема описана та же.

    Вот мой вариант решения. Получился несколько громоздкий, но с задачей справляется - в рамках условий: переход Tab'ом не учитывается (заблокирован), выделяются строки целиком (выбирать отдельные ячейки не требуется), при переходе на вкладку TabControl'a DataGrid сразу получает фокус.

    Переопределяем DataGrid и CurrentCellProperty. Добавляем вспомогательные свойства для блокировки нежелательных изменений CurrentCellProperty и перемещений Scroll'a.

     public class MyDataGrid : DataGrid
     {
      //Положение Scroll'а 
     private double offSet = 0; 
    internal
    double OffSet { get { return offSet; } set { offSet = value; } } //Блокирование выбора ячейки, отличной от текущей internal bool CurrentCellBlocked { get { return StaticCurrentCellBlocked; } set { StaticCurrentCellBlocked = value; if (value) { //Сохранение положения Scroll'а DataGrid var border = VisualTreeHelper.GetChild(this, 0) as Decorator; if (border != null) { var scroll = border.Child as ScrollViewer; OffSet = scroll.VerticalOffset; } } } } private static bool staticCurrentCellBlocked = true; private static bool StaticCurrentCellBlocked { get { return staticCurrentCellBlocked; } set { staticCurrentCellBlocked = value; } } static MyDataGrid() { DataGrid.CurrentCellProperty.OverrideMetadata( typeof(MyDataGrid), new FrameworkPropertyMetadata(new DataGridCellInfo(),
    new PropertyChangedCallback(OnCurrentCellPropertyChanged), new CoerceValueCallback(OnCurrentCellCoerce))); } private static void OnCurrentCellPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { //... } private static object OnCurrentCellCoerce(DependencyObject d, object Value) { MyDataGrid dataGrid = (MyDataGrid)d; if (StaticCurrentCellBlocked) { return new DataGridCellInfo(dataGrid.SelectedItem, dataGrid.Columns[0]); } return Value; } protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e) { if (this.SelectedIndex != -1) { this.CurrentCell = new DataGridCellInfo(this.SelectedItem, this.Columns[0]); } } protected override void OnPreviewKeyDown(KeyEventArgs e) { CurrentCellBlocked = false; } protected override void OnPreviewKeyUp(KeyEventArgs e) { CurrentCellBlocked = true; } protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { CurrentCellBlocked = false; } protected override void OnPreviewMouseUp(MouseButtonEventArgs e) { CurrentCellBlocked = true; }
    protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
    {
    CurrentCellBlocked = false;
    }

    protected override void OnMouseWhee(MouseWheelEventArgs e)
    {
    CurrentCellBlocked = true;
    }
    }

    В экземпляр MyDataGrid добавляем:

     internal void ScrollViewerDataGrid1_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
    if (DataGrid1.CurrentCellBlocked)
    {
    var border = VisualTreeHelper.GetChild(DataGrid1, 0) as Decorator;
    if (border != null)
    {
    var scroll = border.Child as ScrollViewer;
    //Установление Scroll'а в сохраненную ранее позицию
    scroll.ScrollToVerticalOffset(DataGrid1.OffSet);
    }
    }
    }

     

    Либо переопределяем ScrollViewer, после чего добавляем его в Template MyDataGrid'a.

     public class MyScrollViewer : ScrollViewer
     {
     protected override void OnScrollChanged(ScrollChangedEventArgs e)
     {
      DependencyObject depObject = (DependencyObject)e.OriginalSource;
      while ((depObject != null) && !(depObject is MyDataGrid))
      {
      depObject = VisualTreeHelper.GetParent(depObject); 
      }
      if (depObject is MyDataGrid)
      {
      if (((MyDataGrid)depObject).CurrentCellBlocked)
      {
       this.ScrollToVerticalOffset(((MyDataGrid)depObject).OffSet);
      }
      }
      base.OnScrollChanged(e);
     }
     }
    





    • Помечено в качестве ответа Abolmasov Dmitry 27 марта 2011 г. 17:09
    25 марта 2011 г. 16:59

Все ответы

  • Здравстуйте, посмотрите топик на анлийском форуме - TabNavigation into, inside and out of the WPF DataGrid. Там как раз разбирается как сохранить в фокусе выбранный элемент.


    Для связи [mail]
    • Помечено в качестве ответа Abolmasov Dmitry 27 марта 2011 г. 17:09
    23 марта 2011 г. 10:36
  •  

    Спасибо за ссылку!

    Пока только взглядом пробежался. Там используется DataGridCellsPresenter, который (если я не ошибаюсь) есть только в .NET 4. Я же пишу под версию 3.5. Но в целом проблема описана та же.

    Вот мой вариант решения. Получился несколько громоздкий, но с задачей справляется - в рамках условий: переход Tab'ом не учитывается (заблокирован), выделяются строки целиком (выбирать отдельные ячейки не требуется), при переходе на вкладку TabControl'a DataGrid сразу получает фокус.

    Переопределяем DataGrid и CurrentCellProperty. Добавляем вспомогательные свойства для блокировки нежелательных изменений CurrentCellProperty и перемещений Scroll'a.

     public class MyDataGrid : DataGrid
     {
      //Положение Scroll'а 
     private double offSet = 0; 
    internal
    double OffSet { get { return offSet; } set { offSet = value; } } //Блокирование выбора ячейки, отличной от текущей internal bool CurrentCellBlocked { get { return StaticCurrentCellBlocked; } set { StaticCurrentCellBlocked = value; if (value) { //Сохранение положения Scroll'а DataGrid var border = VisualTreeHelper.GetChild(this, 0) as Decorator; if (border != null) { var scroll = border.Child as ScrollViewer; OffSet = scroll.VerticalOffset; } } } } private static bool staticCurrentCellBlocked = true; private static bool StaticCurrentCellBlocked { get { return staticCurrentCellBlocked; } set { staticCurrentCellBlocked = value; } } static MyDataGrid() { DataGrid.CurrentCellProperty.OverrideMetadata( typeof(MyDataGrid), new FrameworkPropertyMetadata(new DataGridCellInfo(),
    new PropertyChangedCallback(OnCurrentCellPropertyChanged), new CoerceValueCallback(OnCurrentCellCoerce))); } private static void OnCurrentCellPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { //... } private static object OnCurrentCellCoerce(DependencyObject d, object Value) { MyDataGrid dataGrid = (MyDataGrid)d; if (StaticCurrentCellBlocked) { return new DataGridCellInfo(dataGrid.SelectedItem, dataGrid.Columns[0]); } return Value; } protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e) { if (this.SelectedIndex != -1) { this.CurrentCell = new DataGridCellInfo(this.SelectedItem, this.Columns[0]); } } protected override void OnPreviewKeyDown(KeyEventArgs e) { CurrentCellBlocked = false; } protected override void OnPreviewKeyUp(KeyEventArgs e) { CurrentCellBlocked = true; } protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { CurrentCellBlocked = false; } protected override void OnPreviewMouseUp(MouseButtonEventArgs e) { CurrentCellBlocked = true; }
    protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
    {
    CurrentCellBlocked = false;
    }

    protected override void OnMouseWhee(MouseWheelEventArgs e)
    {
    CurrentCellBlocked = true;
    }
    }

    В экземпляр MyDataGrid добавляем:

     internal void ScrollViewerDataGrid1_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
    if (DataGrid1.CurrentCellBlocked)
    {
    var border = VisualTreeHelper.GetChild(DataGrid1, 0) as Decorator;
    if (border != null)
    {
    var scroll = border.Child as ScrollViewer;
    //Установление Scroll'а в сохраненную ранее позицию
    scroll.ScrollToVerticalOffset(DataGrid1.OffSet);
    }
    }
    }

     

    Либо переопределяем ScrollViewer, после чего добавляем его в Template MyDataGrid'a.

     public class MyScrollViewer : ScrollViewer
     {
     protected override void OnScrollChanged(ScrollChangedEventArgs e)
     {
      DependencyObject depObject = (DependencyObject)e.OriginalSource;
      while ((depObject != null) && !(depObject is MyDataGrid))
      {
      depObject = VisualTreeHelper.GetParent(depObject); 
      }
      if (depObject is MyDataGrid)
      {
      if (((MyDataGrid)depObject).CurrentCellBlocked)
      {
       this.ScrollToVerticalOffset(((MyDataGrid)depObject).OffSet);
      }
      }
      base.OnScrollChanged(e);
     }
     }
    





    • Помечено в качестве ответа Abolmasov Dmitry 27 марта 2011 г. 17:09
    25 марта 2011 г. 16:59