Skip to content

Commit f6ae29c

Browse files
committed
Visual glitch fix
1 parent 0257560 commit f6ae29c

File tree

2 files changed

+178
-14
lines changed

2 files changed

+178
-14
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections;
6+
using System.Collections.Specialized;
7+
8+
#if WINAPPSDK
9+
using CommunityToolkit.WinUI.Helpers;
10+
#else
11+
using Microsoft.Toolkit.Uwp.Helpers;
12+
#endif
13+
14+
15+
namespace CommunityToolkit.Labs.WinUI;
16+
17+
/// <summary>
18+
/// Enables a state if an Object is <c>null</c> or a String/IEnumerable is empty
19+
/// </summary>
20+
public class IsNullOrEmptyStateTrigger : StateTriggerBase
21+
{
22+
/// <summary>
23+
/// Gets or sets the value used to check for <c>null</c> or empty.
24+
/// </summary>
25+
public object Value
26+
{
27+
get { return GetValue(ValueProperty); }
28+
set { SetValue(ValueProperty, value); }
29+
}
30+
31+
/// <summary>
32+
/// Identifies the <see cref="Value"/> DependencyProperty
33+
/// </summary>
34+
public static readonly DependencyProperty ValueProperty =
35+
DependencyProperty.Register(nameof(Value), typeof(object), typeof(IsNullOrEmptyStateTrigger), new PropertyMetadata(null, OnValuePropertyChanged));
36+
37+
public IsNullOrEmptyStateTrigger()
38+
{
39+
UpdateTrigger();
40+
}
41+
42+
private void UpdateTrigger()
43+
{
44+
var val = Value;
45+
46+
SetActive(IsNullOrEmpty(val));
47+
48+
if (val == null)
49+
{
50+
return;
51+
}
52+
53+
// Try to listen for various notification events
54+
// Starting with INorifyCollectionChanged
55+
#pragma warning disable CS8622 // Nullability of reference types
56+
var valNotifyCollection = val as INotifyCollectionChanged;
57+
if (valNotifyCollection != null)
58+
{
59+
var weakEvent = new WeakEventListener<IsNullOrEmptyStateTrigger, object, NotifyCollectionChangedEventArgs>(this)
60+
{
61+
OnEventAction = static (instance, source, args) => instance.SetActive(IsNullOrEmpty(source)),
62+
OnDetachAction = (weakEventListener) => valNotifyCollection.CollectionChanged -= weakEventListener.OnEvent
63+
};
64+
65+
valNotifyCollection.CollectionChanged += weakEvent.OnEvent;
66+
#pragma warning restore CS8622
67+
return;
68+
}
69+
70+
// Not INotifyCollectionChanged, try IObservableVector
71+
var valObservableVector = val as IObservableVector<object>;
72+
if (valObservableVector != null)
73+
{
74+
var weakEvent = new WeakEventListener<IsNullOrEmptyStateTrigger, object, IVectorChangedEventArgs>(this)
75+
{
76+
OnEventAction = static (instance, source, args) => instance.SetActive(IsNullOrEmpty(source)),
77+
OnDetachAction = (weakEventListener) => valObservableVector.VectorChanged -= weakEventListener.OnEvent
78+
};
79+
80+
valObservableVector.VectorChanged += weakEvent.OnEvent;
81+
return;
82+
}
83+
84+
// Not INotifyCollectionChanged, try IObservableMap
85+
var valObservableMap = val as IObservableMap<object, object>;
86+
if (valObservableMap != null)
87+
{
88+
var weakEvent = new WeakEventListener<IsNullOrEmptyStateTrigger, object, IMapChangedEventArgs<object>>(this)
89+
{
90+
OnEventAction = static (instance, source, args) => instance.SetActive(IsNullOrEmpty(source)),
91+
OnDetachAction = (weakEventListener) => valObservableMap.MapChanged -= weakEventListener.OnEvent
92+
};
93+
94+
valObservableMap.MapChanged += weakEvent.OnEvent;
95+
}
96+
}
97+
98+
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
99+
{
100+
var obj = (IsNullOrEmptyStateTrigger)d;
101+
obj.UpdateTrigger();
102+
}
103+
104+
private static bool IsNullOrEmpty(object val)
105+
{
106+
if (val == null)
107+
{
108+
return true;
109+
}
110+
111+
// Object is not null, check for an empty string
112+
var valString = val as string;
113+
if (valString != null)
114+
{
115+
return valString.Length == 0;
116+
}
117+
118+
// Object is not a string, check for an empty ICollection (faster)
119+
var valCollection = val as ICollection;
120+
if (valCollection != null)
121+
{
122+
return valCollection.Count == 0;
123+
}
124+
125+
// Object is not an ICollection, check for an empty IEnumerable
126+
var valEnumerable = val as IEnumerable;
127+
if (valEnumerable != null)
128+
{
129+
foreach (var item in valEnumerable)
130+
{
131+
// Found an item, not empty
132+
return false;
133+
}
134+
135+
return true;
136+
}
137+
138+
// Not null and not a known type to test for emptiness
139+
return false;
140+
}
141+
}

components/SettingsControls/src/SettingsCard/SettingsCard.xaml

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -294,22 +294,12 @@
294294
</VisualState.Setters>
295295
</VisualState>
296296
</VisualStateGroup>
297+
297298
<VisualStateGroup x:Name="ContentAlignmentStates">
299+
<!-- Default -->
298300
<VisualState x:Name="Right" />
299-
<VisualState x:Name="Left">
300-
<VisualState.StateTriggers>
301-
<labs:IsEqualStateTrigger Value="{Binding ContentAlignment, RelativeSource={RelativeSource TemplatedParent}}"
302-
To="Left" />
303-
</VisualState.StateTriggers>
304-
<VisualState.Setters>
305-
<Setter Target="PART_HeaderIconPresenterHolder.Visibility" Value="Collapsed" />
306-
<Setter Target="PART_DescriptionPresenter.Visibility" Value="Collapsed" />
307-
<Setter Target="PART_HeaderPresenter.Visibility" Value="Collapsed" />
308-
<Setter Target="PART_ContentPresenter.(Grid.Row)" Value="1" />
309-
<Setter Target="PART_ContentPresenter.(Grid.Column)" Value="1" />
310-
<Setter Target="PART_ContentPresenter.HorizontalAlignment" Value="Left" />
311-
</VisualState.Setters>
312-
</VisualState>
301+
302+
<!-- Whenever the control width is less than SettingsCardWrapThreshold, the Content is below the Header/Description -->
313303
<VisualState x:Name="RightWrapped">
314304
<VisualState.StateTriggers>
315305
<labs:ControlSizeTrigger MinWidth="{ThemeResource SettingsCardWrapNoIconThreshold}"
@@ -325,6 +315,8 @@
325315
<Setter Target="HeaderPanel.Margin" Value="0" />
326316
</VisualState.Setters>
327317
</VisualState>
318+
319+
<!-- For even smaller widths: the HeaderIcon is collapsed. -->
328320
<VisualState x:Name="RightWrappedNoIcon">
329321
<VisualState.StateTriggers>
330322
<labs:ControlSizeTrigger MaxWidth="{ThemeResource SettingsCardWrapNoIconThreshold}"
@@ -340,6 +332,24 @@
340332
<Setter Target="HeaderPanel.Margin" Value="0" />
341333
</VisualState.Setters>
342334
</VisualState>
335+
336+
<!-- Header/Description/Icon collapsed, content is to the left. Great for e.g. Checkboxes -->
337+
<VisualState x:Name="Left">
338+
<VisualState.StateTriggers>
339+
<labs:IsEqualStateTrigger Value="{Binding ContentAlignment, RelativeSource={RelativeSource TemplatedParent}}"
340+
To="Left" />
341+
</VisualState.StateTriggers>
342+
<VisualState.Setters>
343+
<Setter Target="PART_HeaderIconPresenterHolder.Visibility" Value="Collapsed" />
344+
<Setter Target="PART_DescriptionPresenter.Visibility" Value="Collapsed" />
345+
<Setter Target="PART_HeaderPresenter.Visibility" Value="Collapsed" />
346+
<Setter Target="PART_ContentPresenter.(Grid.Row)" Value="1" />
347+
<Setter Target="PART_ContentPresenter.(Grid.Column)" Value="1" />
348+
<Setter Target="PART_ContentPresenter.HorizontalAlignment" Value="Left" />
349+
</VisualState.Setters>
350+
</VisualState>
351+
352+
<!-- Similiar to Left, but the HeaderIcon/Header/Description is visible -->
343353
<VisualState x:Name="Vertical">
344354
<VisualState.StateTriggers>
345355
<labs:IsEqualStateTrigger Value="{Binding ContentAlignment, RelativeSource={RelativeSource TemplatedParent}}"
@@ -354,6 +364,19 @@
354364
</VisualState.Setters>
355365
</VisualState>
356366
</VisualStateGroup>
367+
368+
<!-- Collapsing the Content presenter whenever it's empty -->
369+
<VisualStateGroup x:Name="ContentVisibilityStates">
370+
<VisualState x:Name="Visible" />
371+
<VisualState x:Name="Collapsed">
372+
<VisualState.StateTriggers>
373+
<labs:IsNullOrEmptyStateTrigger Value="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" />
374+
</VisualState.StateTriggers>
375+
<VisualState.Setters>
376+
<Setter Target="PART_ContentPresenter.Visibility" Value="Collapsed" />
377+
</VisualState.Setters>
378+
</VisualState>
379+
</VisualStateGroup>
357380
</VisualStateManager.VisualStateGroups>
358381

359382
<Viewbox x:Name="PART_HeaderIconPresenterHolder"

0 commit comments

Comments
 (0)