-
Notifications
You must be signed in to change notification settings - Fork 692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Add a way to track effective visibility of a control #674
Comments
This would be really great to add to UWP. It is sorely missed and results in ugly, half-functional code such as the below. Both IsVisible and IsVisibleChanged would be a boost in a number of scenarios related to control development/optimization. Code /// <summary>
/// Determines if the given UI framework element is visible in the user interface.
/// </summary>
/// <param name="element">The element to test for user visiblity.</param>
/// <param name="container">The container of the element to test,
/// it's assumed this is already visible.</param>
/// <param name="isFullVisibilityRequired">Whether the element must be fully visible in the container.
/// By default partial visiblity is allowed.</param>
/// <returns>True if the element is visible to the user, otherwise false.</returns>
public static bool IsVisible(this FrameworkElement element,
FrameworkElement container,
bool isFullVisibilityRequired = false)
{
Rect containerBounds;
Rect elementBounds;
Rect intersection;
if ((element == null) ||
(container == null))
{
return (false);
}
if ((element.Visibility != Visibility.Visible) ||
(container.Visibility != Visibility.Visible))
{
return (false);
}
// Update the layout just to be sure sizes are correct
container.InvalidateArrange();
container.InvalidateMeasure();
container.UpdateLayout();
elementBounds = element.TransformToVisual(container).TransformBounds(new Rect(0.0, 0.0,
element.ActualWidth,
element.ActualHeight));
containerBounds = new Rect(0.0, 0.0,
container.ActualWidth,
container.ActualHeight);
intersection = elementBounds;
intersection.Intersect(containerBounds);
if (intersection.IsEmpty)
{
// No intersection at all
return (false);
}
else
{
if (isFullVisibilityRequired)
{
if ((intersection.Width == elementBounds.Width) &&
(intersection.Height == elementBounds.Height))
{
// Full intersection
return (true);
}
else
{
// Only partial intersection
return (false);
}
}
else
{
// Any intersection is valid
return (true);
}
}
} |
WPF has three different Visibility states (opposed to the current two in UWP): Visible, Hidden, Collapsed. I don't know about the plans for the UWP Visibility enum, (and how the talk of bringing feature parity with WPF to UWP would affect it) but this implementation would make the API proposed above more future-proof: public class UIElement
{
+ public bool IsVisible { get; }
+ public event EventHandler<VisibilityChangedEventArgs> VisibilityChanged;
}
+ public class VisibilityChangedEventArgs : EventArgs
{
+ public Visibility OldState { get; }
+ public Visibility NewState { get; }
} |
Thank you for filing this issue. I agree with the rationale that determining if something is visible, like actually really visible is difficult to do on the app-side for the reasons mentioned (like monitoring the ancestor chain). Additionally, we'd also want to account for things like elements contained within open/closed popups when we report if the element is Visible. Although this feature request is righteous, it would require changes in the underlying platform meaning the earliest we'd do this work is post-WinUI 3.0. (The roadmap can be found here: #717 ) In the meantime, we will keep this item open but in the Freezer for now. |
Note that a related and also useful feature would be the ability to see if an element is on screen, and this proposal is not that; this IsVisible API doesn't detect that an element is clipped or occluded. Clipping is more detectable now with FrameworkElement.EffectiveViewportChanged, but occlusion would be expensive to calculate.
WPF's Visibility has three states to match the HTML behavior of both display:none (Visibility.Collapsed) and visibility:hidden (Visibility.Hidden). Visibility.Hidden has generally been considered a mistake because you can get the same thing by setting the Opacity to 0, and because the enum is more difficult to work with than a bool. |
I agree. In my opinion, this is a significant problem and a solution would be great to have. But is it possible that a workaround already exists? Is it possible to use Windows.UI.Composition.Visual.IsVisible as a partial workaround? I don't know the answer because the documentation for Given an instance of a class derived from I do realize that This workaround is useful but less-good than an event @dotMorten wrote:
The name
I suggest that it might be best to put the clipping and occlusion testing functionality in a separate feature -- separate to the proposed If the @Felix-Dev suggested:
I would find those properties confusing because it is unclear whether |
I also suggest that it might be better to define the property in the opposite sense. Instead of reporting whether the element is visible, report whether it is hidden/invisible. At first glance, this reversal appears to make no difference, but actually a difference does exist: The accuracy. The reasoning is that the hidden state is easier to accurately detect/determine than the visible state. If an |
@verelpode |
I don't really agree with that. Seems pretty obvious to me for a read-only property. I don't see the reason for adding support for potential new future visibility states. None of those would matter in the user-stories described above: It's all about whether it's currently rendering or not. There's also something to be said of precedence in WPF that has proven what it has works fine, despite WPF actually has more than just two visibility states. |
@dotMorten
OK, then maybe you'd prefer a name like "IsRendered" or "IsCurrentlyRendered" ? More importantly, what do you think of the possibility of using |
If there is a property, then Thinking about it as a query to the renderer, you could have enum values like:
Whilst the control itself knows if it is supposed to be Visible, Hidden, or Collapsed - the renderer would keep track of the more detailed visible state, which the control/code can query. |
@mdtauk -- I like your choice of naming and the idea of returning an enum, but I can think of a downside. If it reports occlusion, but occlusion testing is expensive to calculate, and apps don't always need this much info, then the To help solve this, the idea might be changed from a property to a method with a parameter to indicate whether a full test should be performed. Possibly something like the following:
The above still has a problem: It's unclear whether the event If a full |
@verelpode For what its worth, I wasn't suggesting Occluded need to be included, only that as an enum, it could be one of several statuses that could be reported. I imagine it would be a ReadOnly property, but the renderer would be responsible for reporting what the ActualVisibility would be. The control or codebehind need not do any work to calculate it. The renderer should know something like an occlusion, as it would be drawing pixels over the control, or only drawing a partial amount of pixels. |
Terminal is interested in this. Right now, we are rendering terminal content even when our controls are completely occluded or missing from the visual tree. |
While working on the new shadow implementation for the Toolkit, I noticed that when an element is repositioned in a Canvas (left/top modified) it doesn't seem to trigger a It's a bit related to this same issue. Basically there needs to be a way to track whenever an element's visibility or position changes relative to the screen/viewport regardless of what may have caused it (user interaction with another control, window resizing/moving, parent control re-layouts or moves a child item, etc...) Related to #2900 |
@michael-hawker If a parent only moves (but doesn't change size), why is a layout step needed on the child? |
@dotMorten I'm not saying it should call
We wouldn't need to re-measure things, but we need a way of understanding that something about the UI has moved. For instance with the composition shadow, that's coordinated via a sibling element, so it needs to be updated. But in this Canvas scenario, there's no way I know of how to detect that it's moved... |
I think EffectiveViewportchanged only works in a |
@stmoy Could this be reconsidered? It would allow a nice performance improvement for us and save battery by being better at stop expensive processes when not connected to the view. |
Summary
There's currently no good way to know if a control is currently visible and should be rendering, without having to constantly walk the entire visual tree. This prevents you from reacting to parent controls collapsing a control (think tab and collapse controls), where you for instance might want to stop an expensive SwapChainPanel rendering loop.
Rationale
WPF has always had a read-only
bool UIElement.IsVisible
link and an eventIsVisibleChanged
link that allowed control developers to effectively stop/pause unnecessary work if a control isn't visibly active. Like stop an animation, pause DirectX rendering etc, and not waste battery at that point in time.In UWP there's not really a good way to do this today, except walking the entire UI tree each time the
LayoutUpdated
event fires. You're really left with only being able to react to loaded/unloaded events.You can detect if a specific control gets collapsed, but you'd have to monitor every single parent UI Element as well, so it's not really a practical approach.
Scope
Functional Requirements
I think the WPF equivalent APIs (linked above) are sufficient in behavior and a (near) identical API should be added to UWP:
The text was updated successfully, but these errors were encountered: