Extracted the headerview out of the MainWindow.axaml

This commit is contained in:
Frederik Jacobsen 2025-10-18 21:08:39 +02:00
parent 81f6660da5
commit ac01b81b35
8 changed files with 142 additions and 47 deletions

View File

@ -1,6 +1,7 @@
<Application xmlns="https://github.com/avaloniaui" <Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fluent="using:Avalonia.Themes.Fluent" xmlns:fluent="using:Avalonia.Themes.Fluent"
xmlns:conv="clr-namespace:XSDVisualiser.Desktop.Converters"
x:Class="XSDVisualiser.Desktop.App" x:Class="XSDVisualiser.Desktop.App"
RequestedThemeVariant="Light"> RequestedThemeVariant="Light">
<Application.Resources> <Application.Resources>
@ -13,6 +14,12 @@
<SolidColorBrush x:Key="MutedTextBrush" Color="#64748B"/> <SolidColorBrush x:Key="MutedTextBrush" Color="#64748B"/>
<SolidColorBrush x:Key="SeparatorBrush" Color="#E2E8F0"/> <SolidColorBrush x:Key="SeparatorBrush" Color="#E2E8F0"/>
<SolidColorBrush x:Key="BadgeBackgroundBrush" Color="#F1F5F9"/> <SolidColorBrush x:Key="BadgeBackgroundBrush" Color="#F1F5F9"/>
<!-- Global converters -->
<conv:CardinalityToLabelConverter x:Key="CardinalityToLabel"/>
<conv:OptionalToBorderBrushConverter x:Key="OptionalToBrush"/>
<conv:OptionalToThicknessConverter x:Key="OptionalToThickness"/>
<conv:CollectionHasItemsConverter x:Key="HasItems"/>
</Application.Resources> </Application.Resources>
<Application.Styles> <Application.Styles>

View File

@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:XSDVisualiser.Models;assembly=XSDVisualiser.Core" xmlns:m="clr-namespace:XSDVisualiser.Models;assembly=XSDVisualiser.Core"
xmlns:conv="clr-namespace:XSDVisualiser.Desktop.Converters" xmlns:conv="clr-namespace:XSDVisualiser.Desktop.Converters"
xmlns:views="clr-namespace:XSDVisualiser.Desktop.Views"
x:Class="XSDVisualiser.Desktop.MainWindow" x:Class="XSDVisualiser.Desktop.MainWindow"
x:CompileBindings="False" x:CompileBindings="False"
Title="XSD Visualiser" Width="1200" Height="800"> Title="XSD Visualiser" Width="1200" Height="800">
@ -14,26 +15,7 @@
<Grid RowDefinitions="Auto,*" Margin="12"> <Grid RowDefinitions="Auto,*" Margin="12">
<!-- Header --> <!-- Header -->
<DockPanel LastChildFill="False"> <views:HeaderView x:Name="Header" />
<TextBlock Text="XSD Visualiser" FontSize="20" FontWeight="SemiBold" Margin="0,0,12,0" DockPanel.Dock="Left"/>
<!-- Searchable dropdown for root elements -->
<AutoCompleteBox Width="320" Margin="0,0,12,0" Watermark="Search and choose a root element"
ItemsSource="{Binding RootElements}"
FilterMode="Contains"
MinimumPrefixLength="0">
<AutoCompleteBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}"/>
</DataTemplate>
</AutoCompleteBox.ItemTemplate>
<AutoCompleteBox.SelectedItem>
<Binding Path="SelectedRootElement" Mode="TwoWay"/>
</AutoCompleteBox.SelectedItem>
</AutoCompleteBox>
<Button Content="Open XSD and parse" x:Name="OpenBtn" Width="220"/>
</DockPanel>
<!-- Body: left tree | splitter | right details --> <!-- Body: left tree | splitter | right details -->
<Grid Grid.Row="1" ColumnDefinitions="2*,Auto,*" RowDefinitions="*"> <Grid Grid.Row="1" ColumnDefinitions="2*,Auto,*" RowDefinitions="*">

View File

@ -6,6 +6,7 @@ using Avalonia.Threading;
using XSDVisualiser.Core; using XSDVisualiser.Core;
using XSDVisualiser.Models; using XSDVisualiser.Models;
using XSDVisualiser.Desktop.ViewModels; using XSDVisualiser.Desktop.ViewModels;
using XSDVisualiser.Desktop.Views;
namespace XSDVisualiser.Desktop namespace XSDVisualiser.Desktop
{ {
@ -16,7 +17,10 @@ namespace XSDVisualiser.Desktop
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
_openBtn = this.FindControl<Button>("OpenBtn"); var header = this.FindControl<HeaderView>("Header");
var btn = header?.FindControl<Button>("OpenBtn");
if (btn == null) return;
_openBtn = btn;
_openBtn.Click += OpenBtn_Click; _openBtn.Click += OpenBtn_Click;
} }
@ -29,7 +33,7 @@ namespace XSDVisualiser.Desktop
}; };
ofd.Filters!.Add(new FileDialogFilter { Name = "XSD schema", Extensions = { "xsd" } }); ofd.Filters!.Add(new FileDialogFilter { Name = "XSD schema", Extensions = { "xsd" } });
var files = await ofd.ShowAsync(this); var files = await ofd.ShowAsync(this);
if (files != null && files.Length > 0) if (files is { Length: > 0 })
{ {
await ParseAndShowAsync(files[0]); await ParseAndShowAsync(files[0]);
} }

View File

@ -7,15 +7,11 @@ using XSDVisualiser.Models;
namespace XSDVisualiser.Desktop.ViewModels namespace XSDVisualiser.Desktop.ViewModels
{ {
public class MainWindowViewModel : INotifyPropertyChanged public class MainWindowViewModel(SchemaModel model) : INotifyPropertyChanged
{ {
private SchemaModel _model; private SchemaModel _model = model ?? throw new ArgumentNullException(nameof(model));
private SchemaNode? _selectedRootElement; private SchemaNode? _selectedRootElement;
private SchemaNode? _selectedNode;
public MainWindowViewModel(SchemaModel model)
{
_model = model ?? throw new ArgumentNullException(nameof(model));
}
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
@ -29,15 +25,13 @@ namespace XSDVisualiser.Desktop.ViewModels
get => _model; get => _model;
set set
{ {
if (!ReferenceEquals(_model, value)) if (ReferenceEquals(_model, value)) return;
{
_model = value ?? throw new ArgumentNullException(nameof(value)); _model = value ?? throw new ArgumentNullException(nameof(value));
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(RootElements)); OnPropertyChanged(nameof(RootElements));
OnPropertyChanged(nameof(VisibleRootElements)); OnPropertyChanged(nameof(VisibleRootElements));
} }
} }
}
public IEnumerable<SchemaNode> RootElements => _model?.RootElements ?? Enumerable.Empty<SchemaNode>(); public IEnumerable<SchemaNode> RootElements => _model?.RootElements ?? Enumerable.Empty<SchemaNode>();
@ -46,23 +40,24 @@ namespace XSDVisualiser.Desktop.ViewModels
get => _selectedRootElement; get => _selectedRootElement;
set set
{ {
if (!EqualityComparer<SchemaNode?>.Default.Equals(_selectedRootElement, value)) if (EqualityComparer<SchemaNode?>.Default.Equals(_selectedRootElement, value)) return;
{
_selectedRootElement = value; _selectedRootElement = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(VisibleRootElements)); OnPropertyChanged(nameof(VisibleRootElements));
} }
} }
public SchemaNode? SelectedNode
{
get => _selectedNode;
set
{
if (EqualityComparer<SchemaNode?>.Default.Equals(_selectedNode, value)) return;
_selectedNode = value;
OnPropertyChanged();
}
} }
public IEnumerable<SchemaNode> VisibleRootElements public IEnumerable<SchemaNode> VisibleRootElements => SelectedRootElement != null ? [SelectedRootElement] : RootElements;
{
get
{
if (SelectedRootElement != null)
return new[] { SelectedRootElement };
return RootElements;
}
}
} }
} }

View File

@ -0,0 +1,24 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="XSDVisualiser.Desktop.Views.HeaderView"
x:CompileBindings="False">
<DockPanel LastChildFill="False">
<TextBlock Text="XSD Visualiser" FontSize="20" FontWeight="SemiBold" Margin="0,0,12,0" DockPanel.Dock="Left"/>
<AutoCompleteBox Width="320" Margin="0,0,12,0" Watermark="Search and choose a root element"
ItemsSource="{Binding RootElements}"
FilterMode="Contains"
MinimumPrefixLength="0">
<AutoCompleteBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}"/>
</DataTemplate>
</AutoCompleteBox.ItemTemplate>
<AutoCompleteBox.SelectedItem>
<Binding Path="SelectedRootElement" Mode="TwoWay"/>
</AutoCompleteBox.SelectedItem>
</AutoCompleteBox>
<Button Content="Open XSD and parse" x:Name="OpenBtn" Width="220"/>
</DockPanel>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace XSDVisualiser.Desktop.Views;
public partial class HeaderView : UserControl
{
public HeaderView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,60 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:XSDVisualiser.Models;assembly=XSDVisualiser.Core"
x:Class="XSDVisualiser.Desktop.Views.LeftTreeView"
x:CompileBindings="False">
<Border Background="{DynamicResource PanelBackgroundBrush}"
BorderBrush="{DynamicResource PanelBorderBrush}"
BorderThickness="1" CornerRadius="6" Margin="0,8,8,0">
<ScrollViewer>
<TreeView x:Name="SchemaTree" ItemsSource="{Binding VisibleRootElements}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="{x:Type m:SchemaNode}" ItemsSource="{Binding Children}">
<StackPanel>
<!-- Connector line from parent to this node (hidden for roots where ContentModel is null) -->
<Grid Margin="0,8,8,2" IsVisible="{Binding ContentModel, Converter={x:Static ObjectConverters.IsNotNull}}"
ColumnDefinitions="16,*">
<!-- leading small elbow -->
<Rectangle Grid.Column="0" Width="16" Height="2" Fill="{DynamicResource SeparatorBrush}" VerticalAlignment="Center"/>
<Grid Grid.Column="1">
<Rectangle Height="2" Fill="{DynamicResource SeparatorBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Cardinality, Converter={StaticResource CardinalityToLabel}}"
Background="{DynamicResource BadgeBackgroundBrush}" Padding="6,0"
Foreground="{DynamicResource SubtleTextBrush}"
HorizontalAlignment="Center"/>
</Grid>
</Grid>
<!-- Node content -->
<Border CornerRadius="4"
BorderBrush="{Binding Cardinality, Converter={StaticResource OptionalToBrush}}"
BorderThickness="{Binding Cardinality, Converter={StaticResource OptionalToThickness}}"
Padding="8" Margin="0,0,8,6" Background="{DynamicResource PanelBackgroundBrush}">
<StackPanel Orientation="Vertical" Spacing="2">
<!-- Name on its own line, prominent -->
<TextBlock Text="{Binding Name}" FontWeight="SemiBold"/>
<!-- Path/Namespace on its own line -->
<TextBlock Text="{Binding Namespace, StringFormat=Namespace: {0}}"
Foreground="{DynamicResource SubtleTextBrush}"/>
<!-- Type info on its own line (TypeName with fallback to BuiltInType) -->
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="Type:" FontWeight="Medium"/>
<TextBlock Text="{Binding TypeName}"/>
<TextBlock Text="{Binding BuiltInType, StringFormat=({0})}"
Foreground="{DynamicResource MutedTextBrush}"/>
</StackPanel>
<!-- Optional: show content model as a subtle hint -->
<TextBlock Text="{Binding ContentModel, StringFormat=Model: {0}}"
Foreground="{DynamicResource SubtleTextBrush}" FontWeight="SemiBold"/>
</StackPanel>
</Border>
</StackPanel>
</TreeDataTemplate>
</TreeView.DataTemplates>
</TreeView>
</ScrollViewer>
</Border>
</UserControl>

View File

@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace XSDVisualiser.Desktop.Views
{
public partial class LeftTreeView : UserControl
{
public LeftTreeView()
{
InitializeComponent();
}
}
}