Skip to content

Commit

Permalink
Avalonia ship groups (#463)
Browse files Browse the repository at this point in the history
  • Loading branch information
myangelkamikaze authored May 18, 2024
1 parent 826455a commit 7c76b95
Show file tree
Hide file tree
Showing 59 changed files with 3,578 additions and 726 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.0-beta1" />
</ItemGroup>
</Project>
96 changes: 96 additions & 0 deletions Avalonia.Win32.Interoperability/AvaloniaHwndHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Markup;
using Avalonia.Controls.Embedding;
using AvaloniaControl = Avalonia.Controls.Control;

namespace Avalonia.Win32.Interoperability;

/// <summary>
/// An element that allows you to host an Avalonia control on a WPF page.
/// </summary>
/// <see href="https://github.com/maxkatz6/AvaloniaHwndHostSample"/>
[ContentProperty("Content")]
public class WpfAvaloniaHost : HwndHost
{
private EmbeddableControlRoot? _root;
private AvaloniaControl? _content;

/// <summary>
/// Initializes a new instance of the <see cref="WpfAvaloniaHost"/> class.
/// </summary>
public WpfAvaloniaHost()
{
DataContextChanged += AvaloniaHwndHost_DataContextChanged;
}

private void AvaloniaHwndHost_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (Content != null)
{
// todo: need to figure out what the best way is for setting the DataContext
// Content.DataContext = e.NewValue;
}
}

/// <summary>
/// Gets or sets the Avalonia control hosted by the <see cref="WpfAvaloniaHost"/> element.
/// </summary>
public AvaloniaControl? Content
{
get => _content;
set
{
if (_content != value)
{
_content = value;

if (_root is not null)
{
_root.Content = value;
}

if (value != null)
{
// todo: need to figure out what the best way is for setting the DataContext
// value.DataContext = DataContext;
}
}
}
}

/// <inheritdoc />
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
_root = new EmbeddableControlRoot
{
Content = _content,
};

_root.Prepare();
_root.StartRendering();

IntPtr handle = _root.TryGetPlatformHandle()?.Handle
?? throw new InvalidOperationException("WpfAvaloniaHost is unable to create EmbeddableControlRoot.");

if (PresentationSource.FromVisual(this) is HwndSource source)
{
const int GWL_STYLE = -16;
const int WS_CHILD = 0x40000000;

_ = UnmanagedMethods.SetWindowLong(handle, GWL_STYLE, WS_CHILD);

_ = UnmanagedMethods.SetParent(handle, source.Handle);
}

return new HandleRef(_root, handle);
}

/// <inheritdoc />
protected override void DestroyWindowCore(HandleRef hwnd)
{
_root?.Dispose();
}
}
14 changes: 14 additions & 0 deletions Avalonia.Win32.Interoperability/UnmanagedMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;

namespace Avalonia.Win32.Interoperability;

internal static partial class UnmanagedMethods
{
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool SetParent(IntPtr hWnd, IntPtr hWndNewParent);

[LibraryImport("user32.dll", EntryPoint = "SetWindowLongA", SetLastError = true)]
public static partial uint SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
}
6 changes: 6 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<AvaloniaVersion>11.0.2</AvaloniaVersion>
</PropertyGroup>
</Project>
11 changes: 11 additions & 0 deletions ElectronicObserver.Avalonia.Samples/App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Application
x:Class="ElectronicObserver.Avalonia.Samples.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
RequestedThemeVariant="Default"
>
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
</Application.Styles>
</Application>
42 changes: 42 additions & 0 deletions ElectronicObserver.Avalonia.Samples/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using ElectronicObserver.Avalonia.Samples.ViewModels;
using ElectronicObserver.Avalonia.Samples.Views;
using HotAvalonia;

namespace ElectronicObserver.Avalonia.Samples;

public partial class App : Application
{
public override void Initialize()
{
this.EnableHotReload();
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
BindingPlugins.DataValidators.RemoveAt(0);

if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainViewModel(),
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = new MainViewModel(),
};
}

base.OnFrameworkInitializationCompleted();
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>

<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>

<ItemGroup>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>$(DefineConstants);ENABLE_XAML_HOT_RELOAD</DefineConstants>
</PropertyGroup>

<ItemGroup>
<PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="Avalonia.Markup.Xaml.Loader" Version="$(AvaloniaVersion)" />
<PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="HotAvalonia" Version="1.1.1" />
<PackageReference Include="HotAvalonia.Extensions" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ElectronicObserver.Avalonia\ElectronicObserver.Avalonia.csproj" />
</ItemGroup>
</Project>
21 changes: 21 additions & 0 deletions ElectronicObserver.Avalonia.Samples/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using Avalonia;

namespace ElectronicObserver.Avalonia.Samples;

public static class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);

// Avalonia configuration, don't remove; also used by visual designer.
private static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();

}
78 changes: 78 additions & 0 deletions ElectronicObserver.Avalonia.Samples/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ElectronicObserver.Avalonia.Behaviors.PersistentColumns;
using ElectronicObserver.Avalonia.ShipGroup;

namespace ElectronicObserver.Avalonia.Samples.ViewModels;

public class Test
{
public required string Name { get; set; }
public required string Description { get; set; }
}

public partial class MainViewModel : ViewModelBase
{
private List<Test> Data { get; } =
[
new() { Name = "Test1", Description = "TestDescription1" },
new() { Name = "Test2", Description = "TestDescription2" },
new() { Name = "Test3", Description = "TestDescription3" },
new() { Name = "Test4", Description = "TestDescription4" },
];

public ObservableCollection<ShipGroupItem> Groups { get; set; } =
[
new()
{
Name = "Group1",
Columns =
[
new() { Name = "", IsVisible = true, DisplayIndex = 0, },
new() { Name = "", IsVisible = true, DisplayIndex = 1, },
],
SortDescriptions = [],
},
new()
{
Name = "Group2",
Columns =
[
new() { Name = "", IsVisible = false },
new() { Name = "", IsVisible = false },
],
SortDescriptions = [],
},
];

[ObservableProperty] private ShipGroupItem? _selectedGroup;
[ObservableProperty] private DataGridCollectionView _collectionView = new(new List<Test>());
[ObservableProperty] private ObservableCollection<ColumnModel> _columnProperties = [];

[RelayCommand]
private void SelectGroup(ShipGroupItem group)
{
SelectedGroup = group;
}

partial void OnSelectedGroupChanging(ShipGroupItem? oldValue, ShipGroupItem? newValue)
{
if (oldValue is null) return;

oldValue.SortDescriptions = CollectionView.SortDescriptions;
}

partial void OnSelectedGroupChanged(ShipGroupItem? value)
{
if (value is null) return;

ColumnProperties = value.Columns;
CollectionView = new(Data);

CollectionView.SortDescriptions.Clear();
CollectionView.SortDescriptions.AddRange(value.SortDescriptions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;

namespace ElectronicObserver.Avalonia.Samples.ViewModels;

public class ViewModelBase : ObservableObject
{
}
Loading

0 comments on commit 7c76b95

Please sign in to comment.