FrameworkElement.EffectiveViewportChanged Event

Definition

Occurs when the FrameworkElement's effective viewport changes.

// Register
event_token EffectiveViewportChanged(TypedEventHandler<FrameworkElement, EffectiveViewportChangedEventArgs const&> const& handler) const;

// Revoke with event_token
void EffectiveViewportChanged(event_token const* cookie) const;

// Revoke with event_revoker
FrameworkElement::EffectiveViewportChanged_revoker EffectiveViewportChanged(auto_revoke_t, TypedEventHandler<FrameworkElement, EffectiveViewportChangedEventArgs const&> const& handler) const;
public event TypedEventHandler<FrameworkElement,EffectiveViewportChangedEventArgs> EffectiveViewportChanged;
function onEffectiveViewportChanged(eventArgs) { /* Your code */ }
frameworkElement.addEventListener("effectiveviewportchanged", onEffectiveViewportChanged);
frameworkElement.removeEventListener("effectiveviewportchanged", onEffectiveViewportChanged);
- or -
frameworkElement.oneffectiveviewportchanged = onEffectiveViewportChanged;
Public Custom Event EffectiveViewportChanged As TypedEventHandler(Of FrameworkElement, EffectiveViewportChangedEventArgs) 

Event Type

Windows requirements

Device family
Windows 10, version 1809 (introduced in 10.0.17763.0)
API contract
Windows.Foundation.UniversalApiContract (introduced in v7.0)

Remarks

A scrolling control allows the user to pan/scroll through content that takes up more space than is available in the UI. The portion of the content that the user sees is called the viewport.

The EffectiveViewportChanged event provides multiple pieces of information:

  1. The actual EffectiveViewport
  2. A calculation for the MaxViewport
  3. Scalar values for the BringIntoViewDistanceX and BringIntoViewDistanceY

EffectiveViewport

The EffectiveViewport is the intersection of all known viewports that contain the FrameworkElement in their sub-tree. If there are two or more viewports (for example, a ScrollViewer nested inside another ScrollViewer) that do not overlap, then the EffectiveViewport is an empty Rect.

Note

For a scrolling control's viewport to be known to the framework, the control must have previously registered it using the UIElement.RegisterAsScrollPort method. The framework uses the Clip of the registered element when determining the effective viewport.

When the scrolling control's viewport changes it must invoke its InvalidateViewport method to inform the framework that its viewport has changed and any of its sub-elements that listen to the effective viewport need to be notified of changes.

The EffectiveViewport is given in the coordinate space of the FrameworkElement. There's no need to perform a TransformToVisual with the viewport Rect.

In a simple scenario where there is a ScrollViewer that contains a single element, the EffectiveViewportChanged event provides viewport updates similar to the ViewChanged event. The main difference is that the EffectiveViewportChanged event is raised after the arrange pass of layout.

For example, this ...

<ScrollViewer>
    <Grid Height="4000" Width="4000"
          EffectiveViewportChanged="Grid_EffectiveViewportChanged"/>
</ScrollViewer>

... provides similar viewport information as this...

<ScrollViewer ViewChanged="ScrollViewer_ViewChanged">
    <Grid Height="4000" Width="4000"/>
</ScrollViewer>

MaxViewport

The MaxViewport is similar to the EffectiveViewport, but instead of representing a simple intersection of the known viewports, it represents the intersection of the viewports as if each had been brought into view of any outer viewport. The resulting Rect represents two things:

  1. the largest size that the EffectiveViewport can be (given the current viewport sizes), and
  2. the position of the maximum effective viewport relative to the FrameworkElement.

This information can be used to gauge where and how much content the FrameworkElement should pre-generate to potentially fill the viewport before it is scrolled into view.

Note

Scrolling via direct input such as touch or pen is handled by the system in a separate process. By default, scrolling is handled asynchronously to the UI thread. Controls that perform virtualization may need to pre-generate content well in advance of entering the viewport due to the inherent cost of element creation.

Delaying all content preparation until coming into view can lead to a poor scrolling experience for users. Users may see blank space or stutters, both symptoms of the UI thread not being able to keep up with the speed of panning.

The MaxViewport's position is reported in the coordinate space of the FrameworkElement. If the MaxViewport were transformed to the coordinate space of the first viewport in the FrameworkElement's chain of ancestors, the Rect would be within the bounds of that first viewport.

BringIntoViewDistanceX and Y

These values indicate how close the FrameworkElement is to becoming maximally visible across all its viewports.

If the value is greater than zero, but less than the ActualWidth / ActualHeight then the element is partially within the user-visible viewport. When the values are zero then the FrameworkElement is fully within the user-visible viewport.

Tip

This does not guarantee that the element is visible to the user since other elements with a higher Z-order may still be occluding the FrameworkElement.

Stated more formally, these values are the sum of the absolute distance that the FrameworkElement would be translated when satisfying a call to StartBringIntoView. The values do not account for the possibility that a scroll control has scrolling disabled.

<ListView x:Name="lv">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="x:String">
            <UserControl Tag="{x:Bind}"
                         EffectiveViewportChanged="Item_EffectiveViewportChanged"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
private void Item_EffectiveViewportChanged(FrameworkElement sender, EffectiveViewportChangedEventArgs args)
{
    // If we wanted to know if a list item (w/ vertical scrolling only) is partially within the viewport
    // then we can just check the BringIntoViewDistanceY of the event args.  If the distance is 0 then the item is fully within
    // the effective viewport.  If the BringIntoViewDistanceY is less than the sender's ActualHeight, then its
    // partially within the effective viewport.
    // The EffectiveViewport rect is relative to the sender, so we can use it to know where the element is within the viewport.  
    // NOTE: "Within the viewport" != visible to the user's eye, since another element may overlap and obscure it.
    if (args.BringIntoViewDistanceY < sender.ActualHeight)
    {
        Debug.WriteLine($"Item: {sender.Tag} has {sender.ActualHeight - args.BringIntoViewDistanceY} pixels within the viewport");
    }
    else
    {
        Debug.WriteLine($"Item: {sender.Tag} has {args.BringIntoViewDistanceY - sender.ActualHeight} pixels to go before it is even partially visible");
    }

    // Consider disconnecting from the effective viewport when not needed.  Otherwise, it is called on every viewport change.
    //lv.EffectiveViewportChanged -= Item_EffectiveViewportChanged;
}

Behavior

  • If the effective viewport of a parent and child both change, the parent will receive the notification before the child.
  • The event is only raised for elements in the UI tree that participate in layout. For example, if the element isn't in the live tree, or if the Visibility property of the element or any of its ancestors is set to Collapsed, this event won't be raised.
  • Although the effective viewport does account for render transforms for all the elements ancestors, it does not consider the effects of clipping (other than the clip of the element registered by a scrolling control as its viewport).
  • The effective viewport does not account for occlusion due to other elements having a higher Z-order.

Applies to

See also