Migrate desktop app to Avalonia and publish cross-platform releases
Build and Release / Publish Windows, Linux and macOS (push) Successful in 3m7s
Build and Release / Publish Windows, Linux and macOS (push) Successful in 3m7s
This commit is contained in:
1 parent
864feaf317
commit
f61e298871
17 files changed
+1051
-40
No files matched your search
@@ -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="例如: 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="例如: 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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user