Question

I have a ScrollViewer within which, I have an ItemsControl. The ItemsControl is bound to an ObservableCollection. The ItemsControl has an ItemTemplate, where in I provide a TextBox to display the data.

The data is getting displayed correctly. I have added a popup to each of the TextBox to display additional data with the placement target being Top.

The problem I am facing is when I scroll if an item goes out of view, I need to hide the popup and display it again when the item appears.

I have subscribed to the scrollviewer's ScrollChanged event, but how do I get hold of the item / TextBox which is going out of scope to hide the popup?

Was it helpful?

Solution

You can use the method from the Determining the Visibility of Elements inside ScrollViewer article on Lester's WPF\SL Blog to determine whether a particular item is visible in the ScrollViewer or not. From that blog:

// position of your visual inside the scrollviewer    
GeneralTransform childTransform = ContainedObject.TransformToAncestor(ScrollViewerObj);
Rect rectangle = childTransform.TransformBounds(new Rect(new Point(0,0), 
    ContainedObject.RenderSize));

// Check if the elements Rect intersects with that of the scrollviewer's
Rect result = Rect.Intersect(new Rect(new Point(0, 0), ScrollViewerObj.RenderSize), 
    rectangle);

// if result is Empty then the element is not in view
if (result == Rect.Empty)
{
    ContainedObject.IsPopupOpen = false; // <<< Close relevant popup here
}
else
{
    //obj is partially Or completely visible
    //skip or bring obj in view.
}

Clearly, for this to work, you'll need to add a new bool IsPopupOpen property to the data object that is data bound to the TextBox.Text properties. You'll then need to Bind this to the Popup.IsOpen property too:

<Popup IsOpen="{Binding IsPopupOpen}" StaysOpen="False" ... />

UPDATE >>>

The ScrollViewer.ScrollChanged event will get called very often when a ScrollViewer is scrolled. It would indeed be unwise to perform any code directly in that event handler, however the ScrollChangedEventArgs object has some properties that can help us. Please take a look on the ScrollChangedEventArgs Class page on MSDN for full details of these properties.

There are two properties that you will have access to in your event handler named VerticalChange and HorizontalChange that should show you how much the ScrollViewer has been scrolled since the last event. [Now it's been a while, so I can't guarantee that they are the correct properties, but if you use the example code from the ScrollChangedEventArgs.VerticalChange Property page on MSDN in your event handler, the read out should give you a clue as to which are the correct properties to use.]

So, having found the relevant properties, you can use them in your handler to determine whether to perform your code or not... try something like this:

double totalVerticalChange = 0.0;
double minimumValue = 24.0; // set this to whatever you want as a minimum scroll value
...
private void ScrollViewer_Changed(object sender, ScrollChangedEventArgs e)
{
    if (totalVerticalChange + e.VerticalChange >= minimumValue)
    {
        totalVerticalChange = 0.0;
        // perform your functionality here
    }
    else totalVerticalChange += e.VerticalChange;
}

Please forgive me if there are errors here as I can't check this in Visual Studio at the moment, but hopefully you get the idea... essentially, it's like we are 'filtering' out some of the events.

OTHER TIPS

Take a look at this question here which discusses determining the viewability of an object in a ScrollViewer.

The basic helper method:

private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
    if (!element.IsVisible)
        return false;

    Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
    Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
    return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}

and an example function that does what you want to do:

private void Scroll_Changed(object sender, ScrollChangedEventArgs e)
{
    Object o = sender;
    bool elementIsVisible = false;

    foreach (FrameworkElement child in this.stackPanel1.Children)
    {
        if (child != null)
        {
            elementIsVisible = this.IsUserVisible(child, this.scroller);

            if (!elementIsVisible)
            {
                // Logic to disable popups here...
            }
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top