Browse Source

Merge remote-tracking branch 'origin/animations/tryfix-scb-localvalue' into animations/tryfix-scb-localvalue

pull/5886/head
Jumar Macato 5 years ago
parent
commit
5fcc321c66
No known key found for this signature in database GPG Key ID: B19884DAC3A5BF3F
  1. 22
      src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToBrushConverter.cs
  2. 2
      src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs
  3. 10
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs
  4. 9
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
  5. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs
  6. 160
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs
  7. 15
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs
  8. 17
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  9. 135
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  10. 57
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs

22
src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToBrushConverter.cs

@ -1,22 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace Avalonia.Diagnostics.Converters
{
internal class BoolToBrushConverter : IValueConverter
{
public IBrush Brush { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Brush : Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

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

@ -16,6 +16,8 @@ namespace Avalonia.Diagnostics.Models
public object Handler { get; } public object Handler { get; }
public bool BeginsNewRoute { get; set; }
public string HandlerName public string HandlerName
{ {
get get

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

@ -2,25 +2,17 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class EventOwnerTreeNode : EventTreeNodeBase internal class EventOwnerTreeNode : EventTreeNodeBase
{ {
private static readonly RoutedEvent[] s_defaultEvents =
{
Button.ClickEvent, InputElement.KeyDownEvent, InputElement.KeyUpEvent, InputElement.TextInputEvent,
InputElement.PointerReleasedEvent, InputElement.PointerPressedEvent
};
public EventOwnerTreeNode(Type type, IEnumerable<RoutedEvent> events, EventsPageViewModel vm) public EventOwnerTreeNode(Type type, IEnumerable<RoutedEvent> events, EventsPageViewModel vm)
: base(null, type.Name) : base(null, type.Name)
{ {
Children = new AvaloniaList<EventTreeNodeBase>(events.OrderBy(e => e.Name) Children = new AvaloniaList<EventTreeNodeBase>(events.OrderBy(e => e.Name)
.Select(e => new EventTreeNode(this, e, vm) { IsEnabled = s_defaultEvents.Contains(e) })); .Select(e => new EventTreeNode(this, e, vm)));
IsExpanded = true; IsExpanded = true;
} }

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

@ -9,7 +9,6 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
internal class EventTreeNode : EventTreeNodeBase internal class EventTreeNode : EventTreeNodeBase
{ {
private readonly RoutedEvent _event;
private readonly EventsPageViewModel _parentViewModel; private readonly EventsPageViewModel _parentViewModel;
private bool _isRegistered; private bool _isRegistered;
private FiredEvent _currentEvent; private FiredEvent _currentEvent;
@ -20,10 +19,12 @@ namespace Avalonia.Diagnostics.ViewModels
Contract.Requires<ArgumentNullException>(@event != null); Contract.Requires<ArgumentNullException>(@event != null);
Contract.Requires<ArgumentNullException>(vm != null); Contract.Requires<ArgumentNullException>(vm != null);
_event = @event; Event = @event;
_parentViewModel = vm; _parentViewModel = vm;
} }
public RoutedEvent Event { get; }
public override bool? IsEnabled public override bool? IsEnabled
{ {
get => base.IsEnabled; get => base.IsEnabled;
@ -53,8 +54,10 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
if (IsEnabled.GetValueOrDefault() && !_isRegistered) if (IsEnabled.GetValueOrDefault() && !_isRegistered)
{ {
var allRoutes = RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble;
// FIXME: This leaks event handlers. // FIXME: This leaks event handlers.
_event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); Event.AddClassHandler(typeof(object), HandleEvent, allRoutes, handledEventsToo: true);
_isRegistered = true; _isRegistered = true;
} }
} }

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

@ -8,11 +8,13 @@ namespace Avalonia.Diagnostics.ViewModels
internal bool _updateParent = true; internal bool _updateParent = true;
private bool _isExpanded; private bool _isExpanded;
private bool? _isEnabled = false; private bool? _isEnabled = false;
private bool _isVisible;
protected EventTreeNodeBase(EventTreeNodeBase parent, string text) protected EventTreeNodeBase(EventTreeNodeBase parent, string text)
{ {
Parent = parent; Parent = parent;
Text = text; Text = text;
IsVisible = true;
} }
public IAvaloniaReadOnlyList<EventTreeNodeBase> Children public IAvaloniaReadOnlyList<EventTreeNodeBase> Children
@ -33,6 +35,12 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _isEnabled, value); set => RaiseAndSetIfChanged(ref _isEnabled, value);
} }
public bool IsVisible
{
get => _isVisible;
set => RaiseAndSetIfChanged(ref _isVisible, value);
}
public EventTreeNodeBase Parent public EventTreeNodeBase Parent
{ {
get; get;

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

@ -1,28 +1,43 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Globalization; using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data.Converters; using Avalonia.Diagnostics.Models;
using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class EventsPageViewModel : ViewModelBase internal class EventsPageViewModel : ViewModelBase
{ {
private readonly IControl _root; private static readonly HashSet<RoutedEvent> s_defaultEvents = new HashSet<RoutedEvent>()
{
Button.ClickEvent,
InputElement.KeyDownEvent,
InputElement.KeyUpEvent,
InputElement.TextInputEvent,
InputElement.PointerReleasedEvent,
InputElement.PointerPressedEvent
};
private readonly MainViewModel _mainViewModel;
private string _eventTypeFilter;
private FiredEvent _selectedEvent; private FiredEvent _selectedEvent;
private EventTreeNodeBase _selectedNode;
public EventsPageViewModel(IControl root) public EventsPageViewModel(MainViewModel mainViewModel)
{ {
_root = root; _mainViewModel = mainViewModel;
Nodes = RoutedEventRegistry.Instance.GetAllRegistered() Nodes = RoutedEventRegistry.Instance.GetAllRegistered()
.GroupBy(e => e.OwnerType) .GroupBy(e => e.OwnerType)
.OrderBy(e => e.Key.Name) .OrderBy(e => e.Key.Name)
.Select(g => new EventOwnerTreeNode(g.Key, g, this)) .Select(g => new EventOwnerTreeNode(g.Key, g, this))
.ToArray(); .ToArray();
EnableDefault();
} }
public string Name => "Events"; public string Name => "Events";
@ -37,9 +52,140 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _selectedEvent, value); set => RaiseAndSetIfChanged(ref _selectedEvent, value);
} }
private void Clear() public EventTreeNodeBase SelectedNode
{
get => _selectedNode;
set => RaiseAndSetIfChanged(ref _selectedNode, value);
}
public string EventTypeFilter
{
get => _eventTypeFilter;
set => RaiseAndSetIfChanged(ref _eventTypeFilter, value);
}
public void Clear()
{ {
RecordedEvents.Clear(); RecordedEvents.Clear();
} }
public void DisableAll()
{
EvaluateNodeEnabled(_ => false);
}
public void EnableDefault()
{
EvaluateNodeEnabled(node => s_defaultEvents.Contains(node.Event));
}
public void RequestTreeNavigateTo(EventChainLink navTarget)
{
if (navTarget.Handler is IControl control)
{
_mainViewModel.RequestTreeNavigateTo(control, true);
}
}
public void SelectEventByType(RoutedEvent evt)
{
foreach (var node in Nodes)
{
var result = FindNode(node, evt);
if (result != null && result.IsVisible)
{
SelectedNode = result;
break;
}
}
static EventTreeNodeBase FindNode(EventTreeNodeBase node, RoutedEvent eventType)
{
if (node is EventTreeNode eventNode && eventNode.Event == eventType)
{
return node;
}
if (node.Children != null)
{
foreach (var child in node.Children)
{
var result = FindNode(child, eventType);
if (result != null)
{
return result;
}
}
}
return null;
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == nameof(EventTypeFilter))
{
UpdateEventFilters();
}
}
private void EvaluateNodeEnabled(Func<EventTreeNode, bool> eval)
{
void ProcessNode(EventTreeNodeBase node)
{
if (node is EventTreeNode eventNode)
{
node.IsEnabled = eval(eventNode);
}
if (node.Children != null)
{
foreach (var childNode in node.Children)
{
ProcessNode(childNode);
}
}
}
foreach (var node in Nodes)
{
ProcessNode(node);
}
}
private void UpdateEventFilters()
{
var filter = EventTypeFilter;
bool hasFilter = !string.IsNullOrEmpty(filter);
foreach (var node in Nodes)
{
FilterNode(node, false);
}
bool FilterNode(EventTreeNodeBase node, bool isParentVisible)
{
bool matchesFilter = !hasFilter || node.Text.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
bool hasVisibleChild = false;
if (node.Children != null)
{
foreach (var childNode in node.Children)
{
hasVisibleChild |= FilterNode(childNode, matchesFilter);
}
}
node.IsVisible = hasVisibleChild || matchesFilter || isParentVisible;
return node.IsVisible;
}
}
} }
} }

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

@ -62,13 +62,18 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public void AddToChain(object handler, bool handled, RoutingStrategies route)
{
AddToChain(new EventChainLink(handler, handled, route));
}
public void AddToChain(EventChainLink link) public void AddToChain(EventChainLink link)
{ {
if (EventChain.Count > 0)
{
var prevLink = EventChain[EventChain.Count-1];
if (prevLink.Route != link.Route)
{
link.BeginsNewRoute = true;
}
}
EventChain.Add(link); EventChain.Add(link);
if (HandledBy == null && link.Handled) if (HandledBy == null && link.Handled)
HandledBy = link; HandledBy = link;

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

@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Diagnostics.Models; using Avalonia.Diagnostics.Models;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -27,7 +28,7 @@ namespace Avalonia.Diagnostics.ViewModels
_root = root; _root = root;
_logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root)); _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
_visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root)); _visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root));
_events = new EventsPageViewModel(root); _events = new EventsPageViewModel(this);
UpdateFocusedControl(); UpdateFocusedControl();
KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged; KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged;
@ -193,5 +194,19 @@ namespace Avalonia.Diagnostics.ViewModels
UpdateFocusedControl(); UpdateFocusedControl();
} }
} }
public void RequestTreeNavigateTo(IControl control, bool isVisualTree)
{
var tree = isVisualTree ? _visualTree : _logicalTree;
var node = tree.FindNode(control);
if (node != null)
{
SelectedTab = isVisualTree ? 1 : 0;
tree.SelectControl(control);
}
}
} }
} }

135
src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml

@ -2,58 +2,129 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters" xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters"
x:Class="Avalonia.Diagnostics.Views.EventsPageView"> x:Class="Avalonia.Diagnostics.Views.EventsPageView"
<UserControl.Resources> Margin="2">
<conv:BoolToBrushConverter x:Key="boolToBrush" Brush="#d9ffdc"/> <UserControl.Styles>
</UserControl.Resources> <Style Selector="TextBlock.nav" >
<Grid ColumnDefinitions="*,4,3*"> <Setter Property="TextDecorations">
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" <TextDecorationCollection>
Grid.RowSpan="2"> <TextDecoration Location="Underline" Stroke="Black" StrokeThickness="1" StrokeDashArray="2,2"/>
<TreeView.DataTemplates> </TextDecorationCollection>
<TreeDataTemplate DataType="vm:EventTreeNodeBase" </Setter>
ItemsSource="{Binding Children}"> </Style>
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsEnabled, Mode=TwoWay}" />
</TreeDataTemplate> <Style Selector="TextBlock.nav:pointerover" >
</TreeView.DataTemplates> <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}" />
<TreeView.Styles> <Setter Property="Cursor" Value="Help" />
<Style Selector="TreeViewItem"> </Style>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style> <Style Selector="ListBoxItem" >
</TreeView.Styles> <Setter Property="BorderThickness" Value="1" />
</TreeView> </Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter" >
<Setter Property="BorderBrush" Value="Black" />
</Style>
<Style Selector="ListBoxItem.handled" >
<Setter Property="Background" Value="#d9ffdc" />
</Style>
</UserControl.Styles>
<Grid ColumnDefinitions="1.1*,4,3*">
<Grid Grid.Column="0" RowDefinitions="Auto,*,Auto">
<TextBox Classes="clearButton" Grid.Row="0" Margin="0,0,0,2" Text="{Binding EventTypeFilter}" Watermark="Search event types" />
<TreeView Grid.Row="1" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" >
<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}" />
<Setter Property="IsVisible" Value="{Binding IsVisible}" />
</Style>
</TreeView.Styles>
</TreeView>
<StackPanel Grid.Row="2" Margin="0,2" Orientation="Horizontal" Spacing="2">
<Button Content="Disable all" Command="{Binding DisableAll}" />
<Button Content="Enable default" Command="{Binding EnableDefault}" />
</StackPanel>
</Grid>
<GridSplitter Width="4" Grid.Column="1" /> <GridSplitter Width="4" Grid.Column="1" />
<Grid RowDefinitions="*,4,2*,Auto" Grid.Column="2"> <Grid RowDefinitions="*,4,2*,Auto" Grid.Column="2">
<ListBox Name="eventsList" Items="{Binding RecordedEvents}"
<ListBox Name="EventsList" Items="{Binding RecordedEvents}"
SelectedItem="{Binding SelectedEvent, Mode=TwoWay}"> SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Background="{Binding IsHandled, Converter={StaticResource boolToBrush}}" <ListBoxItem Classes.handled="{Binding IsHandled}">
Text="{Binding DisplayText}" /> <Grid ColumnDefinitions="Auto,Auto,*,Auto">
<StackPanel Grid.Column="0" Spacing="2" Orientation="Horizontal" >
<TextBlock Tag="{Binding Event}" DoubleTapped="NavigateTo" Text="{Binding Event.Name}" FontWeight="Bold" Classes="nav" />
<TextBlock Text="on" />
<TextBlock Tag="{Binding Originator}" DoubleTapped="NavigateTo" Text="{Binding Originator.HandlerName}" Classes="nav" />
</StackPanel>
<StackPanel Margin="2,0,0,0" Grid.Column="1" Spacing="2" Orientation="Horizontal" IsVisible="{Binding IsHandled}" >
<TextBlock Text="::" />
<TextBlock Text="Handled by" />
<TextBlock Tag="{Binding HandledBy}" DoubleTapped="NavigateTo" Text="{Binding HandledBy.HandlerName}" Classes="nav" />
</StackPanel>
<StackPanel Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="Routing (" />
<TextBlock Text="{Binding Event.RoutingStrategies}"/>
<TextBlock Text=")"/>
</StackPanel>
</Grid>
</ListBoxItem>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
<GridSplitter Height="4" Grid.Row="1" /> <GridSplitter Height="4" Grid.Row="1" />
<DockPanel Grid.Row="2" LastChildFill="True"> <DockPanel Grid.Row="2" LastChildFill="True">
<TextBlock DockPanel.Dock="Top" FontSize="16" Text="Event chain:" /> <TextBlock DockPanel.Dock="Top" FontSize="16" Text="Event chain:" />
<ListBox Items="{Binding SelectedEvent.EventChain}"> <ListBox Items="{Binding SelectedEvent.EventChain}">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal" <ListBoxItem Classes.handled="{Binding Handled}">
Background="{Binding Handled, Converter={StaticResource boolToBrush}}"> <StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Route}" />
<TextBlock Text=": " /> <Rectangle IsVisible="{Binding BeginsNewRoute}" StrokeDashArray="2,2" StrokeThickness="1" Stroke="Gray" />
<TextBlock Text="{Binding HandlerName}" />
<TextBlock Text=" handled: " /> <StackPanel Orientation="Horizontal" Spacing="2">
<TextBlock Text="{Binding Handled}" /> <TextBlock Text="{Binding Route}" FontWeight="Bold" />
</StackPanel> <TextBlock Tag="{Binding}" DoubleTapped="NavigateTo" Text="{Binding HandlerName}" Classes="nav" />
</StackPanel>
</StackPanel>
</ListBoxItem>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
</DockPanel> </DockPanel>
<StackPanel Orientation="Horizontal" Grid.Row="3">
<Button Content="Clear" Margin="3" Command="{Binding Clear}" /> <StackPanel Orientation="Horizontal" Grid.Row="3" Spacing="2" Margin="0,2">
<Button Content="Clear" Command="{Binding Clear}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Grid> </Grid>
</UserControl> </UserControl>

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

@ -1,7 +1,14 @@
using System.Linq; using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Diagnostics.ViewModels; using Avalonia.Diagnostics.ViewModels;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading;
namespace Avalonia.Diagnostics.Views namespace Avalonia.Diagnostics.Views
{ {
@ -12,13 +19,53 @@ namespace Avalonia.Diagnostics.Views
public EventsPageView() public EventsPageView()
{ {
InitializeComponent(); InitializeComponent();
_events = this.FindControl<ListBox>("events"); _events = this.FindControl<ListBox>("EventsList");
} }
private void RecordedEvents_CollectionChanged(object sender, public void NavigateTo(object sender, TappedEventArgs e)
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{ {
_events.ScrollIntoView(_events.Items.OfType<FiredEvent>().LastOrDefault()); if (DataContext is EventsPageViewModel vm && sender is Control control)
{
switch (control.Tag)
{
case EventChainLink chainLink:
{
vm.RequestTreeNavigateTo(chainLink);
break;
}
case RoutedEvent evt:
{
vm.SelectEventByType(evt);
break;
}
}
}
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is EventsPageViewModel vm)
{
vm.RecordedEvents.CollectionChanged += OnRecordedEventsChanged;
}
}
private void OnRecordedEventsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (sender is ObservableCollection<FiredEvent> events)
{
var evt = events.LastOrDefault();
if (evt is null)
{
return;
}
Dispatcher.UIThread.Post(() => _events.ScrollIntoView(evt));
}
} }
private void InitializeComponent() private void InitializeComponent()

Loading…
Cancel
Save