committed by
GitHub
368 changed files with 15151 additions and 4477 deletions
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,134 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.TextBlockPage"> |
|||
<StackPanel> |
|||
<TextBlock Classes="h1">TextBlock</TextBlock> |
|||
<TextBlock Classes="h2">A control that can display text</TextBlock> |
|||
<StackPanel |
|||
Orientation="Horizontal" |
|||
Spacing="16" |
|||
HorizontalAlignment="Center" |
|||
Margin="0,16,0,0"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Border"> |
|||
<Setter Property="BorderThickness" Value="1"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> |
|||
<Setter Property="Padding" Value="2"/> |
|||
</Style> |
|||
</StackPanel.Styles> |
|||
<Border> |
|||
<StackPanel Width="200" Spacing="8"> |
|||
<TextBlock TextTrimming="CharacterEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/> |
|||
<TextBlock TextTrimming="WordEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/> |
|||
<TextBlock Text="Left aligned text" TextAlignment="Left" /> |
|||
<TextBlock Text="Center aligned text" TextAlignment="Center" /> |
|||
<TextBlock Text="Right aligned text" TextAlignment="Right" /> |
|||
</StackPanel> |
|||
</Border> |
|||
<Border> |
|||
<StackPanel Width="200" Spacing="8"> |
|||
<TextBlock |
|||
TextWrapping="Wrap" |
|||
Text="Multiline TextBlock with TextWrapping.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> |
|||
</StackPanel> |
|||
</Border> |
|||
<Border> |
|||
<StackPanel Width="200" Spacing="8"> |
|||
<TextBlock Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/> |
|||
<TextBlock Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/> |
|||
<TextBlock Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/> |
|||
<TextBlock Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/> |
|||
</StackPanel> |
|||
</Border> |
|||
</StackPanel> |
|||
<StackPanel |
|||
Orientation="Horizontal" |
|||
Spacing="16" |
|||
HorizontalAlignment="Center" |
|||
Margin="0,16,0,0"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Border"> |
|||
<Setter Property="BorderThickness" Value="1"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> |
|||
<Setter Property="Padding" Value="2"/> |
|||
</Style> |
|||
</StackPanel.Styles> |
|||
<Border> |
|||
<StackPanel Width="200" Spacing="8"> |
|||
<TextBlock TextDecorations="Underline" Text="Underline"/> |
|||
<TextBlock TextDecorations="Strikethrough" Text="Strikethrough"/> |
|||
<TextBlock TextDecorations="Overline" Text="Overline" /> |
|||
<TextBlock TextDecorations="Baseline" Text="Baseline"/> |
|||
<TextBlock Text="Custom TextDecorations"> |
|||
<TextBlock.TextDecorations> |
|||
<TextDecorationCollection> |
|||
<TextDecoration |
|||
Location="Overline" |
|||
PenThicknessUnit="Pixel"> |
|||
<TextDecoration.Pen> |
|||
<Pen Thickness="2"> |
|||
<Pen.Brush> |
|||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> |
|||
<LinearGradientBrush.GradientStops> |
|||
<GradientStop Offset="0" Color="Red"/> |
|||
<GradientStop Offset="1" Color="Green"/> |
|||
</LinearGradientBrush.GradientStops> |
|||
</LinearGradientBrush> |
|||
</Pen.Brush> |
|||
</Pen> |
|||
</TextDecoration.Pen> |
|||
</TextDecoration> |
|||
<TextDecoration |
|||
Location="Strikethrough" |
|||
PenThicknessUnit="Pixel"> |
|||
<TextDecoration.Pen> |
|||
<Pen Thickness="1"> |
|||
<Pen.Brush> |
|||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> |
|||
<LinearGradientBrush.GradientStops> |
|||
<GradientStop Offset="0" Color="Green"/> |
|||
<GradientStop Offset="1" Color="Blue"/> |
|||
</LinearGradientBrush.GradientStops> |
|||
</LinearGradientBrush> |
|||
</Pen.Brush> |
|||
</Pen> |
|||
</TextDecoration.Pen> |
|||
</TextDecoration> |
|||
<TextDecoration |
|||
Location="Underline" |
|||
PenThicknessUnit="Pixel"> |
|||
<TextDecoration.Pen> |
|||
<Pen Thickness="2"> |
|||
<Pen.Brush> |
|||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> |
|||
<LinearGradientBrush.GradientStops> |
|||
<GradientStop Offset="0" Color="Blue"/> |
|||
<GradientStop Offset="1" Color="Red"/> |
|||
</LinearGradientBrush.GradientStops> |
|||
</LinearGradientBrush> |
|||
</Pen.Brush> |
|||
</Pen> |
|||
</TextDecoration.Pen> |
|||
</TextDecoration> |
|||
</TextDecorationCollection> |
|||
</TextBlock.TextDecorations> |
|||
</TextBlock> |
|||
</StackPanel> |
|||
</Border> |
|||
<Border> |
|||
<StackPanel Width="200" Spacing="8"> |
|||
<TextBlock Text="🏻 👌🏻"/> |
|||
<TextBlock Text="🏼 👌🏼" /> |
|||
<TextBlock Text="🏽 👌🏽"/> |
|||
<TextBlock Text="🏾 👌🏾"/> |
|||
<TextBlock Text="🏿 👌🏿"/> |
|||
</StackPanel> |
|||
</Border> |
|||
<Border> |
|||
<StackPanel Width="200" Spacing="8"> |
|||
<TextBlock Text="👪 👨👩👧 👨👩👧👦"/> |
|||
</StackPanel> |
|||
</Border> |
|||
</StackPanel> |
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,18 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class TextBlockPage : UserControl |
|||
{ |
|||
public TextBlockPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// A visitor to resolve an untyped <see cref="AvaloniaProperty"/> to a typed property.
|
|||
/// </summary>
|
|||
/// <typeparam name="TData">The type of user data passed.</typeparam>
|
|||
/// <remarks>
|
|||
/// Pass an instance that implements this interface to
|
|||
/// <see cref="AvaloniaProperty.Accept{TData}(IAvaloniaPropertyVisitor{TData}, ref TData)"/>
|
|||
/// in order to resolve un untyped <see cref="AvaloniaProperty"/> to a typed
|
|||
/// <see cref="StyledPropertyBase{TValue}"/> or <see cref="DirectPropertyBase{TValue}"/>.
|
|||
/// </remarks>
|
|||
public interface IAvaloniaPropertyVisitor<TData> |
|||
where TData : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Called when the property is a styled property.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property value type.</typeparam>
|
|||
/// <param name="property">The property.</param>
|
|||
/// <param name="data">The user data.</param>
|
|||
void Visit<T>(StyledPropertyBase<T> property, ref TData data); |
|||
|
|||
/// <summary>
|
|||
/// Called when the property is a direct property.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property value type.</typeparam>
|
|||
/// <param name="property">The property.</param>
|
|||
/// <param name="data">The user data.</param>
|
|||
void Visit<T>(DirectPropertyBase<T> property, ref TData data); |
|||
} |
|||
} |
|||
@ -0,0 +1,195 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Input; |
|||
|
|||
namespace Avalonia.Controls.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a <see cref="KeyGesture"/> to a string, formatting it according to the current
|
|||
/// platform's style guidelines.
|
|||
/// </summary>
|
|||
public class PlatformKeyGestureConverter : IValueConverter |
|||
{ |
|||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
if (value is null) |
|||
{ |
|||
return null; |
|||
} |
|||
else if (value is KeyGesture gesture && targetType == typeof(string)) |
|||
{ |
|||
return ToPlatformString(gesture); |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
|
|||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="KeyGesture"/> to a string, formatting it according to the current
|
|||
/// platform's style guidelines.
|
|||
/// </summary>
|
|||
/// <param name="gesture">The gesture.</param>
|
|||
/// <returns>The gesture formatted according to the current platform.</returns>
|
|||
public static string ToPlatformString(KeyGesture gesture) |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
return ToString(gesture, "Win"); |
|||
} |
|||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
|||
{ |
|||
return ToString(gesture, "Super"); |
|||
} |
|||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|||
{ |
|||
return ToOSXString(gesture); |
|||
} |
|||
else |
|||
{ |
|||
return gesture.ToString(); |
|||
} |
|||
} |
|||
|
|||
private static string ToString(KeyGesture gesture, string meta) |
|||
{ |
|||
var s = new StringBuilder(); |
|||
|
|||
static void Plus(StringBuilder s) |
|||
{ |
|||
if (s.Length > 0) |
|||
{ |
|||
s.Append("+"); |
|||
} |
|||
} |
|||
|
|||
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Control)) |
|||
{ |
|||
s.Append("Ctrl"); |
|||
} |
|||
|
|||
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Shift)) |
|||
{ |
|||
Plus(s); |
|||
s.Append("Shift"); |
|||
} |
|||
|
|||
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)) |
|||
{ |
|||
Plus(s); |
|||
s.Append("Alt"); |
|||
} |
|||
|
|||
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Meta)) |
|||
{ |
|||
Plus(s); |
|||
s.Append(meta); |
|||
} |
|||
|
|||
Plus(s); |
|||
s.Append(ToString(gesture.Key)); |
|||
|
|||
return s.ToString(); |
|||
} |
|||
|
|||
private static string ToOSXString(KeyGesture gesture) |
|||
{ |
|||
var s = new StringBuilder(); |
|||
|
|||
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Control)) |
|||
{ |
|||
s.Append('⌃'); |
|||
} |
|||
|
|||
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)) |
|||
{ |
|||
s.Append('⌥'); |
|||
} |
|||
|
|||
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Shift)) |
|||
{ |
|||
s.Append('⇧'); |
|||
} |
|||
|
|||
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Meta)) |
|||
{ |
|||
s.Append('⌘'); |
|||
} |
|||
|
|||
s.Append(ToOSXString(gesture.Key)); |
|||
|
|||
return s.ToString(); |
|||
} |
|||
|
|||
private static string ToString(Key key) |
|||
{ |
|||
return key switch |
|||
{ |
|||
Key.Add => "+", |
|||
Key.Back => "Backspace", |
|||
Key.D0 => "0", |
|||
Key.D1 => "1", |
|||
Key.D2 => "2", |
|||
Key.D3 => "3", |
|||
Key.D4 => "4", |
|||
Key.D5 => "5", |
|||
Key.D6 => "6", |
|||
Key.D7 => "7", |
|||
Key.D8 => "8", |
|||
Key.D9 => "9", |
|||
Key.Decimal => ".", |
|||
Key.Divide => "/", |
|||
Key.Down => "Down Arrow", |
|||
Key.Left => "Left Arrow", |
|||
Key.Multiply => "*", |
|||
Key.OemBackslash => "\\", |
|||
Key.OemCloseBrackets => "]", |
|||
Key.OemComma => ",", |
|||
Key.OemMinus => "-", |
|||
Key.OemOpenBrackets => "[", |
|||
Key.OemPeriod=> ".", |
|||
Key.OemPipe => "|", |
|||
Key.OemPlus => "+", |
|||
Key.OemQuestion => "/", |
|||
Key.OemQuotes => "\"", |
|||
Key.OemSemicolon => ";", |
|||
Key.OemTilde => "`", |
|||
Key.Right => "Right Arrow", |
|||
Key.Separator => "/", |
|||
Key.Subtract => "-", |
|||
Key.Up => "Up Arrow", |
|||
_ => key.ToString(), |
|||
}; |
|||
} |
|||
|
|||
private static string ToOSXString(Key key) |
|||
{ |
|||
return key switch |
|||
{ |
|||
Key.Back => "⌫", |
|||
Key.Down => "↓", |
|||
Key.End => "↘", |
|||
Key.Escape => "⎋", |
|||
Key.Home => "↖", |
|||
Key.Left => "←", |
|||
Key.Return => "↩", |
|||
Key.PageDown => "⇞", |
|||
Key.PageUp => "⇟", |
|||
Key.Right => "→", |
|||
Key.Space => "␣", |
|||
Key.Tab => "⇥", |
|||
Key.Up => "↑", |
|||
_ => ToString(key), |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
@ -1,24 +0,0 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="Avalonia.Diagnostics.DevTools"> |
|||
<Grid RowDefinitions="*,Auto" Margin="4"> |
|||
|
|||
<TabControl Grid.Row="0" Items="{Binding Tools}" SelectedItem="{Binding SelectedTool}"> |
|||
<TabControl.ItemTemplate> |
|||
<DataTemplate> |
|||
<TextBlock Text="{Binding Name}" /> |
|||
</DataTemplate> |
|||
</TabControl.ItemTemplate> |
|||
</TabControl> |
|||
|
|||
<StackPanel Grid.Row="1" Spacing="4" Orientation="Horizontal"> |
|||
<TextBlock>Hold Ctrl+Shift over a control to inspect.</TextBlock> |
|||
<Separator Width="8" /> |
|||
<TextBlock>Focused:</TextBlock> |
|||
<TextBlock Text="{Binding FocusedControl}" /> |
|||
<Separator Width="8" /> |
|||
<TextBlock>Pointer Over:</TextBlock> |
|||
<TextBlock Text="{Binding PointerOverElement}" /> |
|||
</StackPanel> |
|||
</Grid> |
|||
</UserControl> |
|||
@ -1,159 +0,0 @@ |
|||
// 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 System.Reactive.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Diagnostics.ViewModels; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public static class DevToolsExtensions |
|||
{ |
|||
public static void AttachDevTools(this TopLevel control) |
|||
{ |
|||
Diagnostics.DevTools.Attach(control, new KeyGesture(Key.F12)); |
|||
} |
|||
|
|||
public static void AttachDevTools(this TopLevel control, KeyGesture gesture) |
|||
{ |
|||
Diagnostics.DevTools.Attach(control, gesture); |
|||
} |
|||
|
|||
public static void OpenDevTools(this TopLevel control) |
|||
{ |
|||
Diagnostics.DevTools.OpenDevTools(control); |
|||
} |
|||
} |
|||
} |
|||
|
|||
namespace Avalonia.Diagnostics |
|||
{ |
|||
public class DevTools : UserControl |
|||
{ |
|||
private static readonly Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>(); |
|||
private static readonly HashSet<IRenderRoot> s_visualTreeRoots = new HashSet<IRenderRoot>(); |
|||
private readonly IDisposable _keySubscription; |
|||
|
|||
public DevTools(IControl root) |
|||
{ |
|||
InitializeComponent(); |
|||
Root = root; |
|||
DataContext = new DevToolsViewModel(root); |
|||
|
|||
_keySubscription = InputManager.Instance.Process |
|||
.OfType<RawKeyEventArgs>() |
|||
.Subscribe(RawKeyDown); |
|||
} |
|||
|
|||
// HACK: needed for XAMLIL, will fix that later
|
|||
public DevTools() |
|||
{ |
|||
} |
|||
|
|||
public IControl Root { get; } |
|||
|
|||
public static IDisposable Attach(TopLevel control, KeyGesture gesture) |
|||
{ |
|||
void PreviewKeyDown(object sender, KeyEventArgs e) |
|||
{ |
|||
if (gesture.Matches(e)) |
|||
{ |
|||
OpenDevTools(control); |
|||
} |
|||
} |
|||
|
|||
return control.AddHandler( |
|||
KeyDownEvent, |
|||
PreviewKeyDown, |
|||
RoutingStrategies.Tunnel); |
|||
} |
|||
|
|||
internal static void OpenDevTools(TopLevel control) |
|||
{ |
|||
if (s_open.TryGetValue(control, out var devToolsWindow)) |
|||
{ |
|||
devToolsWindow.Activate(); |
|||
} |
|||
else |
|||
{ |
|||
var devTools = new DevTools(control); |
|||
|
|||
devToolsWindow = new Window |
|||
{ |
|||
Width = 1024, |
|||
Height = 512, |
|||
Content = devTools, |
|||
DataTemplates = { new ViewLocator<ViewModelBase>() }, |
|||
Title = "Avalonia DevTools" |
|||
}; |
|||
|
|||
devToolsWindow.Closed += devTools.DevToolsClosed; |
|||
s_open.Add(control, devToolsWindow); |
|||
MarkAsDevTool(devToolsWindow); |
|||
devToolsWindow.Show(); |
|||
} |
|||
} |
|||
|
|||
private void DevToolsClosed(object sender, EventArgs e) |
|||
{ |
|||
var devToolsWindow = (Window)sender; |
|||
var devTools = (DevTools)devToolsWindow.Content; |
|||
s_open.Remove((TopLevel)devTools.Root); |
|||
RemoveDevTool(devToolsWindow); |
|||
_keySubscription.Dispose(); |
|||
devToolsWindow.Closed -= DevToolsClosed; |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
private void RawKeyDown(RawKeyEventArgs e) |
|||
{ |
|||
const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift; |
|||
|
|||
if (e.Modifiers == modifiers) |
|||
{ |
|||
var point = (Root.VisualRoot as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default(Point); |
|||
var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible)) |
|||
.FirstOrDefault(); |
|||
|
|||
if (control != null) |
|||
{ |
|||
var vm = (DevToolsViewModel)DataContext; |
|||
vm.SelectControl((IControl)control); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Marks a visual as part of the DevTools, so it can be excluded from event tracking.
|
|||
/// </summary>
|
|||
/// <param name="visual">The visual whose root is to be marked.</param>
|
|||
public static void MarkAsDevTool(IVisual visual) |
|||
{ |
|||
s_visualTreeRoots.Add(visual.GetVisualRoot()); |
|||
} |
|||
|
|||
public static void RemoveDevTool(IVisual visual) |
|||
{ |
|||
s_visualTreeRoots.Remove(visual.GetVisualRoot()); |
|||
} |
|||
|
|||
public static bool BelongsToDevTool(IVisual visual) |
|||
{ |
|||
return s_visualTreeRoots.Contains(visual.GetVisualRoot()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Diagnostics; |
|||
using Avalonia.Input; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// Extension methods for attaching DevTools..
|
|||
/// </summary>
|
|||
public static class DevToolsExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Attaches DevTools to a window, to be opened with the F12 key.
|
|||
/// </summary>
|
|||
/// <param name="root">The window to attach DevTools to.</param>
|
|||
public static void AttachDevTools(this TopLevel root) |
|||
{ |
|||
DevTools.Attach(root, new KeyGesture(Key.F12)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attaches DevTools to a window, to be opened with the specified key gesture.
|
|||
/// </summary>
|
|||
/// <param name="root">The window to attach DevTools to.</param>
|
|||
/// <param name="gesture">The key gesture to open DevTools.</param>
|
|||
public static void AttachDevTools(this TopLevel root, KeyGesture gesture) |
|||
{ |
|||
DevTools.Attach(root, gesture); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Diagnostics.Views; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Diagnostics |
|||
{ |
|||
public static class DevTools |
|||
{ |
|||
private static readonly Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>(); |
|||
|
|||
public static IDisposable Attach(TopLevel root, KeyGesture gesture) |
|||
{ |
|||
void PreviewKeyDown(object sender, KeyEventArgs e) |
|||
{ |
|||
if (gesture.Matches(e)) |
|||
{ |
|||
Open(root); |
|||
} |
|||
} |
|||
|
|||
return root.AddDisposableHandler( |
|||
InputElement.KeyDownEvent, |
|||
PreviewKeyDown, |
|||
RoutingStrategies.Tunnel); |
|||
} |
|||
|
|||
public static IDisposable Open(TopLevel root) |
|||
{ |
|||
if (s_open.TryGetValue(root, out var window)) |
|||
{ |
|||
window.Activate(); |
|||
} |
|||
else |
|||
{ |
|||
window = new MainWindow |
|||
{ |
|||
Width = 1024, |
|||
Height = 512, |
|||
Root = root, |
|||
}; |
|||
|
|||
window.Closed += DevToolsClosed; |
|||
s_open.Add(root, window); |
|||
window.Show(); |
|||
} |
|||
|
|||
return Disposable.Create(() => window?.Close()); |
|||
} |
|||
|
|||
private static void DevToolsClosed(object sender, EventArgs e) |
|||
{ |
|||
var window = (MainWindow)sender; |
|||
s_open.Remove(window.Root); |
|||
window.Closed -= DevToolsClosed; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
#pragma warning disable IDE1006 // Naming Styles
|
|||
|
|||
using Avalonia.Diagnostics.ViewModels; |
|||
|
|||
namespace Avalonia.Diagnostics.Models |
|||
{ |
|||
public class ConsoleContext |
|||
{ |
|||
private readonly ConsoleViewModel _owner; |
|||
|
|||
internal ConsoleContext(ConsoleViewModel owner) => _owner = owner; |
|||
|
|||
public readonly string help = @"Welcome to Avalonia DevTools. Here you can execute arbitrary C# code using Roslyn scripting.
|
|||
|
|||
The following variables are available: |
|||
|
|||
e: The control currently selected in the logical or visual tree view |
|||
root: The root of the visual tree |
|||
|
|||
The following commands are available: |
|||
|
|||
clear(): Clear the output history |
|||
";
|
|||
|
|||
public dynamic e { get; internal set; } |
|||
public dynamic root { get; internal set; } |
|||
|
|||
internal static object NoOutput { get; } = new object(); |
|||
|
|||
public object clear() |
|||
{ |
|||
_owner.History.Clear(); |
|||
return NoOutput; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Diagnostics.Models |
|||
{ |
|||
internal class ConsoleHistoryItem |
|||
{ |
|||
public ConsoleHistoryItem(string input, object output) |
|||
{ |
|||
Input = input; |
|||
Output = output; |
|||
Foreground = output is Exception ? Brushes.Red : Brushes.Green; |
|||
} |
|||
|
|||
public string Input { get; } |
|||
public object Output { get; } |
|||
public IBrush Foreground { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
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 string _priority; |
|||
private string _group; |
|||
|
|||
public AvaloniaPropertyViewModel(AvaloniaObject o, AvaloniaProperty property) |
|||
{ |
|||
_target = o; |
|||
Property = property; |
|||
|
|||
Name = property.IsAttached ? |
|||
$"[{property.OwnerType.Name}.{property.Name}]" : |
|||
property.Name; |
|||
|
|||
if (property.IsDirect) |
|||
{ |
|||
_group = "Properties"; |
|||
Priority = "Direct"; |
|||
} |
|||
|
|||
Update(); |
|||
} |
|||
|
|||
public AvaloniaProperty Property { get; } |
|||
public override object Key => Property; |
|||
public override string Name { get; } |
|||
public bool IsAttached => Property.IsAttached; |
|||
|
|||
public string Priority |
|||
{ |
|||
get => _priority; |
|||
private set => RaiseAndSetIfChanged(ref _priority, value); |
|||
} |
|||
|
|||
public override string Type => _type; |
|||
|
|||
public override string Value |
|||
{ |
|||
get => ConvertToString(_value); |
|||
set |
|||
{ |
|||
try |
|||
{ |
|||
var convertedValue = ConvertFromString(value, Property.PropertyType); |
|||
_target.SetValue(Property, convertedValue); |
|||
} |
|||
catch { } |
|||
} |
|||
} |
|||
|
|||
public override string Group |
|||
{ |
|||
get => _group; |
|||
} |
|||
|
|||
public override void Update() |
|||
{ |
|||
if (Property.IsDirect) |
|||
{ |
|||
RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); |
|||
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); |
|||
} |
|||
else |
|||
{ |
|||
var val = _target.GetDiagnostic(Property); |
|||
|
|||
RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); |
|||
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); |
|||
|
|||
if (val != null) |
|||
{ |
|||
SetGroup(IsAttached ? "Attached Properties" : "Properties"); |
|||
Priority = val.Priority.ToString(); |
|||
} |
|||
else |
|||
{ |
|||
SetGroup(Priority = "Unset"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void SetGroup(string group) |
|||
{ |
|||
RaiseAndSetIfChanged(ref _group, group, nameof(Group)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System.ComponentModel; |
|||
using System.Reflection; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class ClrPropertyViewModel : PropertyViewModel |
|||
{ |
|||
private readonly object _target; |
|||
private string _type; |
|||
private object _value; |
|||
|
|||
public ClrPropertyViewModel(object o, PropertyInfo property) |
|||
{ |
|||
_target = o; |
|||
Property = property; |
|||
|
|||
if (!property.DeclaringType.IsInterface) |
|||
{ |
|||
Name = property.Name; |
|||
} |
|||
else |
|||
{ |
|||
Name = property.DeclaringType.Name + '.' + property.Name; |
|||
} |
|||
|
|||
Update(); |
|||
} |
|||
|
|||
public PropertyInfo Property { get; } |
|||
public override object Key => Name; |
|||
public override string Name { get; } |
|||
public override string Group => "CLR Properties"; |
|||
|
|||
public override string Type => _type; |
|||
|
|||
public override string Value |
|||
{ |
|||
get => ConvertToString(_value); |
|||
set |
|||
{ |
|||
try |
|||
{ |
|||
var convertedValue = ConvertFromString(value, Property.PropertyType); |
|||
Property.SetValue(_target, convertedValue); |
|||
} |
|||
catch { } |
|||
} |
|||
} |
|||
|
|||
public override void Update() |
|||
{ |
|||
var val = Property.GetValue(_target); |
|||
RaiseAndSetIfChanged(ref _value, val, nameof(Value)); |
|||
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
using System; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Diagnostics.Models; |
|||
using Microsoft.CodeAnalysis.CSharp.Scripting; |
|||
using Microsoft.CodeAnalysis.Scripting; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class ConsoleViewModel : ViewModelBase |
|||
{ |
|||
private readonly ConsoleContext _context; |
|||
private readonly Action<ConsoleContext> _updateContext; |
|||
private int _historyIndex = -1; |
|||
private string _input; |
|||
private bool _isVisible; |
|||
private ScriptState<object> _state; |
|||
|
|||
public ConsoleViewModel(Action<ConsoleContext> updateContext) |
|||
{ |
|||
_context = new ConsoleContext(this); |
|||
_updateContext = updateContext; |
|||
} |
|||
|
|||
public string Input |
|||
{ |
|||
get => _input; |
|||
set => RaiseAndSetIfChanged(ref _input, value); |
|||
} |
|||
|
|||
public bool IsVisible |
|||
{ |
|||
get => _isVisible; |
|||
set => RaiseAndSetIfChanged(ref _isVisible, value); |
|||
} |
|||
|
|||
public AvaloniaList<ConsoleHistoryItem> History { get; } = new AvaloniaList<ConsoleHistoryItem>(); |
|||
|
|||
public async Task Execute() |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(Input)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var options = ScriptOptions.Default |
|||
.AddReferences(Assembly.GetAssembly(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo))); |
|||
|
|||
_updateContext(_context); |
|||
|
|||
if (_state == null) |
|||
{ |
|||
_state = await CSharpScript.RunAsync(Input, options: options, globals: _context); |
|||
} |
|||
else |
|||
{ |
|||
_state = await _state.ContinueWithAsync(Input); |
|||
} |
|||
|
|||
if (_state.ReturnValue != ConsoleContext.NoOutput) |
|||
{ |
|||
History.Add(new ConsoleHistoryItem(Input, _state.ReturnValue ?? "(null)")); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
History.Add(new ConsoleHistoryItem(Input, ex)); |
|||
} |
|||
|
|||
Input = string.Empty; |
|||
_historyIndex = -1; |
|||
} |
|||
|
|||
public void HistoryUp() |
|||
{ |
|||
if (History.Count > 0) |
|||
{ |
|||
if (_historyIndex == -1) |
|||
{ |
|||
_historyIndex = History.Count - 1; |
|||
} |
|||
else if (_historyIndex > 0) |
|||
{ |
|||
--_historyIndex; |
|||
} |
|||
|
|||
Input = History[_historyIndex].Input; |
|||
} |
|||
} |
|||
|
|||
public void HistoryDown() |
|||
{ |
|||
if (History.Count > 0 && _historyIndex >= 0) |
|||
{ |
|||
if (_historyIndex == History.Count - 1) |
|||
{ |
|||
_historyIndex = -1; |
|||
Input = string.Empty; |
|||
} |
|||
else |
|||
{ |
|||
Input = History[++_historyIndex].Input; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void ToggleVisibility() => IsVisible = !IsVisible; |
|||
} |
|||
} |
|||
@ -0,0 +1,179 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using Avalonia.Collections; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class ControlDetailsViewModel : ViewModelBase, IDisposable |
|||
{ |
|||
private readonly IVisual _control; |
|||
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex; |
|||
private AvaloniaPropertyViewModel _selectedProperty; |
|||
private string _propertyFilter; |
|||
|
|||
public ControlDetailsViewModel(IVisual control, string propertyFilter) |
|||
{ |
|||
_control = control; |
|||
|
|||
var properties = GetAvaloniaProperties(control) |
|||
.Concat(GetClrProperties(control)) |
|||
.OrderBy(x => x, PropertyComparer.Instance) |
|||
.ThenBy(x => x.Name) |
|||
.ToList(); |
|||
|
|||
_propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToList()); |
|||
_propertyFilter = propertyFilter; |
|||
|
|||
var view = new DataGridCollectionView(properties); |
|||
view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group))); |
|||
view.Filter = FilterProperty; |
|||
PropertiesView = view; |
|||
|
|||
if (control is INotifyPropertyChanged inpc) |
|||
{ |
|||
inpc.PropertyChanged += ControlPropertyChanged; |
|||
} |
|||
|
|||
if (control is AvaloniaObject ao) |
|||
{ |
|||
ao.PropertyChanged += ControlPropertyChanged; |
|||
} |
|||
} |
|||
|
|||
public DataGridCollectionView PropertiesView { get; } |
|||
|
|||
public string PropertyFilter |
|||
{ |
|||
get => _propertyFilter; |
|||
set |
|||
{ |
|||
if (RaiseAndSetIfChanged(ref _propertyFilter, value)) |
|||
{ |
|||
PropertiesView.Refresh(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public AvaloniaPropertyViewModel SelectedProperty |
|||
{ |
|||
get => _selectedProperty; |
|||
set => RaiseAndSetIfChanged(ref _selectedProperty, value); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_control is INotifyPropertyChanged inpc) |
|||
{ |
|||
inpc.PropertyChanged -= ControlPropertyChanged; |
|||
} |
|||
|
|||
if (_control is AvaloniaObject ao) |
|||
{ |
|||
ao.PropertyChanged -= ControlPropertyChanged; |
|||
} |
|||
} |
|||
|
|||
private IEnumerable<PropertyViewModel> GetAvaloniaProperties(object o) |
|||
{ |
|||
if (o is AvaloniaObject ao) |
|||
{ |
|||
return AvaloniaPropertyRegistry.Instance.GetRegistered(ao) |
|||
.Concat(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(ao.GetType())) |
|||
.Select(x => new AvaloniaPropertyViewModel(ao, x)); |
|||
} |
|||
else |
|||
{ |
|||
return Enumerable.Empty<AvaloniaPropertyViewModel>(); |
|||
} |
|||
} |
|||
|
|||
private IEnumerable<PropertyViewModel> GetClrProperties(object o) |
|||
{ |
|||
foreach (var p in GetClrProperties(o, o.GetType())) |
|||
{ |
|||
yield return p; |
|||
} |
|||
|
|||
foreach (var i in o.GetType().GetInterfaces()) |
|||
{ |
|||
foreach (var p in GetClrProperties(o, i)) |
|||
{ |
|||
yield return p; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private IEnumerable<PropertyViewModel> GetClrProperties(object o, Type t) |
|||
{ |
|||
return t.GetProperties() |
|||
.Where(x => x.GetIndexParameters().Length == 0) |
|||
.Select(x => new ClrPropertyViewModel(o, x)); |
|||
} |
|||
|
|||
private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (_propertyIndex.TryGetValue(e.Property, out var properties)) |
|||
{ |
|||
foreach (var property in properties) |
|||
{ |
|||
property.Update(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void ControlPropertyChanged(object sender, PropertyChangedEventArgs e) |
|||
{ |
|||
if (_propertyIndex.TryGetValue(e.PropertyName, out var properties)) |
|||
{ |
|||
foreach (var property in properties) |
|||
{ |
|||
property.Update(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private bool FilterProperty(object arg) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(PropertyFilter) && arg is PropertyViewModel property) |
|||
{ |
|||
return property.Name.IndexOf(PropertyFilter, StringComparison.OrdinalIgnoreCase) != -1; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private class PropertyComparer : IComparer<PropertyViewModel> |
|||
{ |
|||
public static PropertyComparer Instance { get; } = new PropertyComparer(); |
|||
|
|||
public int Compare(PropertyViewModel x, PropertyViewModel y) |
|||
{ |
|||
var groupX = GroupIndex(x.Group); |
|||
var groupY = GroupIndex(y.Group); |
|||
|
|||
if (groupX != groupY) |
|||
{ |
|||
return groupX - groupY; |
|||
} |
|||
else |
|||
{ |
|||
return string.CompareOrdinal(x.Name, y.Name); |
|||
} |
|||
} |
|||
|
|||
private int GroupIndex(string group) |
|||
{ |
|||
switch (group) |
|||
{ |
|||
case "Properties": return 0; |
|||
case "Attached Properties": return 1; |
|||
case "CLR Properties": return 2; |
|||
default: return 3; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Diagnostics.Models; |
|||
using Avalonia.Input; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class MainViewModel : ViewModelBase, IDisposable |
|||
{ |
|||
private readonly IControl _root; |
|||
private readonly TreePageViewModel _logicalTree; |
|||
private readonly TreePageViewModel _visualTree; |
|||
private readonly EventsPageViewModel _events; |
|||
private ViewModelBase _content; |
|||
private int _selectedTab; |
|||
private string _focusedControl; |
|||
private string _pointerOverElement; |
|||
|
|||
public MainViewModel(IControl root) |
|||
{ |
|||
_root = root; |
|||
_logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root)); |
|||
_visualTree = new TreePageViewModel(VisualTreeNode.Create(root)); |
|||
_events = new EventsPageViewModel(root); |
|||
|
|||
UpdateFocusedControl(); |
|||
KeyboardDevice.Instance.PropertyChanged += (s, e) => |
|||
{ |
|||
if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) |
|||
{ |
|||
UpdateFocusedControl(); |
|||
} |
|||
}; |
|||
|
|||
SelectedTab = 0; |
|||
root.GetObservable(TopLevel.PointerOverElementProperty) |
|||
.Subscribe(x => PointerOverElement = x?.GetType().Name); |
|||
Console = new ConsoleViewModel(UpdateConsoleContext); |
|||
} |
|||
|
|||
public ConsoleViewModel Console { get; } |
|||
|
|||
public ViewModelBase Content |
|||
{ |
|||
get { return _content; } |
|||
private set |
|||
{ |
|||
if (_content is TreePageViewModel oldTree && |
|||
value is TreePageViewModel newTree && |
|||
oldTree?.SelectedNode?.Visual is IControl control) |
|||
{ |
|||
newTree.SelectControl(control); |
|||
} |
|||
|
|||
RaiseAndSetIfChanged(ref _content, value); |
|||
} |
|||
} |
|||
|
|||
public int SelectedTab |
|||
{ |
|||
get { return _selectedTab; } |
|||
set |
|||
{ |
|||
_selectedTab = value; |
|||
|
|||
switch (value) |
|||
{ |
|||
case 0: |
|||
Content = _logicalTree; |
|||
break; |
|||
case 1: |
|||
Content = _visualTree; |
|||
break; |
|||
case 2: |
|||
Content = _events; |
|||
break; |
|||
} |
|||
|
|||
RaisePropertyChanged(); |
|||
} |
|||
} |
|||
|
|||
public string FocusedControl |
|||
{ |
|||
get { return _focusedControl; } |
|||
private set { RaiseAndSetIfChanged(ref _focusedControl, value); } |
|||
} |
|||
|
|||
public string PointerOverElement |
|||
{ |
|||
get { return _pointerOverElement; } |
|||
private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } |
|||
} |
|||
|
|||
private void UpdateConsoleContext(ConsoleContext context) |
|||
{ |
|||
context.root = _root; |
|||
|
|||
if (Content is TreePageViewModel tree) |
|||
{ |
|||
context.e = tree.SelectedNode?.Visual; |
|||
} |
|||
} |
|||
|
|||
public void SelectControl(IControl control) |
|||
{ |
|||
var tree = Content as TreePageViewModel; |
|||
|
|||
if (tree != null) |
|||
{ |
|||
tree.SelectControl(control); |
|||
} |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_logicalTree.Dispose(); |
|||
_visualTree.Dispose(); |
|||
} |
|||
|
|||
private void UpdateFocusedControl() |
|||
{ |
|||
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
using System.Reflection; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal abstract class PropertyViewModel : ViewModelBase |
|||
{ |
|||
private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; |
|||
private static readonly Type[] StringParameter = new[] { typeof(string) }; |
|||
private static readonly Type[] StringIFormatProviderParameters = new[] { typeof(string), typeof(IFormatProvider) }; |
|||
|
|||
public abstract object Key { get; } |
|||
public abstract string Name { get; } |
|||
public abstract string Group { get; } |
|||
public abstract string Type { get; } |
|||
public abstract string Value { get; set; } |
|||
public abstract void Update(); |
|||
|
|||
protected static string ConvertToString(object value) |
|||
{ |
|||
if (value is null) |
|||
{ |
|||
return "(null)"; |
|||
} |
|||
|
|||
var converter = TypeDescriptor.GetConverter(value); |
|||
return converter?.ConvertToString(value) ?? value.ToString(); |
|||
} |
|||
|
|||
protected static object ConvertFromString(string s, Type targetType) |
|||
{ |
|||
var converter = TypeDescriptor.GetConverter(targetType); |
|||
|
|||
if (converter != null && converter.CanConvertFrom(typeof(string))) |
|||
{ |
|||
return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s); |
|||
} |
|||
else |
|||
{ |
|||
var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); |
|||
|
|||
if (method != null) |
|||
{ |
|||
return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture }); |
|||
} |
|||
|
|||
method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null); |
|||
|
|||
if (method != null) |
|||
{ |
|||
return method.Invoke(null, new object[] { s }); |
|||
} |
|||
} |
|||
|
|||
throw new InvalidCastException("Unable to convert value."); |
|||
} |
|||
} |
|||
} |
|||
@ -1,34 +1,42 @@ |
|||
// 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.ComponentModel; |
|||
using System.Runtime.CompilerServices; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class ViewModelBase : INotifyPropertyChanged |
|||
{ |
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
private PropertyChangedEventHandler _propertyChanged; |
|||
private List<string> events = new List<string>(); |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged |
|||
{ |
|||
add { _propertyChanged += value; events.Add("added"); } |
|||
remove { _propertyChanged -= value; events.Add("removed"); } |
|||
} |
|||
|
|||
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) |
|||
{ |
|||
} |
|||
|
|||
[NotifyPropertyChangedInvocator] |
|||
protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null) |
|||
{ |
|||
if (!EqualityComparer<T>.Default.Equals(field, value)) |
|||
{ |
|||
field = value; |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
RaisePropertyChanged(propertyName); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
[NotifyPropertyChangedInvocator] |
|||
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) |
|||
{ |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
var e = new PropertyChangedEventArgs(propertyName); |
|||
OnPropertyChanged(e); |
|||
_propertyChanged?.Invoke(this, e); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue