committed by
GitHub
12 changed files with 537 additions and 0 deletions
@ -0,0 +1,38 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Diagnostics.Models |
|||
{ |
|||
internal class EventChainLink |
|||
{ |
|||
public EventChainLink(object handler, bool handled, RoutingStrategies route) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(handler != null); |
|||
|
|||
this.Handler = handler; |
|||
this.Handled = handled; |
|||
this.Route = route; |
|||
} |
|||
|
|||
public object Handler { get; } |
|||
|
|||
public string HandlerName |
|||
{ |
|||
get |
|||
{ |
|||
if (Handler is INamed named && !string.IsNullOrEmpty(named.Name)) |
|||
{ |
|||
return named.Name + " (" + Handler.GetType().Name + ")"; |
|||
} |
|||
return Handler.GetType().Name; |
|||
} |
|||
} |
|||
|
|||
public bool Handled { get; } |
|||
|
|||
public RoutingStrategies Route { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class EventOwnerTreeNode : EventTreeNodeBase |
|||
{ |
|||
private static readonly RoutedEvent[] s_defaultEvents = new RoutedEvent[] |
|||
{ |
|||
Button.ClickEvent, |
|||
InputElement.KeyDownEvent, |
|||
InputElement.KeyUpEvent, |
|||
InputElement.TextInputEvent, |
|||
InputElement.PointerReleasedEvent, |
|||
InputElement.PointerPressedEvent, |
|||
}; |
|||
|
|||
public EventOwnerTreeNode(Type type, IEnumerable<RoutedEvent> events, EventsViewModel vm) |
|||
: base(null, type.Name) |
|||
{ |
|||
this.Children = new AvaloniaList<EventTreeNodeBase>(events.OrderBy(e => e.Name) |
|||
.Select(e => new EventTreeNode(this, e, vm) { IsEnabled = s_defaultEvents.Contains(e) })); |
|||
this.IsExpanded = true; |
|||
} |
|||
|
|||
public override bool? IsEnabled |
|||
{ |
|||
get => base.IsEnabled; |
|||
set |
|||
{ |
|||
if (base.IsEnabled != value) |
|||
{ |
|||
base.IsEnabled = value; |
|||
if (_updateChildren && value != null) |
|||
{ |
|||
foreach (var child in Children) |
|||
{ |
|||
try |
|||
{ |
|||
child._updateParent = false; |
|||
child.IsEnabled = value; |
|||
} |
|||
finally |
|||
{ |
|||
child._updateParent = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
using Avalonia.Diagnostics.Models; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Threading; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class EventTreeNode : EventTreeNodeBase |
|||
{ |
|||
private RoutedEvent _event; |
|||
private EventsViewModel _parentViewModel; |
|||
private bool _isRegistered; |
|||
private FiredEvent _currentEvent; |
|||
|
|||
public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsViewModel vm) |
|||
: base(parent, @event.Name) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(@event != null); |
|||
Contract.Requires<ArgumentNullException>(vm != null); |
|||
|
|||
this._event = @event; |
|||
this._parentViewModel = vm; |
|||
} |
|||
|
|||
public override bool? IsEnabled |
|||
{ |
|||
get => base.IsEnabled; |
|||
set |
|||
{ |
|||
if (base.IsEnabled != value) |
|||
{ |
|||
base.IsEnabled = value; |
|||
UpdateTracker(); |
|||
if (Parent != null && _updateParent) |
|||
{ |
|||
try |
|||
{ |
|||
Parent._updateChildren = false; |
|||
Parent.UpdateChecked(); |
|||
} |
|||
finally |
|||
{ |
|||
Parent._updateChildren = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void UpdateTracker() |
|||
{ |
|||
if (IsEnabled.GetValueOrDefault() && !_isRegistered) |
|||
{ |
|||
_event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); |
|||
_isRegistered = true; |
|||
} |
|||
} |
|||
|
|||
private void HandleEvent(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (!_isRegistered || IsEnabled == false) |
|||
return; |
|||
if (sender is IVisual v && DevTools.BelongsToDevTool(v)) |
|||
return; |
|||
|
|||
var s = sender; |
|||
var handled = e.Handled; |
|||
var route = e.Route; |
|||
|
|||
Action handler = delegate |
|||
{ |
|||
if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e)) |
|||
{ |
|||
_currentEvent = new FiredEvent(e, new EventChainLink(s, handled, route)); |
|||
|
|||
_parentViewModel.RecordedEvents.Add(_currentEvent); |
|||
|
|||
while (_parentViewModel.RecordedEvents.Count > 100) |
|||
_parentViewModel.RecordedEvents.RemoveAt(0); |
|||
} |
|||
else |
|||
{ |
|||
_currentEvent.AddToChain(new EventChainLink(s, handled, route)); |
|||
} |
|||
}; |
|||
|
|||
if (!Dispatcher.UIThread.CheckAccess()) |
|||
Dispatcher.UIThread.Post(handler); |
|||
else |
|||
handler(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal abstract class EventTreeNodeBase : ViewModelBase |
|||
{ |
|||
internal bool _updateChildren = true; |
|||
internal bool _updateParent = true; |
|||
private bool _isExpanded; |
|||
private bool? _isEnabled = false; |
|||
|
|||
public EventTreeNodeBase(EventTreeNodeBase parent, string text) |
|||
{ |
|||
this.Parent = parent; |
|||
this.Text = text; |
|||
} |
|||
|
|||
public IAvaloniaReadOnlyList<EventTreeNodeBase> Children |
|||
{ |
|||
get; |
|||
protected set; |
|||
} |
|||
|
|||
public bool IsExpanded |
|||
{ |
|||
get { return _isExpanded; } |
|||
set { RaiseAndSetIfChanged(ref _isExpanded, value); } |
|||
} |
|||
|
|||
public virtual bool? IsEnabled |
|||
{ |
|||
get { return _isEnabled; } |
|||
set { RaiseAndSetIfChanged(ref _isEnabled, value); } |
|||
} |
|||
|
|||
public EventTreeNodeBase Parent |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
public string Text |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
internal void UpdateChecked() |
|||
{ |
|||
IsEnabled = GetValue(); |
|||
|
|||
bool? GetValue() |
|||
{ |
|||
if (Children == null) |
|||
return false; |
|||
bool? value = false; |
|||
for (int i = 0; i < Children.Count; i++) |
|||
{ |
|||
if (i == 0) |
|||
{ |
|||
value = Children[i].IsEnabled; |
|||
continue; |
|||
} |
|||
|
|||
if (value != Children[i].IsEnabled) |
|||
{ |
|||
value = null; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.ObjectModel; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Windows.Input; |
|||
|
|||
using Avalonia.Controls; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class EventsViewModel : ViewModelBase |
|||
{ |
|||
private readonly IControl _root; |
|||
private FiredEvent _selectedEvent; |
|||
|
|||
public EventsViewModel(IControl root) |
|||
{ |
|||
this._root = root; |
|||
this.Nodes = RoutedEventRegistry.Instance.GetAllRegistered() |
|||
.GroupBy(e => e.OwnerType) |
|||
.OrderBy(e => e.Key.Name) |
|||
.Select(g => new EventOwnerTreeNode(g.Key, g, this)) |
|||
.ToArray(); |
|||
} |
|||
|
|||
public EventTreeNodeBase[] Nodes { get; } |
|||
|
|||
public ObservableCollection<FiredEvent> RecordedEvents { get; } = new ObservableCollection<FiredEvent>(); |
|||
|
|||
public FiredEvent SelectedEvent |
|||
{ |
|||
get => _selectedEvent; |
|||
set => RaiseAndSetIfChanged(ref _selectedEvent, value); |
|||
} |
|||
|
|||
private void Clear() |
|||
{ |
|||
RecordedEvents.Clear(); |
|||
} |
|||
} |
|||
|
|||
internal class BoolToBrushConverter : IValueConverter |
|||
{ |
|||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
return (bool)value ? Brushes.LightGreen : Brushes.Transparent; |
|||
} |
|||
|
|||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.ObjectModel; |
|||
|
|||
using Avalonia.Diagnostics.Models; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class FiredEvent : ViewModelBase |
|||
{ |
|||
private RoutedEventArgs _eventArgs; |
|||
private EventChainLink _handledBy; |
|||
|
|||
public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(eventArgs != null); |
|||
Contract.Requires<ArgumentNullException>(originator != null); |
|||
|
|||
this._eventArgs = eventArgs; |
|||
this.Originator = originator; |
|||
AddToChain(originator); |
|||
} |
|||
|
|||
public bool IsPartOfSameEventChain(RoutedEventArgs e) |
|||
{ |
|||
return e == _eventArgs; |
|||
} |
|||
|
|||
public RoutedEvent Event => _eventArgs.RoutedEvent; |
|||
|
|||
public bool IsHandled => HandledBy?.Handled == true; |
|||
|
|||
public ObservableCollection<EventChainLink> EventChain { get; } = new ObservableCollection<EventChainLink>(); |
|||
|
|||
public string DisplayText |
|||
{ |
|||
get |
|||
{ |
|||
if (IsHandled) |
|||
{ |
|||
return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine + |
|||
$"strategies: {Event.RoutingStrategies}; handled by: {HandledBy.HandlerName}"; |
|||
} |
|||
return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}"; |
|||
} |
|||
} |
|||
|
|||
public EventChainLink Originator { get; } |
|||
|
|||
public EventChainLink HandledBy |
|||
{ |
|||
get { return _handledBy; } |
|||
set |
|||
{ |
|||
if (_handledBy != value) |
|||
{ |
|||
_handledBy = value; |
|||
RaisePropertyChanged(); |
|||
RaisePropertyChanged(nameof(IsHandled)); |
|||
RaisePropertyChanged(nameof(DisplayText)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void AddToChain(object handler, bool handled, RoutingStrategies route) |
|||
{ |
|||
AddToChain(new EventChainLink(handler, handled, route)); |
|||
} |
|||
|
|||
public void AddToChain(EventChainLink link) |
|||
{ |
|||
EventChain.Add(link); |
|||
if (HandledBy == null && link.Handled) |
|||
HandledBy = link; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"> |
|||
<UserControl.Resources> |
|||
<vm:BoolToBrushConverter x:Key="boolToBrush" /> |
|||
</UserControl.Resources> |
|||
<Grid ColumnDefinitions="*,4,3*"> |
|||
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" Grid.RowSpan="2"> |
|||
<TreeView.DataTemplates> |
|||
<TreeDataTemplate DataType="vm:EventTreeNodeBase" |
|||
ItemsSource="{Binding Children}"> |
|||
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsEnabled, Mode=TwoWay}" /> |
|||
</TreeDataTemplate> |
|||
</TreeView.DataTemplates> |
|||
<TreeView.Styles> |
|||
<Style Selector="TreeViewItem"> |
|||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> |
|||
</Style> |
|||
</TreeView.Styles> |
|||
</TreeView> |
|||
|
|||
<GridSplitter Width="4" Grid.Column="1" /> |
|||
<Grid RowDefinitions="*,4,2*,Auto" Grid.Column="2"> |
|||
<ListBox Name="eventsList" Items="{Binding RecordedEvents}" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}"> |
|||
<ListBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<TextBlock Background="{Binding IsHandled, Converter={StaticResource boolToBrush}}" Text="{Binding DisplayText}" /> |
|||
</DataTemplate> |
|||
</ListBox.ItemTemplate> |
|||
</ListBox> |
|||
<GridSplitter Height="4" Grid.Row="1" /> |
|||
<DockPanel Grid.Row="2" LastChildFill="True"> |
|||
<TextBlock DockPanel.Dock="Top" FontSize="16" Text="Event chain:" /> |
|||
<ListBox Items="{Binding SelectedEvent.EventChain}"> |
|||
<ListBox.ItemTemplate> |
|||
<DataTemplate> |
|||
<StackPanel Orientation="Horizontal" Background="{Binding Handled, Converter={StaticResource boolToBrush}}"> |
|||
<TextBlock Text="{Binding Route}" /> |
|||
<TextBlock Text=": " /> |
|||
<TextBlock Text="{Binding HandlerName}" /> |
|||
<TextBlock Text=" handled: " /> |
|||
<TextBlock Text="{Binding Handled}" /> |
|||
</StackPanel> |
|||
</DataTemplate> |
|||
</ListBox.ItemTemplate> |
|||
</ListBox> |
|||
</DockPanel> |
|||
<StackPanel Orientation="Horizontal" Grid.Row="3"> |
|||
<Button Content="Clear" Margin="3" Command="{Binding Clear}" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
</Grid> |
|||
</UserControl> |
|||
@ -0,0 +1,32 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Linq; |
|||
|
|||
using Avalonia.Controls; |
|||
using Avalonia.Diagnostics.ViewModels; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace Avalonia.Diagnostics.Views |
|||
{ |
|||
public class EventsView : UserControl |
|||
{ |
|||
private ListBox _events; |
|||
|
|||
public EventsView() |
|||
{ |
|||
this.InitializeComponent(); |
|||
_events = this.FindControl<ListBox>("events"); |
|||
} |
|||
|
|||
private void RecordedEvents_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
_events.ScrollIntoView(_events.Items.OfType<FiredEvent>().LastOrDefault()); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue