Browse Source

Merge pull request #5998 from AvaloniaUI/nullable/diagnostics

Enable nullable on Avalonia.Diagnostics project
pull/6039/head
Max Katz 5 years ago
committed by GitHub
parent
commit
74dbc5b26c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 81
      src/Avalonia.Base/Metadata/NullableAttributes.cs
  2. 4
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  3. 8
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs
  4. 11
      src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs
  5. 4
      src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs
  6. 10
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  7. 4
      src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs
  8. 4
      src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs
  9. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs
  10. 45
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
  11. 13
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
  12. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs
  13. 35
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  14. 12
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
  15. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs
  16. 23
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
  17. 6
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs
  18. 11
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs
  19. 22
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs
  20. 15
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs
  21. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
  22. 23
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  23. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
  24. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
  25. 10
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs
  26. 16
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  27. 39
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
  28. 14
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  29. 12
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs
  30. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  31. 14
      src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs
  32. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs
  33. 8
      src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs
  34. 33
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  35. 24
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
  36. 2
      src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs

81
src/Avalonia.Base/Metadata/NullableAttributes.cs

@ -1,6 +1,5 @@
#pragma warning disable MA0048 // File name must match type name
#define INTERNAL_NULLABLE_ATTRIBUTES
#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
// https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
@ -10,6 +9,7 @@
namespace System.Diagnostics.CodeAnalysis
{
#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
@ -136,5 +136,82 @@ namespace System.Diagnostics.CodeAnalysis
/// <summary>Gets the condition parameter value.</summary>
public bool ParameterValue { get; }
}
}
#endif // NETSTANDARD2_0 attributes
#if NETSTANDARD2_1 || NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
/// <summary>
/// Specifies that the method or property will ensure that the listed field and property members have
/// not-<see langword="null"/> values.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class MemberNotNullAttribute : Attribute
{
/// <summary>Gets field or property member names.</summary>
public string[] Members { get; }
/// <summary>Initializes the attribute with a field or property member.</summary>
/// <param name="member">The field or property member that is promised to be not-null.</param>
public MemberNotNullAttribute(string member)
{
Members = new[] { member };
}
/// <summary>Initializes the attribute with the list of field and property members.</summary>
/// <param name="members">The list of field and property members that are promised to be not-null.</param>
public MemberNotNullAttribute(params string[] members)
{
Members = members;
}
}
/// <summary>
/// Specifies that the method or property will ensure that the listed field and property members have
/// non-<see langword="null"/> values when returning with the specified return value condition.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class MemberNotNullWhenAttribute : Attribute
{
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
/// <summary>Gets field or property member names.</summary>
public string[] Members { get; }
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value,
/// the associated parameter will not be <see langword="null"/>.
/// </param>
/// <param name="member">The field or property member that is promised to be not-<see langword="null"/>.</param>
public MemberNotNullWhenAttribute(bool returnValue, string member)
{
ReturnValue = returnValue;
Members = new[] { member };
}
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.
/// </summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value,
/// the associated parameter will not be <see langword="null"/>.
/// </param>
/// <param name="members">The list of field and property members that are promised to be not-null.</param>
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
{
ReturnValue = returnValue;
Members = members;
}
}
#endif // NETSTANDARD2_1 attributes
}

4
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -3,12 +3,16 @@
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace>
<PackageId>Avalonia.Diagnostics</PackageId>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

8
src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs

@ -10,8 +10,8 @@ namespace Avalonia.Diagnostics.Controls
AvaloniaProperty.RegisterDirect<ThicknessEditor, Thickness>(nameof(Thickness), o => o.Thickness,
(o, v) => o.Thickness = v, defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<ThicknessEditor, string> HeaderProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, string>(nameof(Header), o => o.Header,
public static readonly DirectProperty<ThicknessEditor, string?> HeaderProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, string?>(nameof(Header), o => o.Header,
(o, v) => o.Header = v);
public static readonly DirectProperty<ThicknessEditor, bool> IsPresentProperty =
@ -36,7 +36,7 @@ namespace Avalonia.Diagnostics.Controls
AvaloniaProperty.Register<ThicknessEditor, IBrush>(nameof(Highlight));
private Thickness _thickness;
private string _header;
private string? _header;
private bool _isPresent = true;
private double _left;
private double _top;
@ -50,7 +50,7 @@ namespace Avalonia.Diagnostics.Controls
set => SetAndRaise(ThicknessProperty, ref _thickness, value);
}
public string Header
public string? Header
{
get => _header;
set => SetAndRaise(HeaderProperty, ref _header, value);

11
src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs

@ -8,12 +8,17 @@ namespace Avalonia.Diagnostics.Converters
{
public double Opacity { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return (bool)value ? 1d : Opacity;
if (value is bool boolean && boolean)
{
return 1d;
}
return Opacity;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}

4
src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs

@ -7,12 +7,12 @@ namespace Avalonia.Diagnostics.Converters
{
internal class EnumToCheckedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return Equals(value, parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool isChecked && isChecked)
{

10
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -6,8 +6,6 @@ using Avalonia.Diagnostics.Views;
using Avalonia.Input;
using Avalonia.Interactivity;
#nullable enable
namespace Avalonia.Diagnostics
{
public static class DevTools
@ -24,7 +22,7 @@ namespace Avalonia.Diagnostics
public static IDisposable Attach(TopLevel root, DevToolsOptions options)
{
void PreviewKeyDown(object sender, KeyEventArgs e)
void PreviewKeyDown(object? sender, KeyEventArgs e)
{
if (options.Gesture.Matches(e))
{
@ -71,10 +69,10 @@ namespace Avalonia.Diagnostics
return Disposable.Create(() => window?.Close());
}
private static void DevToolsClosed(object sender, EventArgs e)
private static void DevToolsClosed(object? sender, EventArgs e)
{
var window = (MainWindow)sender;
s_open.Remove(window.Root);
var window = (MainWindow)sender!;
s_open.Remove(window.Root!);
window.Closed -= DevToolsClosed;
}
}

4
src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs

@ -22,8 +22,8 @@ The following commands are available:
clear(): Clear the output history
";
public dynamic e { get; internal set; }
public dynamic root { get; internal set; }
public dynamic? e { get; internal set; }
public dynamic? root { get; internal set; }
internal static object NoOutput { get; } = new object();

4
src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs

@ -7,9 +7,7 @@ namespace Avalonia.Diagnostics.Models
{
public EventChainLink(object handler, bool handled, RoutingStrategies route)
{
Contract.Requires<ArgumentNullException>(handler != null);
Handler = handler;
Handler = handler ?? throw new ArgumentNullException(nameof(handler));
Handled = handled;
Route = route;
}

4
src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs

@ -9,12 +9,12 @@ namespace Avalonia.Diagnostics
{
public IControl Build(object data)
{
var name = data.GetType().FullName.Replace("ViewModel", "View");
var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type);
return (Control)Activator.CreateInstance(type)!;
}
else
{

45
src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs

@ -1,17 +1,17 @@
using System.ComponentModel;
using Avalonia.Collections;
namespace Avalonia.Diagnostics.ViewModels
{
internal class AvaloniaPropertyViewModel : PropertyViewModel
{
private readonly AvaloniaObject _target;
private string _type;
private object _value;
private object? _value;
private string _priority;
private string _group;
#nullable disable
// Remove "nullable disable" after MemberNotNull will work on our CI.
public AvaloniaPropertyViewModel(AvaloniaObject o, AvaloniaProperty property)
#nullable restore
{
_target = o;
Property = property;
@ -20,12 +20,6 @@ namespace Avalonia.Diagnostics.ViewModels
$"[{property.OwnerType.Name}.{property.Name}]" :
property.Name;
if (property.IsDirect)
{
_group = "Properties";
Priority = "Direct";
}
Update();
}
@ -34,11 +28,7 @@ namespace Avalonia.Diagnostics.ViewModels
public override string Name { get; }
public bool IsAttached => Property.IsAttached;
public string Priority
{
get => _priority;
private set => RaiseAndSetIfChanged(ref _priority, value);
}
public string Priority => _priority;
public override string Type => _type;
@ -56,40 +46,37 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public override string Group
{
get => _group;
}
public override string Group => _group;
// [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))]
public override void Update()
{
if (Property.IsDirect)
{
RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type));
RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority));
_group = "Properties";
}
else
{
var val = _target.GetDiagnostic(Property);
RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type));
if (val != null)
{
SetGroup(IsAttached ? "Attached Properties" : "Properties");
Priority = val.Priority.ToString();
RaiseAndSetIfChanged(ref _priority, val.Priority.ToString(), nameof(Priority));
RaiseAndSetIfChanged(ref _group, IsAttached ? "Attached Properties" : "Properties", nameof(Group));
}
else
{
SetGroup(Priority = "Unset");
RaiseAndSetIfChanged(ref _priority, "Unset", nameof(Priority));
RaiseAndSetIfChanged(ref _group, "Unset", nameof(Group));
}
}
}
private void SetGroup(string group)
{
RaiseAndSetIfChanged(ref _group, group, nameof(Group));
}
}
}

13
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs

@ -1,5 +1,4 @@
using System.ComponentModel;
using System.Reflection;
using System.Reflection;
namespace Avalonia.Diagnostics.ViewModels
{
@ -7,14 +6,17 @@ namespace Avalonia.Diagnostics.ViewModels
{
private readonly object _target;
private string _type;
private object _value;
private object? _value;
#nullable disable
// Remove "nullable disable" after MemberNotNull will work on our CI.
public ClrPropertyViewModel(object o, PropertyInfo property)
#nullable restore
{
_target = o;
Property = property;
if (!property.DeclaringType.IsInterface)
if (property.DeclaringType == null || !property.DeclaringType.IsInterface)
{
Name = property.Name;
}
@ -47,11 +49,12 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
// [MemberNotNull(nameof(_type))]
public override void Update()
{
var val = Property.GetValue(_target);
RaiseAndSetIfChanged(ref _value, val, nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type));
}
}
}

3
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs

@ -15,11 +15,12 @@ namespace Avalonia.Diagnostics.ViewModels
private int _historyIndex = -1;
private string _input;
private bool _isVisible;
private ScriptState<object> _state;
private ScriptState<object>? _state;
public ConsoleViewModel(Action<ConsoleContext> updateContext)
{
_context = new ConsoleContext(this);
_input = string.Empty;
_updateContext = updateContext;
}

35
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -18,10 +18,10 @@ namespace Avalonia.Diagnostics.ViewModels
{
private readonly IVisual _control;
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex;
private AvaloniaPropertyViewModel _selectedProperty;
private AvaloniaPropertyViewModel? _selectedProperty;
private bool _snapshotStyles;
private bool _showInactiveStyles;
private string _styleStatus;
private string? _styleStatus;
public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control)
{
@ -83,7 +83,8 @@ namespace Avalonia.Diagnostics.ViewModels
{
foreach (var setter in style.Setters)
{
if (setter is Setter regularSetter)
if (setter is Setter regularSetter
&& regularSetter.Property != null)
{
var setterValue = regularSetter.Value;
@ -115,13 +116,14 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
private (object resourceKey, bool isDynamic)? GetResourceInfo(object value)
private (object resourceKey, bool isDynamic)? GetResourceInfo(object? value)
{
if (value is StaticResourceExtension staticResource)
{
return (staticResource.ResourceKey, false);
}
else if (value is DynamicResourceExtension dynamicResource)
else if (value is DynamicResourceExtension dynamicResource
&& dynamicResource.ResourceKey != null)
{
return (dynamicResource.ResourceKey, true);
}
@ -137,7 +139,7 @@ namespace Avalonia.Diagnostics.ViewModels
public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; }
public AvaloniaPropertyViewModel SelectedProperty
public AvaloniaPropertyViewModel? SelectedProperty
{
get => _selectedProperty;
set => RaiseAndSetIfChanged(ref _selectedProperty, value);
@ -155,7 +157,7 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _showInactiveStyles, value);
}
public string StyleStatus
public string? StyleStatus
{
get => _styleStatus;
set => RaiseAndSetIfChanged(ref _styleStatus, value);
@ -248,7 +250,7 @@ namespace Avalonia.Diagnostics.ViewModels
.Select(x => new ClrPropertyViewModel(o, x));
}
private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (_propertyIndex.TryGetValue(e.Property, out var properties))
{
@ -261,9 +263,10 @@ namespace Avalonia.Diagnostics.ViewModels
Layout.ControlPropertyChanged(sender, e);
}
private void ControlPropertyChanged(object sender, PropertyChangedEventArgs e)
private void ControlPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (_propertyIndex.TryGetValue(e.PropertyName, out var properties))
if (e.PropertyName != null
&& _propertyIndex.TryGetValue(e.PropertyName, out var properties))
{
foreach (var property in properties)
{
@ -277,7 +280,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
private void OnClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
private void OnClassesChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (!SnapshotStyles)
{
@ -349,10 +352,10 @@ namespace Avalonia.Diagnostics.ViewModels
{
public static PropertyComparer Instance { get; } = new PropertyComparer();
public int Compare(PropertyViewModel x, PropertyViewModel y)
public int Compare(PropertyViewModel? x, PropertyViewModel? y)
{
var groupX = GroupIndex(x.Group);
var groupY = GroupIndex(y.Group);
var groupX = GroupIndex(x?.Group);
var groupY = GroupIndex(y?.Group);
if (groupX != groupY)
{
@ -360,11 +363,11 @@ namespace Avalonia.Diagnostics.ViewModels
}
else
{
return string.CompareOrdinal(x.Name, y.Name);
return string.CompareOrdinal(x?.Name, y?.Name);
}
}
private int GroupIndex(string group)
private int GroupIndex(string? group)
{
switch (group)
{

12
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs

@ -12,14 +12,14 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly IVisual _control;
private Thickness _borderThickness;
private double _height;
private string _heightConstraint;
private string? _heightConstraint;
private HorizontalAlignment _horizontalAlignment;
private Thickness _marginThickness;
private Thickness _paddingThickness;
private bool _updatingFromControl;
private VerticalAlignment _verticalAlignment;
private double _width;
private string _widthConstraint;
private string? _widthConstraint;
public ControlLayoutViewModel(IVisual control)
{
@ -80,13 +80,13 @@ namespace Avalonia.Diagnostics.ViewModels
private set => RaiseAndSetIfChanged(ref _height, value);
}
public string WidthConstraint
public string? WidthConstraint
{
get => _widthConstraint;
private set => RaiseAndSetIfChanged(ref _widthConstraint, value);
}
public string HeightConstraint
public string? HeightConstraint
{
get => _heightConstraint;
private set => RaiseAndSetIfChanged(ref _heightConstraint, value);
@ -112,7 +112,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
if (_control is IAvaloniaObject ao)
{
string CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty)
string? CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty)
{
bool hasMin = ao.IsSet(minProperty);
bool hasMax = ao.IsSet(maxProperty);
@ -179,7 +179,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
public void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
try
{

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs

@ -27,7 +27,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (_updateChildren && value != null)
{
foreach (var child in Children)
foreach (var child in Children!)
{
try
{

23
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs

@ -11,16 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels
{
private readonly EventsPageViewModel _parentViewModel;
private bool _isRegistered;
private FiredEvent _currentEvent;
private FiredEvent? _currentEvent;
public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsPageViewModel vm)
: base(parent, @event.Name)
{
Contract.Requires<ArgumentNullException>(@event != null);
Contract.Requires<ArgumentNullException>(vm != null);
Event = @event;
_parentViewModel = vm;
Event = @event ?? throw new ArgumentNullException(nameof(@event));
_parentViewModel = vm ?? throw new ArgumentNullException(nameof(vm));
}
public RoutedEvent Event { get; }
@ -62,18 +59,18 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
private void HandleEvent(object sender, RoutedEventArgs e)
private void HandleEvent(object? sender, RoutedEventArgs e)
{
if (!_isRegistered || IsEnabled == false)
return;
if (sender is IVisual v && BelongsToDevTool(v))
return;
var s = sender;
var s = sender!;
var handled = e.Handled;
var route = e.Route;
Action handler = delegate
void handler()
{
if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e))
{
@ -98,14 +95,16 @@ namespace Avalonia.Diagnostics.ViewModels
private static bool BelongsToDevTool(IVisual v)
{
while (v != null)
var current = v;
while (current != null)
{
if (v is MainView || v is MainWindow)
if (current is MainView || current is MainWindow)
{
return true;
}
v = v.VisualParent;
current = current.VisualParent;
}
return false;

6
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs

@ -10,14 +10,14 @@ namespace Avalonia.Diagnostics.ViewModels
private bool? _isEnabled = false;
private bool _isVisible;
protected EventTreeNodeBase(EventTreeNodeBase parent, string text)
protected EventTreeNodeBase(EventTreeNodeBase? parent, string text)
{
Parent = parent;
Text = text;
IsVisible = true;
}
public IAvaloniaReadOnlyList<EventTreeNodeBase> Children
public IAvaloniaReadOnlyList<EventTreeNodeBase>? Children
{
get;
protected set;
@ -41,7 +41,7 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _isVisible, value);
}
public EventTreeNodeBase Parent
public EventTreeNodeBase? Parent
{
get;
}

11
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
@ -23,8 +22,8 @@ namespace Avalonia.Diagnostics.ViewModels
};
private readonly MainViewModel _mainViewModel;
private FiredEvent _selectedEvent;
private EventTreeNodeBase _selectedNode;
private FiredEvent? _selectedEvent;
private EventTreeNodeBase? _selectedNode;
public EventsPageViewModel(MainViewModel mainViewModel)
{
@ -48,13 +47,13 @@ namespace Avalonia.Diagnostics.ViewModels
public ObservableCollection<FiredEvent> RecordedEvents { get; } = new ObservableCollection<FiredEvent>();
public FiredEvent SelectedEvent
public FiredEvent? SelectedEvent
{
get => _selectedEvent;
set => RaiseAndSetIfChanged(ref _selectedEvent, value);
}
public EventTreeNodeBase SelectedNode
public EventTreeNodeBase? SelectedNode
{
get => _selectedNode;
set => RaiseAndSetIfChanged(ref _selectedNode, value);
@ -99,7 +98,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
static EventTreeNodeBase FindNode(EventTreeNodeBase node, RoutedEvent eventType)
static EventTreeNodeBase? FindNode(EventTreeNodeBase node, RoutedEvent eventType)
{
if (node is EventTreeNode eventNode && eventNode.Event == eventType)
{

22
src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs

@ -11,10 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
private string _filterString = string.Empty;
private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter;
private string _processedFilter;
private Regex _filterRegex;
private Regex? _filterRegex;
public event EventHandler RefreshFilter;
public event EventHandler? RefreshFilter;
public bool HasErrors => _errors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public bool Filter(string input)
{
@ -31,13 +34,11 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
_processedFilter = FilterString.Trim();
try
{
var options = RegexOptions.Compiled;
var pattern = UseRegexFilter
? _processedFilter : Regex.Escape(_processedFilter);
? FilterString.Trim() : Regex.Escape(FilterString.Trim());
if (!UseCaseSensitiveFilter)
{
options |= RegexOptions.IgnoreCase;
@ -109,16 +110,13 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public IEnumerable GetErrors(string propertyName)
public IEnumerable GetErrors(string? propertyName)
{
if (_errors.TryGetValue(propertyName, out var error))
if (propertyName != null
&& _errors.TryGetValue(propertyName, out var error))
{
yield return error;
}
}
public bool HasErrors => _errors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
}

15
src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs

@ -8,15 +8,12 @@ namespace Avalonia.Diagnostics.ViewModels
internal class FiredEvent : ViewModelBase
{
private readonly RoutedEventArgs _eventArgs;
private EventChainLink _handledBy;
private EventChainLink? _handledBy;
public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator)
{
Contract.Requires<ArgumentNullException>(eventArgs != null);
Contract.Requires<ArgumentNullException>(originator != null);
_eventArgs = eventArgs;
Originator = originator;
_eventArgs = eventArgs ?? throw new ArgumentNullException(nameof(eventArgs));
Originator = originator ?? throw new ArgumentNullException(nameof(originator));
AddToChain(originator);
}
@ -25,7 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels
return e == _eventArgs;
}
public RoutedEvent Event => _eventArgs.RoutedEvent;
public RoutedEvent Event => _eventArgs.RoutedEvent!;
public bool IsHandled => HandledBy?.Handled == true;
@ -38,7 +35,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (IsHandled)
{
return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine +
$"strategies: {Event.RoutingStrategies}; handled by: {HandledBy.HandlerName}";
$"strategies: {Event.RoutingStrategies}; handled by: {HandledBy!.HandlerName}";
}
return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}";
@ -47,7 +44,7 @@ namespace Avalonia.Diagnostics.ViewModels
public EventChainLink Originator { get; }
public EventChainLink HandledBy
public EventChainLink? HandledBy
{
get => _handledBy;
set

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs

@ -7,22 +7,24 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class LogicalTreeNode : TreeNode
{
public LogicalTreeNode(ILogical logical, TreeNode parent)
public LogicalTreeNode(ILogical logical, TreeNode? parent)
: base((Control)logical, parent)
{
Children = new LogicalTreeNodeCollection(this, logical);
}
public override TreeNodeCollection Children { get; }
public static LogicalTreeNode[] Create(object control)
{
var logical = control as ILogical;
return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null;
return logical != null ? new[] { new LogicalTreeNode(logical, null) } : Array.Empty<LogicalTreeNode>();
}
internal class LogicalTreeNodeCollection : TreeNodeCollection
{
private readonly ILogical _control;
private IDisposable _subscription;
private IDisposable? _subscription;
public LogicalTreeNodeCollection(TreeNode owner, ILogical control)
: base(owner)

23
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -1,10 +1,10 @@
using System;
using System.ComponentModel;
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
@ -17,13 +17,16 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly IDisposable _pointerOverSubscription;
private ViewModelBase _content;
private int _selectedTab;
private string _focusedControl;
private string _pointerOverElement;
private string? _focusedControl;
private string? _pointerOverElement;
private bool _shouldVisualizeMarginPadding = true;
private bool _shouldVisualizeDirtyRects;
private bool _showFpsOverlay;
#nullable disable
// Remove "nullable disable" after MemberNotNull will work on our CI.
public MainViewModel(TopLevel root)
#nullable restore
{
_root = root;
_logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
@ -84,6 +87,7 @@ namespace Avalonia.Diagnostics.ViewModels
public ViewModelBase Content
{
get { return _content; }
// [MemberNotNull(nameof(_content))]
private set
{
if (_content is TreePageViewModel oldTree &&
@ -114,34 +118,35 @@ namespace Avalonia.Diagnostics.ViewModels
public int SelectedTab
{
get { return _selectedTab; }
// [MemberNotNull(nameof(_content))]
set
{
_selectedTab = value;
switch (value)
{
case 0:
Content = _logicalTree;
break;
case 1:
Content = _visualTree;
break;
case 2:
Content = _events;
break;
default:
Content = _logicalTree;
break;
}
RaisePropertyChanged();
}
}
public string FocusedControl
public string? FocusedControl
{
get { return _focusedControl; }
private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
}
public string PointerOverElement
public string? PointerOverElement
{
get { return _pointerOverElement; }
private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
@ -187,7 +192,7 @@ namespace Avalonia.Diagnostics.ViewModels
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
}
private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e)
private void KeyboardPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs

@ -18,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels
public abstract string Value { get; set; }
public abstract void Update();
protected static string ConvertToString(object value)
protected static string ConvertToString(object? value)
{
if (value is null)
{
@ -31,13 +31,13 @@ namespace Avalonia.Diagnostics.ViewModels
if (!converter.CanConvertTo(typeof(string)) ||
converter.GetType() == typeof(CollectionConverter))
{
return value.ToString();
return value.ToString() ?? "(null)";
}
return converter.ConvertToString(value);
}
private static object InvokeParse(string s, Type targetType)
private static object? InvokeParse(string s, Type targetType)
{
var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null);
@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
throw new InvalidCastException("Unable to convert value.");
}
protected static object ConvertFromString(string s, Type targetType)
protected static object? ConvertFromString(string s, Type targetType)
{
var converter = TypeDescriptor.GetConverter(targetType);

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs

@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics.ViewModels
public IBrush Tint { get; }
public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object resourceValue, bool isDynamic) : base(property, resourceValue)
public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic) : base(property, resourceValue)
{
Key = resourceKey;
Tint = isDynamic ? Brushes.Orange : Brushes.Brown;
@ -16,12 +16,14 @@ namespace Avalonia.Diagnostics.ViewModels
public void CopyResourceKey()
{
if (Key is null)
var textToCopy = Key?.ToString();
if (textToCopy is null)
{
return;
}
CopyToClipboard(Key.ToString());
CopyToClipboard(textToCopy);
}
}
}

10
src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs

@ -11,7 +11,7 @@ namespace Avalonia.Diagnostics.ViewModels
public string Name { get; }
public object Value { get; }
public object? Value { get; }
public bool IsActive
{
@ -25,7 +25,7 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _isVisible, value);
}
public SetterViewModel(AvaloniaProperty property, object value)
public SetterViewModel(AvaloniaProperty property, object? value)
{
Property = property;
Name = property.Name;
@ -36,12 +36,14 @@ namespace Avalonia.Diagnostics.ViewModels
public void CopyValue()
{
if (Value is null)
var textToCopy = Value?.ToString();
if (textToCopy is null)
{
return;
}
CopyToClipboard(Value.ToString());
CopyToClipboard(textToCopy);
}
public void CopyPropertyName()

16
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@ -9,17 +9,18 @@ using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
internal class TreeNode : ViewModelBase, IDisposable
internal abstract class TreeNode : ViewModelBase, IDisposable
{
private IDisposable _classesSubscription;
private IDisposable? _classesSubscription;
private string _classes;
private bool _isExpanded;
public TreeNode(IVisual visual, TreeNode parent)
public TreeNode(IVisual visual, TreeNode? parent)
{
Parent = parent;
Type = visual.GetType().Name;
Visual = visual;
_classes = string.Empty;
if (visual is IControl control)
{
@ -51,10 +52,9 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public TreeNodeCollection Children
public abstract TreeNodeCollection Children
{
get;
protected set;
}
public string Classes
@ -63,7 +63,7 @@ namespace Avalonia.Diagnostics.ViewModels
private set { RaiseAndSetIfChanged(ref _classes, value); }
}
public string ElementName
public string? ElementName
{
get;
}
@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.ViewModels
set { RaiseAndSetIfChanged(ref _isExpanded, value); }
}
public TreeNode Parent
public TreeNode? Parent
{
get;
}
@ -92,7 +92,7 @@ namespace Avalonia.Diagnostics.ViewModels
public void Dispose()
{
_classesSubscription.Dispose();
_classesSubscription?.Dispose();
Children.Dispose();
}

39
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs

@ -3,46 +3,33 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using Avalonia.Collections;
namespace Avalonia.Diagnostics.ViewModels
{
internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable
{
private AvaloniaList<TreeNode> _inner;
private AvaloniaList<TreeNode>? _inner;
public TreeNodeCollection(TreeNode owner) => Owner = owner;
public TreeNode this[int index]
{
get
{
EnsureInitialized();
return _inner[index];
}
}
public TreeNode this[int index] => EnsureInitialized()[index];
public int Count
{
get
{
EnsureInitialized();
return _inner.Count;
}
}
public int Count => EnsureInitialized().Count;
protected TreeNode Owner { get; }
public event NotifyCollectionChangedEventHandler CollectionChanged
public event NotifyCollectionChangedEventHandler? CollectionChanged
{
add => _inner.CollectionChanged += value;
remove => _inner.CollectionChanged -= value;
add => EnsureInitialized().CollectionChanged += value;
remove => EnsureInitialized().CollectionChanged -= value;
}
public event PropertyChangedEventHandler PropertyChanged
public event PropertyChangedEventHandler? PropertyChanged
{
add => _inner.PropertyChanged += value;
remove => _inner.PropertyChanged -= value;
add => EnsureInitialized().PropertyChanged += value;
remove => EnsureInitialized().PropertyChanged -= value;
}
public virtual void Dispose()
@ -58,21 +45,21 @@ namespace Avalonia.Diagnostics.ViewModels
public IEnumerator<TreeNode> GetEnumerator()
{
EnsureInitialized();
return _inner.GetEnumerator();
return EnsureInitialized().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
protected abstract void Initialize(AvaloniaList<TreeNode> nodes);
private void EnsureInitialized()
private AvaloniaList<TreeNode> EnsureInitialized()
{
if (_inner is null)
{
_inner = new AvaloniaList<TreeNode>();
Initialize(_inner);
}
return _inner;
}
}
}

14
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -6,8 +6,8 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class TreePageViewModel : ViewModelBase, IDisposable
{
private TreeNode _selectedNode;
private ControlDetailsViewModel _details;
private TreeNode? _selectedNode;
private ControlDetailsViewModel? _details;
public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes)
{
@ -29,7 +29,7 @@ namespace Avalonia.Diagnostics.ViewModels
public TreeNode[] Nodes { get; protected set; }
public TreeNode SelectedNode
public TreeNode? SelectedNode
{
get => _selectedNode;
private set
@ -44,7 +44,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public ControlDetailsViewModel Details
public ControlDetailsViewModel? Details
{
get => _details;
private set
@ -68,7 +68,7 @@ namespace Avalonia.Diagnostics.ViewModels
_details?.Dispose();
}
public TreeNode FindNode(IControl control)
public TreeNode? FindNode(IControl control)
{
foreach (var node in Nodes)
{
@ -104,7 +104,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
private void ExpandNode(TreeNode node)
private void ExpandNode(TreeNode? node)
{
if (node != null)
{
@ -113,7 +113,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
private TreeNode FindNode(TreeNode node, IControl control)
private TreeNode? FindNode(TreeNode node, IControl control)
{
if (node.Visual == control)
{

12
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs

@ -1,16 +1,16 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Avalonia.Diagnostics.ViewModels
{
internal class ViewModelBase : INotifyPropertyChanged
{
private PropertyChangedEventHandler _propertyChanged;
private PropertyChangedEventHandler? _propertyChanged;
private List<string> events = new List<string>();
public event PropertyChangedEventHandler PropertyChanged
public event PropertyChangedEventHandler? PropertyChanged
{
add { _propertyChanged += value; events.Add("added"); }
remove { _propertyChanged -= value; events.Add("removed"); }
@ -20,7 +20,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
}
protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
protected bool RaiseAndSetIfChanged<T>([NotNullIfNotNull("value")] ref T field, T value, [CallerMemberName] string propertyName = null!)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.ViewModels
return false;
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null!)
{
var e = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(e);

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class VisualTreeNode : TreeNode
{
public VisualTreeNode(IVisual visual, TreeNode parent)
public VisualTreeNode(IVisual visual, TreeNode? parent)
: base(visual, parent)
{
Children = new VisualTreeNodeCollection(this, visual);
@ -20,16 +20,18 @@ namespace Avalonia.Diagnostics.ViewModels
public bool IsInTemplate { get; private set; }
public override TreeNodeCollection Children { get; }
public static VisualTreeNode[] Create(object control)
{
var visual = control as IVisual;
return visual != null ? new[] { new VisualTreeNode(visual, null) } : null;
return visual != null ? new[] { new VisualTreeNode(visual, null) } : Array.Empty<VisualTreeNode>();
}
internal class VisualTreeNodeCollection : TreeNodeCollection
{
private readonly IVisual _control;
private IDisposable _subscription;
private IDisposable? _subscription;
public VisualTreeNodeCollection(TreeNode owner, IVisual control)
: base(owner)

14
src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs

@ -30,22 +30,26 @@ namespace Avalonia.Diagnostics.Views
AvaloniaXamlLoader.Load(this);
}
private void HistoryChanged(object sender, NotifyCollectionChangedEventArgs e)
private void HistoryChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems[0] is IControl control)
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems?[0] is IControl control)
{
DispatcherTimer.RunOnce(control.BringIntoView, TimeSpan.Zero);
}
}
private void InputKeyDown(object sender, KeyEventArgs e)
private void InputKeyDown(object? sender, KeyEventArgs e)
{
var vm = (ConsoleViewModel)DataContext;
var vm = (ConsoleViewModel?)DataContext;
if (vm is null)
{
return;
}
switch (e.Key)
{
case Key.Enter:
vm.Execute();
_ = vm.Execute();
e.Handled = true;
break;
case Key.Up:

2
src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs

@ -53,7 +53,7 @@ namespace Avalonia.Diagnostics.Views
}
}
private void OnRecordedEventsChanged(object sender, NotifyCollectionChangedEventArgs e)
private void OnRecordedEventsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (sender is ObservableCollection<FiredEvent> events)
{

8
src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs

@ -27,7 +27,11 @@ namespace Avalonia.Diagnostics.Views
public void ToggleConsole()
{
var vm = (MainViewModel)DataContext;
var vm = (MainViewModel?)DataContext;
if (vm is null)
{
return;
}
if (_consoleHeight == -1)
{
@ -54,7 +58,7 @@ namespace Avalonia.Diagnostics.Views
AvaloniaXamlLoader.Load(this);
}
private void PreviewKeyDown(object sender, KeyEventArgs e)
private void PreviewKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{

33
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.Views
internal class MainWindow : Window, IStyleHost
{
private readonly IDisposable _keySubscription;
private TopLevel _root;
private TopLevel? _root;
public MainWindow()
{
@ -26,7 +26,7 @@ namespace Avalonia.Diagnostics.Views
.Subscribe(RawKeyDown);
}
public TopLevel Root
public TopLevel? Root
{
get => _root;
set
@ -43,7 +43,7 @@ namespace Avalonia.Diagnostics.Views
if (_root != null)
{
_root.Closed += RootClosed;
DataContext = new MainViewModel(value);
DataContext = new MainViewModel(_root);
}
else
{
@ -53,15 +53,20 @@ namespace Avalonia.Diagnostics.Views
}
}
IStyleHost IStyleHost.StylingParent => null;
IStyleHost? IStyleHost.StylingParent => null;
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_keySubscription.Dispose();
_root.Closed -= RootClosed;
_root = null;
((MainViewModel)DataContext)?.Dispose();
if (_root != null)
{
_root.Closed -= RootClosed;
_root = null;
}
((MainViewModel?)DataContext)?.Dispose();
}
private void InitializeComponent()
@ -71,12 +76,20 @@ namespace Avalonia.Diagnostics.Views
private void RawKeyDown(RawKeyEventArgs e)
{
var vm = (MainViewModel?)DataContext;
if (vm is null)
{
return;
}
const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift;
if (e.Modifiers == modifiers)
{
#pragma warning disable CS0618 // Type or member is obsolete
var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default;
#pragma warning restore CS0618 // Type or member is obsolete
var control = Root.GetVisualsAt(point, x =>
{
if (x is AdornerLayer || !x.IsVisible) return false;
@ -87,7 +100,6 @@ namespace Avalonia.Diagnostics.Views
if (control != null)
{
var vm = (MainViewModel)DataContext;
vm.SelectControl((IControl)control);
}
}
@ -97,12 +109,11 @@ namespace Avalonia.Diagnostics.Views
{
var enable = e.Key == Key.S;
var vm = (MainViewModel)DataContext;
vm.EnableSnapshotStyles(enable);
}
}
}
private void RootClosed(object sender, EventArgs e) => Close();
private void RootClosed(object? sender, EventArgs e) => Close();
}
}

24
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs

@ -14,12 +14,13 @@ namespace Avalonia.Diagnostics.Views
internal class TreePageView : UserControl
{
private readonly Panel _adorner;
private AdornerLayer _currentLayer;
private AdornerLayer? _currentLayer;
private TreeView _tree;
public TreePageView()
{
InitializeComponent();
_tree = this.FindControl<TreeView>("tree");
_tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized;
_adorner = new Panel
@ -37,9 +38,15 @@ namespace Avalonia.Diagnostics.Views
};
}
protected void AddAdorner(object sender, PointerEventArgs e)
protected void AddAdorner(object? sender, PointerEventArgs e)
{
var node = (TreeNode)((Control)sender).DataContext;
var node = (TreeNode?)((Control)sender!).DataContext;
var vm = (TreePageViewModel?)DataContext;
if (node is null || vm is null)
{
return;
}
var visual = (Visual)node.Visual;
_currentLayer = AdornerLayer.GetAdornerLayer(visual);
@ -53,8 +60,6 @@ namespace Avalonia.Diagnostics.Views
_currentLayer.Children.Add(_adorner);
AdornerLayer.SetAdornedElement(_adorner, visual);
var vm = (TreePageViewModel) DataContext;
if (vm.MainView.ShouldVisualizeMarginPadding)
{
var paddingBorder = (Border)_adorner.Children[0];
@ -74,7 +79,7 @@ namespace Avalonia.Diagnostics.Views
return new Thickness(-input.Left, -input.Top, -input.Right, -input.Bottom);
}
protected void RemoveAdorner(object sender, PointerEventArgs e)
protected void RemoveAdorner(object? sender, PointerEventArgs e)
{
foreach (var border in _adorner.Children.OfType<Border>())
{
@ -90,18 +95,17 @@ namespace Avalonia.Diagnostics.Views
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_tree = this.FindControl<TreeView>("tree");
}
private void TreeViewItemMaterialized(object sender, ItemContainerEventArgs e)
private void TreeViewItemMaterialized(object? sender, ItemContainerEventArgs e)
{
var item = (TreeViewItem)e.Containers[0].ContainerControl;
item.TemplateApplied += TreeViewItemTemplateApplied;
}
private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e)
private void TreeViewItemTemplateApplied(object? sender, TemplateAppliedEventArgs e)
{
var item = (TreeViewItem)sender;
var item = (TreeViewItem)sender!;
// This depends on the default tree item template.
// We want to handle events in the item header but exclude events coming from children.

2
src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs

@ -17,7 +17,7 @@ namespace Avalonia.Diagnostics
private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent)
{
Control control = visual as Control;
Control? control = visual as Control;
builder.Append(Indent(indent - 1));

Loading…
Cancel
Save