Added MemoryStreamDebugVisualizer
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace MemoryStreamVisualizer;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.VisualStudio.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Extension entrypoint for the VisualStudio.Extensibility extension.
|
||||
/// </summary>
|
||||
[VisualStudioContribution]
|
||||
internal class ExtensionEntrypoint : Extension
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override ExtensionConfiguration ExtensionConfiguration => new()
|
||||
{
|
||||
Metadata = new(
|
||||
id: "MemoryStreamVisualizer.97a0a2fb-f163-4fa3-91f0-48a2d4ad9f57",
|
||||
version: this.ExtensionAssemblyVersion,
|
||||
publisherName: "Microsoft",
|
||||
displayName: "MemoryStream Debugger Visualizer",
|
||||
description: "A debugger visualizer for MemoryStream"),
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
base.InitializeServices(serviceCollection);
|
||||
|
||||
// You can configure dependency injection here by adding services to the serviceCollection.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace MemoryStreamVisualizer;
|
||||
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel class representing a row of binary data.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class HexEditorRow
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HexEditorRow"/> class.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of this row.</param>
|
||||
/// <param name="data">The bytes making up this row of data, in their hex representation.</param>
|
||||
/// <param name="ascii">The bytes making up this row of data, in their Ascii representation.</param>
|
||||
public HexEditorRow(int index, string data, string ascii)
|
||||
{
|
||||
this.Index = index;
|
||||
this.Data = data;
|
||||
this.Ascii = ascii;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of this row.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public int Index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of this row in hex format.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string HexIndex => $"{this.Index:X8}h";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bytes making up this row of data, in their hex representation.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bytes making up this row of data, in their Ascii representation.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Ascii { get; }
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace MemoryStreamVisualizer;
|
||||
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Windows;
|
||||
using Microsoft.VisualStudio.Extensibility.UI;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel class representing the data contained by a <see cref="MemoryStream"/>.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class MemoryStreamData : NotifyPropertyChangedObject
|
||||
{
|
||||
private long length;
|
||||
private long position;
|
||||
private Visibility loadingVisibility = Visibility.Visible;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the length of the <see cref="MemoryStream"/>.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public long Length
|
||||
{
|
||||
get => this.length;
|
||||
set
|
||||
{
|
||||
if (this.SetProperty(ref this.length, value))
|
||||
{
|
||||
this.RaiseNotifyPropertyChangedEvent(nameof(this.HexLength));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the <see cref="MemoryStream"/> in hex format.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string HexLength => $"{this.length:X}h";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current position of the <see cref="MemoryStream"/>.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public long Position
|
||||
{
|
||||
get => this.position;
|
||||
set
|
||||
{
|
||||
if (this.SetProperty(ref this.position, value))
|
||||
{
|
||||
this.RaiseNotifyPropertyChangedEvent(nameof(this.HexPosition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current position of the <see cref="MemoryStream"/> in hex format.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string HexPosition => $"{this.position:X}h";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data currently contained in the <see cref="MemoryStream"/>.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public ObservableList<HexEditorRow> Data { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the loading bar should be visible.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Visibility LoadingVisibility
|
||||
{
|
||||
get => this.loadingVisibility;
|
||||
set => this.SetProperty(ref this.loadingVisibility, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace MemoryStreamVisualizer;
|
||||
|
||||
using MemoryStreamObjectSource;
|
||||
using Microsoft.VisualStudio.Extensibility;
|
||||
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
|
||||
using Microsoft.VisualStudio.RpcContracts.RemoteUI;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Debugger visualizer provider for <see cref="MemoryStream"/>.
|
||||
/// </summary>
|
||||
[VisualStudioContribution]
|
||||
internal class MemoryStreamDebuggerVisualizerProvider : DebuggerVisualizerProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
|
||||
[
|
||||
new VisualizerTargetType("%MemoryStreamVisualizer.MemoryStreamDebuggerVisualizerProvider.Name%", typeof(MemoryStream))
|
||||
])
|
||||
{
|
||||
VisualizerObjectSourceType = new VisualizerObjectSourceType(typeof(MemoryStreamVisualizerObjectSource)),
|
||||
Style = VisualizerStyle.ToolWindow,
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<IRemoteUserControl>(new MemoryStreamVisualizerUserControl(visualizerTarget));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>12</LangVersion>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Remove="C:\Users\heilm\source\repos\DebugVisualizer\stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.14.40608" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.14.40608" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="MemoryStreamVisualizerUserControl.xaml" />
|
||||
<EmbeddedResource Include="MemoryStreamVisualizerUserControl.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MemoryStreamObjectSource\MemoryStreamObjectSource.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\bin\MemoryStreamObjectSource\$(Configuration)\netstandard2.0\MemoryStreamObjectSource.dll" Link="netstandard2.0\MemoryStreamObjectSource.dll" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
|
||||
using MemoryStreamObjectSource;
|
||||
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
|
||||
using Microsoft.VisualStudio.Extensibility.UI;
|
||||
using Microsoft.VisualStudio.RpcContracts.DebuggerVisualizers;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MemoryStreamVisualizer;
|
||||
/// <summary>
|
||||
/// Remote UI user control for the MemoryStreamVisualizer.
|
||||
/// </summary>
|
||||
internal class MemoryStreamVisualizerUserControl : RemoteUserControl
|
||||
{
|
||||
private readonly VisualizerTarget visualizerTarget;
|
||||
private readonly MemoryStreamData dataContext;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryStreamVisualizerUserControl"/> class.
|
||||
/// </summary>
|
||||
/// <param name="visualizerTarget">The visualizer target to be used to retrieve data from.</param>
|
||||
public MemoryStreamVisualizerUserControl(VisualizerTarget visualizerTarget)
|
||||
: base(new MemoryStreamData())
|
||||
{
|
||||
visualizerTarget.Changed += this.VisualizerTargetStateChangedAsync;
|
||||
|
||||
this.dataContext = (MemoryStreamData)this.DataContext!;
|
||||
this.visualizerTarget = visualizerTarget;
|
||||
}
|
||||
|
||||
private Task VisualizerTargetStateChangedAsync(VisualizerTargetStateNotification args)
|
||||
{
|
||||
if (args == VisualizerTargetStateNotification.Available || args == VisualizerTargetStateNotification.ValueUpdated)
|
||||
{
|
||||
this.dataContext.Data.Clear();
|
||||
this.dataContext.Position = 0;
|
||||
this.dataContext.Length = 0;
|
||||
this.dataContext.LoadingVisibility = Visibility.Visible;
|
||||
|
||||
return this.RetrieveDataAsync();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RetrieveDataAsync()
|
||||
{
|
||||
ReadOnlySequence<byte> data;
|
||||
do
|
||||
{
|
||||
using MemoryStream memoryStream = new(sizeof(int));
|
||||
using BinaryWriter binaryWriter = new(memoryStream);
|
||||
int index = this.dataContext.Data.Count * MemoryStreamVisualizerObjectSource.RowLength;
|
||||
if (index >= 1024 * 1024)
|
||||
{
|
||||
break; // Let's not retrieve more than 1MB of data
|
||||
}
|
||||
|
||||
binaryWriter.Write(index);
|
||||
binaryWriter.Flush();
|
||||
try
|
||||
{
|
||||
data = (await this.visualizerTarget.ObjectSource.RequestDataAsync(new ReadOnlySequence<byte>(memoryStream.ToArray()), CancellationToken.None)).Value;
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
// I can get an exception if the debug session is unpaused, so I need to handle it gracefully
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (data.Length > 0 && this.Read(data));
|
||||
|
||||
this.dataContext.LoadingVisibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private bool Read(ReadOnlySequence<byte> data)
|
||||
{
|
||||
int byteInRowCount = 0;
|
||||
StringBuilder binaryText = new();
|
||||
StringBuilder asciiText = new();
|
||||
|
||||
SequenceReader<byte> reader = new(data);
|
||||
if (!reader.TryReadLittleEndian(out long position) || !reader.TryReadLittleEndian(out long length))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.dataContext.Position = position;
|
||||
this.dataContext.Length = length;
|
||||
|
||||
if (reader.UnreadSpan.Length == 0)
|
||||
{
|
||||
return false; // We always receive data unless we are at the end of the MemoryStream
|
||||
}
|
||||
|
||||
List<HexEditorRow> rows = new(MemoryStreamVisualizerObjectSource.RowsCountPerRequest);
|
||||
byte[] tmp = new byte[1];
|
||||
while (reader.TryRead(out byte b))
|
||||
{
|
||||
byteInRowCount++;
|
||||
if (byteInRowCount > 1)
|
||||
{
|
||||
binaryText.Append(' ');
|
||||
}
|
||||
|
||||
binaryText.Append(b.ToString("X2", CultureInfo.InvariantCulture));
|
||||
tmp[0] = b;
|
||||
asciiText.Append(char.IsControl((char)b) || b == 0xAD ? '•' : Encoding.Latin1.GetChars(tmp)[0]);
|
||||
|
||||
if (byteInRowCount == MemoryStreamVisualizerObjectSource.RowLength)
|
||||
{
|
||||
CompleteRow();
|
||||
}
|
||||
}
|
||||
|
||||
if (byteInRowCount > 0)
|
||||
{
|
||||
CompleteRow();
|
||||
this.dataContext.Data.AddRange(rows);
|
||||
return false; // We only receive partial rows at the end of the MemoryStream
|
||||
}
|
||||
|
||||
this.dataContext.Data.AddRange(rows);
|
||||
return true;
|
||||
|
||||
void CompleteRow()
|
||||
{
|
||||
rows.Add(new HexEditorRow(
|
||||
index: (this.dataContext.Data.Count + rows.Count) * MemoryStreamVisualizerObjectSource.RowLength,
|
||||
data: binaryText.ToString(),
|
||||
ascii: asciiText.ToString()));
|
||||
|
||||
byteInRowCount = 0;
|
||||
binaryText.Clear();
|
||||
asciiText.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vs="http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml"
|
||||
xmlns:styles="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
|
||||
xmlns:colors="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ProgressBar IsIndeterminate="True" VerticalAlignment="Top" Visibility="{Binding LoadingVisibility}" Style="{StaticResource {x:Static styles:VsResourceKeys.ProgressBarStyleKey}}" />
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="1">
|
||||
<Label VerticalAlignment="Center" Style="{StaticResource {x:Static styles:VsResourceKeys.ThemedDialogLabelStyleKey}}">Position: </Label>
|
||||
<TextBox IsReadOnly="True" Width="75" VerticalAlignment="Center">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.TextBoxStyleKey}}">
|
||||
<Setter Property="Text" Value="{Binding Position, Mode=OneWay}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsChecked, ElementName=HexCheck, FallbackValue=True}" Value="True">
|
||||
<Setter Property="Text" Value="{Binding HexPosition, Mode=OneWay}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
<Label Margin="2,0,0,0" VerticalAlignment="Center" Style="{StaticResource {x:Static styles:VsResourceKeys.ThemedDialogLabelStyleKey}}">Length:</Label>
|
||||
<TextBox IsReadOnly="True" Width="100" VerticalAlignment="Center">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.TextBoxStyleKey}}">
|
||||
<Setter Property="Text" Value="{Binding Length, Mode=OneWay}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsChecked, ElementName=HexCheck, FallbackValue=True}" Value="True">
|
||||
<Setter Property="Text" Value="{Binding HexLength, Mode=OneWay}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
<CheckBox x:Name="HexCheck" Margin="5,0,0,0" VerticalAlignment="Center" Style="{StaticResource {x:Static styles:VsResourceKeys.CheckBoxStyleKey}}">Hex</CheckBox>
|
||||
</StackPanel>
|
||||
<DataGrid ItemsSource="{Binding Data}"
|
||||
EnableRowVirtualization="True"
|
||||
AutoGenerateColumns="False"
|
||||
SelectionMode="Extended"
|
||||
SelectionUnit="Cell"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserResizeRows="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
Grid.Row="2">
|
||||
<DataGrid.Resources>
|
||||
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type DataGrid}, ResourceId=DataGridSelectAllButtonStyle}" TargetType="{x:Type Button}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Rectangle Fill="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Style>
|
||||
<Style TargetType="{x:Type DataGrid}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridLineBrushKey}}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
</DataGrid.Style>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static colors:ThemedDialogColors.WindowPanelTextBrushKey}}"/>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.RowHeaderStyle>
|
||||
<Style TargetType="{x:Type DataGridRowHeader}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingTextBrushKey}}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridLineBrushKey}}"/>
|
||||
<Setter Property="Content" Value="{Binding Index, Mode=OneWay}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsChecked, ElementName=HexCheck, FallbackValue=True}" Value="True">
|
||||
<Setter Property="Content" Value="{Binding HexIndex, Mode=OneWay}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowHeaderStyle>
|
||||
<DataGrid.ColumnHeaderStyle>
|
||||
<Style TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingTextBrushKey}}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridLineBrushKey}}"/>
|
||||
<Setter Property="Padding" Value="2,0,0,0"/>
|
||||
</Style>
|
||||
</DataGrid.ColumnHeaderStyle>
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="{x:Type DataGridCell}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.ActionButtonStrokeHoverBrushKey}}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static colors:ThemedDialogColors.ActionButtonTextActiveBrushKey}}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static colors:ThemedDialogColors.ActionButtonStrokeHoverBrushKey}}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Hex value" Binding="{Binding Data}" FontFamily="Consolas" />
|
||||
<DataGridTextColumn Header="Ascii" Binding="{Binding Ascii}" FontFamily="Consolas" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
Reference in New Issue
Block a user