How to take the index, of the rows visible in a WPF DataGrid?

0

I'm working with WPF, and I can not find a way to take the index of the visible rows of a datagrid. I'm working with MVVM ... thank you very much to anyone who can give me a hand!

    
asked by Julian Vasquez Perez 02.02.2017 в 21:48
source

2 answers

0

I do not see a way to do it without breaking the MVVM scheme into something for the simple fact that the ViewModel should not see the DataGrid, but based on some articles it can be done this way:

First: You need this ExtensionMethod:

public static T GetVisualChild<T>(this Visual referenceVisual) where T : Visual
{
    Visual child = null;
    for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++)
    {
        child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
        if (child != null && child is T)
        {
            break;
        }
        else if (child != null)
        {
            child = GetVisualChild<T>(child);
            if (child != null && child is T)
            {
                break;
            }
        }
    }
    return child as T;
}

Now, as I said, you have to break the MVVM a bit, and this is where: In the codeBehind of your view, call the ViewModel

Create the Property

private MyViewModel _myUsedVM;
public MyViewModel MyUsedVM
{
    get { return _myUsedVM; }
    set { _myUsedVM= value; }
}

and then you define the ViewModel in the constructor by calling it through its Key (name you give it and what you use to assign the DataContext in the view):

<DataGrid DataContext="{StaticResource MyVM}"/>


//El constructor del archivo .cs de tu vista, 
//OJO!! No crees una nueva instancia!! se llama la que ya existe
public MyView()
{
    MyUsedVM= (MyViewModel)FindResource("MyVM"); //x:Key
}

Then, to the LOADED event of the DataGrid you have to assign it the code that will link the extensionMethod to the control

private void dg_items_Loaded(object sender, RoutedEventArgs e)
        {
            ScrollViewer scrollViewer = dg_items.GetVisualChild<ScrollViewer>(); //Extension method
            if (scrollViewer != null)
            {
                ScrollBar scrollBar = scrollViewer.Template.FindName("PART_VerticalScrollBar", scrollViewer) as ScrollBar;
                if (scrollBar != null)
                {
                    scrollBar.ValueChanged += delegate
                    {
                        double PrimerIndexVisible = scrollViewer.VerticalOffset;
                        double TotalElementosVisibles = scrollViewer.ViewportHeight;
                        MyUsedVM.SaberLoQueEstoyViendo(PrimerIndexVisible, TotalElementosVisibles);
                    };
                }
            }
        }

Finally in the ViewModel you make a public method that receives both data, the index and the total that are doubles:

public void SaberLoQueEstoyViendo(double primerIndex, double totalItems)
{
    //Aquí ya sabes cual es el primer item que se ve en la lista y cuantos hay visibles
}

And that's it, it's not that difficult.

I ALREADY KNOW that this breaks the MVVM pattern a bit but I do not see how to achieve what you want without doing it since what you want is a view function, not the viewModel, if you know how to control Control events in the ViewModel you can do without breaking the MVVM but I do not know how to do it.

I hope it will help you get out of the way, I tried it and it works. Greetings !!

    
answered by 05.02.2017 в 19:38
0

[FULL MVVM MODE] This mode uses the namespace System.Windows.Interactivity to control events in the ViewModel.

Step 1) Add reference to System.Windows.Interactivity

Step 2) Add the namespace in the window where the DataGrid is xmlns: i="http://schemas.microsoft.com/expression/2010/interactivity

<Window x:Class="GiaGenesis.Views.Dialogs.SendCheckListGestorDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:GiaGenesis.ViewModels"....

Step 3) Add this extension method in your static extension class

public static T GetVisualChild<T>(this Visual referenceVisual) where T : Visual
{
    Visual child = null;
    for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++)
    {
        child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
        if (child != null && child is T)
        {
            break;
        }
        else if (child != null)
        {
            child = GetVisualChild<T>(child);
            if (child != null && child is T)
            {
                break;
            }
        }
    }
    return child as T;
}

Step 4) Create an ICommand in your ViewModel that you will use to bind the DataGrid to the extension method

//Declaración del ICommand
private ICommand _bindCommand;
public ICommand BindCommand
{
    get { return _bindCommand; }
    set { _bindCommand = value; }
}

You define it in the constructor so that when launched, call the method that will make the link with the extension method

//Constructor
public DemoViewModel()
{
    //Para enlazar el datagrid al metodo
    BindCommand = new ParamCommand(new Action<object>(BindMethod));
    //botón para ver los datos
    //ViewCommand = new RelayCommand(new Action(SaberLoQueEstoyViendo));
}

The method:

//El método para bindear el datagrid al metodo
private void BindMethod(object obj) 
{
    DataGrid datagrid = (DataGrid)obj;
    ScrollViewer scrollViewer = datagrid.GetVisualChild<ScrollViewer>(); //Extension method
    if (scrollViewer != null)
    {
        ScrollBar scrollBar = scrollViewer.Template.FindName("PART_VerticalScrollBar", scrollViewer) as ScrollBar;
        if (scrollBar != null)
        {
            //cada vez que muevas el scroll esto se ejecutará
            scrollBar.ValueChanged += delegate
            {
                double PrimerIndexVisible = scrollViewer.VerticalOffset;
                double TotalElementosVisibles = scrollViewer.ViewportHeight;
                //opcional: mando la info a un método que muestra os valores en un control de lista mediante una colección en el mismo viewmodel
                SaberLoQueEstoyViendo(PrimerIndexVisible, TotalElementosVisibles);
            };
        }
    }
}

This is optional, I see the info in a litbox, but you can omit this method and use a button to call the info, saving the variables PrimerIndexVisible and TotalElementosVisibles at the class level:

//Y el método que muestra el index y el total de indexs (items) que se ven
public void SaberLoQueEstoyViendo(double primerIndex, double totalItems)
{
    //Log es una ObservableColection de strings bindeada a un ListBox
    Log.Add("-> " + primerIndex + " : " + totalItems);
    //Lo mismo pero por consola
    Console.WriteLine("-> " + primerIndex + " : " + totalItems); //Esto lo tengo en un listbox, se actualiza solo
    RaisePropertyChanged("Log");
}

Step 5) Define your DataGrid in this way:

<DataGrid ItemsSource="{Binding MiTablaSourceView}" Margin="5,50,5.4,99.8">
 <i:Interaction.Triggers>
  <i:EventTrigger EventName="Loaded">
   <i:InvokeCommandAction Command="{Binding BindCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
  </i:EventTrigger>
 </i:Interaction.Triggers>
</DataGrid>

And now, you have what you want without using the .cs of the view. I leave both forms as independent answers so they can see the similarities in code control.

Greetings.

PS: The ParamCommand class is the same as the RelayCommand with parameter:

public class ParamCommand : ICommand
{
    private Action<object> _action;
    private readonly Func<bool> _canExecute;

    public ParamCommand(Action<object> action)
    {
        _action = action;
        _canExecute = () => true;
    }

    public ParamCommand(Action<object> action, Func<bool> canExecute)
    {
        _action = action;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (this._canExecute == null)
        {
            return true;
        }
        else
        {
            bool result = this._canExecute.Invoke();
            return result;
        }
    }

    public void Execute(object parameter)
    {
        if (CanExecute(parameter))
        {
            if (parameter != null)
            {
                _action(parameter);
            }
        }
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

}
    
answered by 09.02.2017 в 03:24