Migrate desktop app to Avalonia and publish cross-platform releases
Build and Release / Publish Windows, Linux and macOS (push) Successful in 3m7s

This commit is contained in:
biss committed 2026-07-19 08:51:41 +08:00
1 parent 864feaf317
commit f61e298871
17 files changed
+1051 -40

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="kcsj.Views.DataInputWindow"
Title="数据输入"
Width="900"
Height="700"
MinWidth="720"
MinHeight="580"
WindowStartupLocation="CenterOwner">
<DockPanel Margin="24">
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="*,Auto" Margin="0,18,0,0">
<TextBlock x:Name="InputStatusText"
VerticalAlignment="Center"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"
Text="请选择文件或切换到手动输入。" />
<Button Grid.Column="1" MinWidth="92" Content="关闭" Click="Cancel_Click" />
</Grid>
<TabControl x:Name="DataTabs">
<TabItem Header="从文件导入">
<Grid Margin="18" RowDefinitions="Auto,Auto,Auto,*" RowSpacing="14">
<Grid ColumnDefinitions="120,*,Auto" ColumnSpacing="10">
<TextBlock VerticalAlignment="Center" Text="已知点文件" />
<TextBox x:Name="KnownPathTextBox" Grid.Column="1" IsReadOnly="True" PlaceholderText="请选择 TXT 文件" />
<Button Grid.Column="2" Content="浏览…" Click="BrowseKnownFile_Click" />
</Grid>
<Grid Grid.Row="1" ColumnDefinitions="120,*,Auto" ColumnSpacing="10">
<TextBlock VerticalAlignment="Center" Text="观测数据文件" />
<TextBox x:Name="ObservationPathTextBox" Grid.Column="1" IsReadOnly="True" PlaceholderText="请选择 TXT 文件" />
<Button Grid.Column="2" Content="浏览…" Click="BrowseObservationFile_Click" />
</Grid>
<StackPanel Grid.Row="2" Orientation="Horizontal" Spacing="10">
<Button MinWidth="110" Content="预览数据" Click="PreviewFiles_Click" />
<Button MinWidth="110" Content="导入数据" Click="ImportFiles_Click" />
</StackPanel>
<Grid Grid.Row="3" ColumnDefinitions="*,*" ColumnSpacing="14">
<Grid RowDefinitions="Auto,*">
<TextBlock Margin="0,0,0,8" FontWeight="SemiBold" Text="已知点预览" />
<TextBox x:Name="KnownPreviewTextBox"
Grid.Row="1"
IsReadOnly="True"
AcceptsReturn="True"
TextWrapping="NoWrap"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
FontFamily="Consolas,Menlo,monospace" />
</Grid>
<Grid Grid.Column="1" RowDefinitions="Auto,*">
<TextBlock Margin="0,0,0,8" FontWeight="SemiBold" Text="观测数据预览" />
<TextBox x:Name="ObservationPreviewTextBox"
Grid.Row="1"
IsReadOnly="True"
AcceptsReturn="True"
TextWrapping="NoWrap"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
FontFamily="Consolas,Menlo,monospace" />
</Grid>
</Grid>
</Grid>
</TabItem>
<TabItem Header="手动输入">
<Grid Margin="18" RowDefinitions="Auto,*,Auto" RowSpacing="14">
<Border Padding="12"
Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
CornerRadius="6">
<TextBlock TextWrapping="Wrap"
Text="每行一条数据,可使用空格、Tab 或逗号分隔;空行以及以 # 或 // 开头的行会被忽略。" />
</Border>
<Grid Grid.Row="1" ColumnDefinitions="*,*" ColumnSpacing="14">
<Grid RowDefinitions="Auto,*">
<TextBlock Margin="0,0,0,8" FontWeight="SemiBold" Text="已知点(点名 高程)" />
<TextBox x:Name="ManualKnownTextBox"
Grid.Row="1"
AcceptsReturn="True"
TextWrapping="NoWrap"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
PlaceholderText="例如:&#10;BM1 100.0000"
FontFamily="Consolas,Menlo,monospace" />
</Grid>
<Grid Grid.Column="1" RowDefinitions="Auto,*">
<TextBlock Margin="0,0,0,8" FontWeight="SemiBold" Text="观测数据(起点 终点 高差 距离)" />
<TextBox x:Name="ManualObservationTextBox"
Grid.Row="1"
AcceptsReturn="True"
TextWrapping="NoWrap"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
PlaceholderText="例如:&#10;BM1 P1 1.2345 2.5"
FontFamily="Consolas,Menlo,monospace" />
</Grid>
</Grid>
<StackPanel Grid.Row="2" Orientation="Horizontal" Spacing="10">
<Button MinWidth="110" Content="检查并预览" Click="PreviewManual_Click" />
<Button MinWidth="110" Content="保存数据" Click="SaveManual_Click" />
</StackPanel>
</Grid>
</TabItem>
</TabControl>
</DockPanel>
</Window>
+182
View File
@@ -0,0 +1,182 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage;
using kcsj.Models;
using kcsj.Services;
namespace kcsj.Views;
public partial class DataInputWindow : Window
{
private static readonly FilePickerFileType TextFileType = new("文本文件")
{
Patterns = ["*.txt"],
MimeTypes = ["text/plain"]
};
private TabControl TabsControl => this.FindControl<TabControl>("DataTabs")!;
private TextBlock StatusControl => this.FindControl<TextBlock>("InputStatusText")!;
private TextBox KnownPathControl => this.FindControl<TextBox>("KnownPathTextBox")!;
private TextBox ObservationPathControl => this.FindControl<TextBox>("ObservationPathTextBox")!;
private TextBox KnownPreviewControl => this.FindControl<TextBox>("KnownPreviewTextBox")!;
private TextBox ObservationPreviewControl => this.FindControl<TextBox>("ObservationPreviewTextBox")!;
private TextBox ManualKnownControl => this.FindControl<TextBox>("ManualKnownTextBox")!;
private TextBox ManualObservationControl => this.FindControl<TextBox>("ManualObservationTextBox")!;
private List<KnownPoint> _fileKnownPoints = [];
private List<Observation> _fileObservations = [];
public DataInputWindow()
: this(0)
{
}
public DataInputWindow(int selectedTab)
{
AvaloniaXamlLoader.Load(this);
TabsControl.SelectedIndex = Math.Clamp(selectedTab, 0, 1);
}
private async void BrowseKnownFile_Click(object? sender, RoutedEventArgs e)
{
string? path = await PickTextFileAsync("选择已知点高程文件");
if (path is not null)
{
KnownPathControl.Text = path;
_fileKnownPoints = [];
}
}
private async void BrowseObservationFile_Click(object? sender, RoutedEventArgs e)
{
string? path = await PickTextFileAsync("选择观测数据文件");
if (path is not null)
{
ObservationPathControl.Text = path;
_fileObservations = [];
}
}
private async Task<string?> PickTextFileAsync(string title)
{
IReadOnlyList<IStorageFile> files = await StorageProvider.OpenFilePickerAsync(
new FilePickerOpenOptions
{
Title = title,
AllowMultiple = false,
FileTypeFilter = [TextFileType, FilePickerFileTypes.All]
});
return files.FirstOrDefault()?.Path.LocalPath;
}
private async void PreviewFiles_Click(object? sender, RoutedEventArgs e)
{
try
{
LoadFileData();
ShowFilePreview();
StatusControl.Text = $"预览完成:{_fileKnownPoints.Count} 个已知点,{_fileObservations.Count} 条观测。";
LogService.AddLog("文件数据预览成功。");
}
catch (Exception exception)
{
ClearFilePreview();
LogService.AddLog("数据预览失败:" + exception.Message);
await DialogWindow.ShowMessageAsync(this, "数据预览失败", exception.Message);
}
}
private async void ImportFiles_Click(object? sender, RoutedEventArgs e)
{
try
{
LoadFileData();
DataStore.SetData(_fileKnownPoints, _fileObservations, "文件导入");
LogService.AddLog("文件数据导入成功。");
Close(true);
}
catch (Exception exception)
{
LogService.AddLog("导入失败:" + exception.Message);
await DialogWindow.ShowMessageAsync(this, "导入失败", exception.Message);
}
}
private async void PreviewManual_Click(object? sender, RoutedEventArgs e)
{
try
{
(List<KnownPoint> knownPoints, List<Observation> observations) = ParseManualData();
StatusControl.Text = $"手动输入检查通过:{knownPoints.Count} 个已知点,{observations.Count} 条观测。";
await DialogWindow.ShowMessageAsync(
this,
"手动输入预览",
InputParser.BuildKnownPointsPreview(knownPoints) + "\n" +
InputParser.BuildObservationsPreview(observations));
}
catch (Exception exception)
{
LogService.AddLog("手动输入检查失败:" + exception.Message);
await DialogWindow.ShowMessageAsync(this, "输入错误", exception.Message);
}
}
private async void SaveManual_Click(object? sender, RoutedEventArgs e)
{
try
{
(List<KnownPoint> knownPoints, List<Observation> observations) = ParseManualData();
DataStore.SetData(knownPoints, observations, "手动输入");
LogService.AddLog("手动输入数据已保存。");
Close(true);
}
catch (Exception exception)
{
LogService.AddLog("手动输入数据时发生错误:" + exception.Message);
await DialogWindow.ShowMessageAsync(this, "输入错误", exception.Message);
}
}
private void LoadFileData()
{
string knownPath = KnownPathControl.Text?.Trim() ?? string.Empty;
string observationPath = ObservationPathControl.Text?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(knownPath))
{
throw new InvalidOperationException("请先选择已知点高程文件。");
}
if (string.IsNullOrWhiteSpace(observationPath))
{
throw new InvalidOperationException("请先选择观测数据文件。");
}
_fileKnownPoints = InputParser.ReadKnownPointsFile(knownPath);
_fileObservations = InputParser.ReadObservationsFile(observationPath);
}
private void ShowFilePreview()
{
KnownPreviewControl.Text = InputParser.BuildKnownPointsPreview(_fileKnownPoints);
ObservationPreviewControl.Text = InputParser.BuildObservationsPreview(_fileObservations);
}
private void ClearFilePreview()
{
_fileKnownPoints = [];
_fileObservations = [];
KnownPreviewControl.Text = string.Empty;
ObservationPreviewControl.Text = string.Empty;
}
private (List<KnownPoint> KnownPoints, List<Observation> Observations) ParseManualData() =>
(
InputParser.ParseKnownPointsText(ManualKnownControl.Text ?? string.Empty),
InputParser.ParseObservationsText(ManualObservationControl.Text ?? string.Empty)
);
private void Cancel_Click(object? sender, RoutedEventArgs e) => Close(false);
}
+73
View File
@@ -0,0 +1,73 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
namespace kcsj.Views;
public sealed class DialogWindow : Window
{
private DialogWindow(string title, string message, bool showCancel)
{
Title = title;
CanResize = false;
SizeToContent = SizeToContent.WidthAndHeight;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
MinWidth = 360;
MaxWidth = 560;
TextBlock messageText = new()
{
Text = message,
TextWrapping = TextWrapping.Wrap,
MaxWidth = 500,
FontSize = 15
};
Button confirmButton = new()
{
Content = "确定",
MinWidth = 88,
HorizontalContentAlignment = HorizontalAlignment.Center
};
confirmButton.Click += (_, _) => Close(true);
StackPanel buttons = new()
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Right,
Spacing = 10
};
if (showCancel)
{
Button cancelButton = new()
{
Content = "取消",
MinWidth = 88,
HorizontalContentAlignment = HorizontalAlignment.Center
};
cancelButton.Click += (_, _) => Close(false);
buttons.Children.Add(cancelButton);
}
buttons.Children.Add(confirmButton);
StackPanel content = new() { Spacing = 24 };
content.Children.Add(messageText);
content.Children.Add(buttons);
Content = new Border
{
Padding = new Thickness(24),
Background = Brushes.Transparent,
Child = content
};
}
public static Task ShowMessageAsync(Window owner, string title, string message) =>
new DialogWindow(title, message, false).ShowDialog<bool>(owner);
public static Task<bool> ConfirmAsync(Window owner, string title, string message) =>
new DialogWindow(title, message, true).ShowDialog<bool>(owner);
}
Loaded 3 of 17 files, more files were not shown because too many files have changed in this diff. Show more