Skip to content

Commit 98aeecd

Browse files
committed
Applying visibility change to child controls
1 parent 803586b commit 98aeecd

File tree

2 files changed

+130
-1
lines changed

2 files changed

+130
-1
lines changed

src/Controls/src/Core/VisualElement/VisualElement.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,12 @@ static void OnTransformChanged(BindableObject bindable, object oldValue, object
269269
BindableProperty.Create("TransformOrigin", typeof(Point), typeof(VisualElement), new Point(.5d, .5d),
270270
propertyChanged: (b, o, n) => { (((VisualElement)b).AnchorX, ((VisualElement)b).AnchorY) = (Point)n; });
271271

272+
bool _isVisibleExplicit = (bool)IsVisibleProperty.DefaultValue;
273+
272274
/// <summary>Bindable property for <see cref="IsVisible"/>.</summary>
273275
public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(VisualElement), true,
274-
propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable).OnIsVisibleChanged((bool)oldvalue, (bool)newvalue));
276+
propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable).OnIsVisibleChanged((bool)oldvalue, (bool)newvalue),
277+
coerceValue: CoerceIsVisibleProperty);
275278

276279
/// <summary>Bindable property for <see cref="Opacity"/>.</summary>
277280
public static readonly BindableProperty OpacityProperty = BindableProperty.Create(nameof(Opacity), typeof(double), typeof(VisualElement), 1d, coerceValue: (bindable, value) => ((double)value).Clamp(0, 1));
@@ -694,6 +697,36 @@ private protected bool InputTransparentCore
694697
}
695698
}
696699

700+
/// <summary>
701+
/// This value represents the cumulative IsVisible value.
702+
/// All types that override this property need to also invoke
703+
/// the RefreshIsVisibleProperty() method if the value will change.
704+
/// </summary>
705+
private protected bool IsVisibleCore
706+
{
707+
get
708+
{
709+
if (_isVisibleExplicit == false)
710+
{
711+
// If the explicitly set value is false, then nothing else matters
712+
// And we can save the effort of a Parent check
713+
return false;
714+
}
715+
716+
var parent = Parent as VisualElement;
717+
while (parent is not null)
718+
{
719+
if (!parent.IsVisible)
720+
{
721+
return false;
722+
}
723+
parent = parent.Parent as VisualElement;
724+
}
725+
726+
return _isVisibleExplicit;
727+
}
728+
}
729+
697730
/// <summary>
698731
/// Gets a value indicating whether this element is focused currently. This is a bindable property.
699732
/// </summary>
@@ -1499,6 +1532,7 @@ internal virtual void OnIsVisibleChanged(bool oldValue, bool newValue)
14991532
fe.Handler?.UpdateValue(nameof(IView.Visibility));
15001533
}
15011534

1535+
(this as IPropertyPropagationController)?.PropagatePropertyChanged(IsVisibleProperty.PropertyName);
15021536
InvalidateMeasureInternal(InvalidationTrigger.Undefined);
15031537
}
15041538

@@ -1663,6 +1697,17 @@ static object CoerceInputTransparentProperty(BindableObject bindable, object val
16631697
return false;
16641698
}
16651699

1700+
static object CoerceIsVisibleProperty(BindableObject bindable, object value)
1701+
{
1702+
if (bindable is VisualElement visualElement)
1703+
{
1704+
visualElement._isVisibleExplicit = (bool)value;
1705+
return visualElement.IsVisibleCore;
1706+
}
1707+
1708+
return false;
1709+
}
1710+
16661711
static void OnInputTransparentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
16671712
{
16681713
(bindable as IPropertyPropagationController)?.PropagatePropertyChanged(VisualElement.InputTransparentProperty.PropertyName);
@@ -1730,6 +1775,9 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName
17301775
if (propertyName == null || propertyName == InputTransparentProperty.PropertyName)
17311776
this.RefreshPropertyValue(InputTransparentProperty, _inputTransparentExplicit);
17321777

1778+
if (propertyName == null || propertyName == IsVisibleProperty.PropertyName)
1779+
this.RefreshPropertyValue(IsVisibleProperty, _isVisibleExplicit);
1780+
17331781
PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren());
17341782
}
17351783

@@ -1740,6 +1788,13 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName
17401788
protected void RefreshIsEnabledProperty() =>
17411789
this.RefreshPropertyValue(IsEnabledProperty, _isEnabledExplicit);
17421790

1791+
/// <summary>
1792+
/// This method must always be called if some event occurs and the value of
1793+
/// the <see cref="IsVisibleCore"/> property will change.
1794+
/// </summary>
1795+
internal void RefreshIsVisibleProperty() =>
1796+
this.RefreshPropertyValue(IsVisibleProperty, _isVisibleExplicit);
1797+
17431798
/// <summary>
17441799
/// This method must always be called if some event occurs and the value of
17451800
/// the InputTransparentCore property will change.

src/Controls/tests/Core.UnitTests/VisualElementTests.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,5 +317,79 @@ public void WidthAndHeightRequestPropagateToHandler()
317317
Assert.Equal(2, heightMapperCalled);
318318
Assert.Equal(2, widthMapperCalled);
319319
}
320+
321+
[Fact]
322+
public void ShouldPropagateVisibilityToChildren()
323+
{
324+
var grid = new Grid() { IsVisible = false };
325+
var label = new Label() { IsVisible = true };
326+
grid.Add(label);
327+
328+
Assert.False(label.IsVisible);
329+
Assert.Equal(grid.IsVisible, label.IsVisible);
330+
}
331+
332+
[Theory]
333+
[InlineData(false, true, true, false, false, false)]
334+
[InlineData(true, false, true, true, false, false)]
335+
public void IsVisiblePropagates(bool rootIsVisible, bool nestedIsVisible, bool childIsVisible, bool expectedRootVisibility, bool expectedNestedVisibility, bool expectedChildVisibility)
336+
{
337+
var root = new Grid() { IsVisible = rootIsVisible };
338+
var nested = new Grid() { IsVisible = nestedIsVisible };
339+
var child = new Button() { IsVisible = childIsVisible };
340+
341+
nested.Add(child);
342+
root.Add(nested);
343+
344+
Assert.Equal(root.IsVisible, expectedRootVisibility);
345+
Assert.Equal(nested.IsVisible, expectedNestedVisibility);
346+
Assert.Equal(child.IsVisible, expectedChildVisibility);
347+
}
348+
349+
[Fact]
350+
public void IsVisibleParentCorrectlyUnsetsPropagatedChange()
351+
{
352+
var button = new Button();
353+
var grid = new Grid { button };
354+
355+
grid.IsVisible = false;
356+
Assert.False(button.IsVisible);
357+
358+
grid.IsVisible = true;
359+
Assert.True(button.IsVisible);
360+
}
361+
362+
[Fact]
363+
public void ButtonShouldStayHiddenIfExplicitlySet()
364+
{
365+
var button = new Button { IsVisible = false };
366+
var grid = new Grid { button };
367+
368+
grid.IsVisible = false;
369+
Assert.False(button.IsVisible);
370+
371+
// button stays hidden if it was explicitly set
372+
grid.IsVisible = true;
373+
Assert.False(button.IsVisible);
374+
}
375+
376+
[Fact]
377+
public void ButtonShouldBeVisibleWhenExplicitlySetWhenParentIsVisible()
378+
{
379+
var button = new Button { IsVisible = false };
380+
var grid = new Grid { button };
381+
382+
// everything is hidden
383+
grid.IsVisible = false;
384+
Assert.False(button.IsVisible);
385+
386+
// make button visible, but it should not appear
387+
button.IsVisible = true;
388+
Assert.False(button.IsVisible);
389+
390+
// button appears when parent appears
391+
grid.IsVisible = true;
392+
Assert.True(button.IsVisible);
393+
}
320394
}
321395
}

0 commit comments

Comments
 (0)