1

The view having one DataGrid and The ViewModel having the following function

public  DependencyObject ScrollViewer(DependencyObject targetControl)
    {
        if (targetControl is ScrollViewer)
        {
            return targetControl;
        }

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(targetControl); i++)
        {
            var child = VisualTreeHelper.GetChild(targetControl, i);
            var result = ScrollViewer(child);
            if (result == null)
            {
                continue;
            }
            else
            {
                return result;
            }
        }
        return null;
    }

now i want to get scrollviewer of the grid using above function.

var scrolViewer = ScrollViewer(dataGridID) as ScrollViewer;

If i pass the datagrid id as parameter. i will get the result. But it is not possible in MVVM pattern. is there any way to get the visual child data grid ?

ASHOK A
  • 1,966
  • 3
  • 17
  • 31
  • Generally speaking, you're not supposed to pass View elements to the ViewModel as the idea is to separate those two concerns. What are you trying to do? – keyboardP Mar 30 '13 at 12:50
  • without using mvvm pattern, i can get the visual child of data grid through pass the id of the data grid in the ScrollViewer() function. But i don't know, how to achieve this scenario in mvvm pattern. – ASHOK A Mar 30 '13 at 12:54
  • I understand that but why are you trying to get the visual child itself? Can you not add the relevant commands and bindings to the visual child and connect those to your ViewModel? – keyboardP Mar 30 '13 at 12:56
  • I need to get scroll viewer of the data grid. then i will handle the scroll with some function. – ASHOK A Mar 30 '13 at 12:59
  • Depending on how strictly you want to adhere to MVVM, you can do it in your View code-behind and attach an event handler to your VM to control the scroll (this is different than mixing your VM and View directly). Some people may frown on this (those who want no code in the View at all), in which case, you can create an `Attached Property` and assign it to your ScrollViewer. This property can access the scrolling properties of the ScrollViewer and you can then bind that to your VM. – keyboardP Mar 30 '13 at 13:04
  • I can't get your point. if it will be as code, it would be much better. I explain my requirement. I have a grid. and two button called Top and Bottom. if i click Bottom button when the scroll viewer of the grid is visible, the scrollbar of scroll viewer comes Bottom. if i click Top button the scrollbar go to Top. Please help me with code. – ASHOK A Mar 30 '13 at 13:15
  • Ok, give me a few minutes and I'll see I can come up with a basic Attached Property. – keyboardP Mar 30 '13 at 13:42

1 Answers1

1

I've created a quick Attached Property that should allow you to stick with the MVVM concepts and also provide you the ability to scroll to the top and bottom of the DataGrid. It can be improved (e.g. avoid two separate properties and use an IValueConverter to decide how to scroll) but this should give you a good starting point.

First, we create the Attached Properties (one for scrolling to the bottom and one for the top)

public static class Scroller
{
     //Create the attached property and register it
     public static readonly DependencyProperty ScrollToBottomProperty =
        DependencyProperty.RegisterAttached("ScrollToBottom", typeof(bool), typeof(Scroller), new PropertyMetadata(false, ScrollToBottomPropertyChanged));


     //Create the get and set methods for the property
     public static bool GetScrollToBottom(DependencyObject obj)
     {
         return (bool)obj.GetValue(ScrollToBottomProperty);
     }

     public static void SetScrollToBottom(DependencyObject obj, bool value)
     {
         obj.SetValue(ScrollToBottomProperty, value);
     }


     //Get the control that you've attached this to (DataGrid in this case) and find its ScrollViewer 
     private static void ScrollToBottomPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
         var scrollViewer = GetScrollViewer(d) as ScrollViewer;
         if (scrollViewer != null && (bool)e.NewValue)
         {
            //Use built in ScrollToBottom method to scroll to...well...the bottom :)
             scrollViewer.ScrollToBottom();
         }
     }


     //Same as above but for "ScrollToTop" method
     public static readonly DependencyProperty ScrollToTopProperty =
     DependencyProperty.RegisterAttached("ScrollToTop", typeof(bool), typeof(Scroller), new PropertyMetadata(false, ScrollToTopPropertyChanged));


     public static bool GetScrollToTop(DependencyObject obj)
     {
         return (bool)obj.GetValue(ScrollToTopProperty);
     }

     public static void SetScrollToTop(DependencyObject obj, bool value)
     {
         obj.SetValue(ScrollToTopProperty, value);
     }



     private static void ScrollToTopPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
         var scrollViewer = GetScrollViewer(d) as ScrollViewer;

         if (scrollViewer != null && (bool)e.NewValue)
         {
             scrollViewer.ScrollToTop();
         }
     }


     //Your ScrollViewerMethod (I renamed it to GetScrollViewer for clarity)
     public static DependencyObject GetScrollViewer(DependencyObject targetControl)
     {
         if (targetControl is ScrollViewer)
         {
             return targetControl;
         }

         for (int i = 0; i < VisualTreeHelper.GetChildrenCount(targetControl); i++)
         {
             var child = VisualTreeHelper.GetChild(targetControl, i);
             var result = GetScrollViewer(child);
             if (result == null)
             {
                 continue;
             }
             else
             {
                 return result;
             }
         }
         return null;
     }
}

In your XAML, you need to add the relevant namespace (change yours accordingly)

xmlns:custom="clr-namespace:ScrollExampleMVVM"

We can then attach the properties to our DataGrid

<DataGrid custom:Scroller.ScrollToBottom="{Binding ScrollBottom}" custom:Scroller.ScrollToTop="{Binding ScrollTop}" ...?

In your ViewModel, you can have your public ScrollBottom and ScrollTop properties

private bool _scrollBottom = false;
public bool ScrollBottom 
{
    get { return _scrollBottom; }
    set
    {
         _scrollBottom = value;                
         NotifyPropertyChanged("ScrollBottom");              
    }
}

private bool _scrollTop = false;
public bool ScrollTop
{
     get { return _scrollTop; }
     set
     {
          _scrollTop = value;
          NotifyPropertyChanged("ScrollTop");
     }
}

Finally handle your buttons (I'm guessing you're using ICommands) to call the scrolling

private void ScrollBottomCommand(object param)
{
    //Immediately set back to false so that it can be reused
    ScrollBottom = true;
    ScrollBottom = false;
}

private void ScrollTopCommand(object param)
{
    ScrollTop = true;
    ScrollTop = false;
}

That should work. As mentioned earlier, you could probably improve it to avoid the reset code but hopefully this should give an idea.

keyboardP
  • 68,824
  • 13
  • 156
  • 205
  • This is what i excepted. It is working. Thank you very much for your effort. One kind request, i am newbie to MVVM pattern , is there any help for me ? – ASHOK A Apr 01 '13 at 07:11
  • @ASHOKA - No problem! It can be confusing at first but you can definitely pick it up with a bit of practice. There are some good links to tutorials here http://stackoverflow.com/questions/1405739/mvvm-tutorial-from-start-to-finish – keyboardP Apr 01 '13 at 11:44