fix: fix IPV4Box key handling in DataGrid. Add a SampleBox project for extra testing. Upgrade to released version.

This commit is contained in:
rabbitism
2024-07-27 22:00:39 +08:00
parent f073ce76c2
commit 57b1dd808a
17 changed files with 295 additions and 17 deletions

View File

@@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution
demo\Directory.Build.props = demo\Directory.Build.props
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox", "demo\Sandbox\Sandbox.csproj", "{1E94BAFD-867E-425F-8E12-7F416D523C94}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +70,10 @@ Global
{F99B3D07-4560-4B05-892C-0FF2757FEF2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F99B3D07-4560-4B05-892C-0FF2757FEF2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F99B3D07-4560-4B05-892C-0FF2757FEF2E}.Release|Any CPU.Build.0 = Release|Any CPU
{1E94BAFD-867E-425F-8E12-7F416D523C94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E94BAFD-867E-425F-8E12-7F416D523C94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E94BAFD-867E-425F-8E12-7F416D523C94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E94BAFD-867E-425F-8E12-7F416D523C94}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{407A91FD-A88B-459B-8DCE-8C6AA98279FE} = {A41BAF0D-DA61-4A63-889A-084BAD36FD66}
@@ -76,5 +82,6 @@ Global
{94C2BBD9-8B57-4AE9-AAFD-7D4335B15A8E} = {A41BAF0D-DA61-4A63-889A-084BAD36FD66}
{D1942476-8473-4608-BB9F-5AC01083BBDA} = {A41BAF0D-DA61-4A63-889A-084BAD36FD66}
{F99B3D07-4560-4B05-892C-0FF2757FEF2E} = {A41BAF0D-DA61-4A63-889A-084BAD36FD66}
{1E94BAFD-867E-425F-8E12-7F416D523C94} = {A41BAF0D-DA61-4A63-889A-084BAD36FD66}
EndGlobalSection
EndGlobal

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<AvaloniaVersion>11.1.0-rc2</AvaloniaVersion>
<AvaloniaVersion>11.1.1</AvaloniaVersion>
</PropertyGroup>
</Project>

19
demo/Sandbox/App.axaml Normal file
View File

@@ -0,0 +1,19 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sandbox.App"
xmlns:local="using:Sandbox"
xmlns:semi="https://irihi.tech/semi"
xmlns:u-semi="https://irihi.tech/ursa/themes/semi"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<semi:SemiTheme Locale="zh-cn" />
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
<u-semi:SemiTheme Locale="zh-cn" />
</Application.Styles>
</Application>

33
demo/Sandbox/App.axaml.cs Normal file
View File

@@ -0,0 +1,33 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Sandbox.ViewModels;
using Sandbox.Views;
namespace Sandbox;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// 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);
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

20
demo/Sandbox/Program.cs Normal file
View File

@@ -0,0 +1,20 @@
using Avalonia;
using System;
namespace Sandbox;
sealed 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.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}

View File

@@ -0,0 +1,30 @@
<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>
<Folder Include="Models\"/>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
<PackageReference Include="Semi.Avalonia" Version="11.1.0" />
<PackageReference Include="Semi.Avalonia.DataGrid" Version="11.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Ursa.Themes.Semi\Ursa.Themes.Semi.csproj" />
<ProjectReference Include="..\..\src\Ursa\Ursa.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,32 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Sandbox.ViewModels;
namespace Sandbox;
public class ViewLocator : IDataTemplate
{
public Control? Build(object? data)
{
if (data is null)
return null;
var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null)
{
var control = (Control)Activator.CreateInstance(type)!;
control.DataContext = data;
return control;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object? data)
{
return data is ViewModelBase;
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.ObjectModel;
using System.Net;
namespace Sandbox.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<DataGridItem> Items { get; set; }
public MainWindowViewModel()
{
Items = new ObservableCollection<DataGridItem>()
{
new DataGridItem() { Name = "John Doe", Age = 42 },
new DataGridItem() { Name = "Jane Doe", Age = 39 },
new DataGridItem() { Name = "Sammy Doe", Age = 13 },
new DataGridItem() { Name = "Barry Doe", Age = 7 },
new DataGridItem() { Name = "Molly Doe", Age = 5 },
};
}
}
public class DataGridItem
{
public string? Name { get; set; }
public int Age { get; set; }
public IPAddress? Address { get; set; }
}

View File

@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Sandbox.ViewModels;
public class ViewModelBase : ObservableObject
{
}

View File

@@ -0,0 +1,45 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Sandbox.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="https://irihi.tech/ursa"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Sandbox.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="Sandbox">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid>
<DataGrid ItemsSource="{Binding Items}" IsReadOnly="False" HorizontalAlignment="Left">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTemplateColumn Width="2*" Header="Address" MaxWidth="300" MinWidth="300">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Address}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<u:IPv4Box
HorizontalAlignment="Stretch"
IPAddress="{Binding Address}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Sandbox.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

18
demo/Sandbox/app.manifest Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="Sandbox.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@@ -19,7 +19,7 @@
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
<PackageReference Include="Semi.Avalonia" Version="11.1.0-rc2.1" />
<PackageReference Include="Semi.Avalonia" Version="11.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<AvaloniaVersion>11.1.0-rc2</AvaloniaVersion>
<AvaloniaVersion>11.1.1</AvaloniaVersion>
</PropertyGroup>
</Project>

View File

@@ -42,6 +42,7 @@
<Setter Property="u:IPv4Box.SelectionForegroundBrush" Value="{DynamicResource IPv4BoxSelectionForeground}" />
<Setter Property="u:IPv4Box.CaretBrush" Value="{DynamicResource IPv4BoxCaretBrush}" />
<Setter Property="u:IPv4Box.ContextFlyout" Value="{DynamicResource IPv4BoxMenuFlyout}" />
<Setter Property="FocusAdorner" Value="{x:Null}"></Setter>
<Setter Property="u:IPv4Box.Template">
<ControlTemplate TargetType="u:IPv4Box">
<Border

View File

@@ -133,7 +133,11 @@ public class IPv4Box: TemplatedControl
protected override void OnKeyDown(KeyEventArgs e)
{
if (_currentActivePresenter is null) return;
if (_currentActivePresenter is null)
{
_currentActivePresenter = _presenters[0];
}
var keymap = TopLevel.GetTopLevel(this)?.PlatformSettings?.HotkeyConfiguration;
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
if (e.Key is Key.Enter or Key.Return)
@@ -141,25 +145,36 @@ public class IPv4Box: TemplatedControl
ParseBytes(ShowLeadingZero);
SetIPAddressInternal();
base.OnKeyDown(e);
e.Handled = true;
return;
}
if (keymap is not null && Match(keymap.SelectAll))
{
_currentActivePresenter.SelectionStart = 0;
_currentActivePresenter.SelectionEnd = _currentActivePresenter.Text?.Length ?? 0;
e.Handled = true;
return;
}
else if (keymap is not null && Match(keymap.Copy))
if (keymap is not null && Match(keymap.Copy))
{
Copy();
e.Handled = true;
return;
}
else if (keymap is not null && Match(keymap.Paste))
if (keymap is not null && Match(keymap.Paste))
{
Paste();
e.Handled = true;
return;
}
else if (keymap is not null && Match(keymap.Cut))
if (keymap is not null && Match(keymap.Cut))
{
Cut();
e.Handled = true;
return;
}
if (e.Key == Key.Tab)
{
@@ -173,23 +188,30 @@ public class IPv4Box: TemplatedControl
MoveToNextPresenter(_currentActivePresenter, true);
_currentActivePresenter?.ShowCaret();
e.Handled = true;
return;
}
else if (e.Key == Key.Back)
if (e.Key == Key.Back)
{
DeleteImplementation(_currentActivePresenter);
e.Handled = true;
return;
}
else if (e.Key == Key.Right )
if (e.Key == Key.Right )
{
OnPressRightKey();
e.Handled = true;
return;
}
else if (e.Key == Key.Left)
if (e.Key == Key.Left)
{
OnPressLeftKey();
e.Handled = true;
return;
}
else
{
base.OnKeyDown(e);
}
base.OnKeyDown(e);
}
protected override void OnTextInput(TextInputEventArgs e)
@@ -201,9 +223,11 @@ public class IPv4Box: TemplatedControl
{
_currentActivePresenter?.HideCaret();
_currentActivePresenter.ClearSelection();
MoveToNextPresenter(_currentActivePresenter, false);
_currentActivePresenter?.ShowCaret();
_currentActivePresenter.MoveCaretToStart();
if (MoveToNextPresenter(_currentActivePresenter, false))
{
_currentActivePresenter?.ShowCaret();
_currentActivePresenter.MoveCaretToStart();
}
e.Handled = false;
return;
}
@@ -274,6 +298,7 @@ public class IPv4Box: TemplatedControl
}
}
base.OnPointerPressed(e);
e.Handled = true;
}
protected override void OnLostFocus(RoutedEventArgs e)