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:
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="kcsj.Views.MainWindow"
|
||||
Title="水准网平差程序"
|
||||
Width="980"
|
||||
Height="680"
|
||||
MinWidth="780"
|
||||
MinHeight="560"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<DockPanel>
|
||||
<Menu DockPanel.Dock="Top">
|
||||
<MenuItem Header="数据管理">
|
||||
<MenuItem Header="从文件导入" Click="OpenFileInput_Click" />
|
||||
<MenuItem Header="手动输入" Click="OpenManualInput_Click" />
|
||||
<Separator />
|
||||
<MenuItem Header="删除当前数据" Click="ClearData_Click" />
|
||||
<MenuItem Header="数据检查" Click="CheckData_Click" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="开始平差" Click="RunAdjustment_Click" />
|
||||
<MenuItem Header="查看结果" Click="ShowResult_Click" />
|
||||
<MenuItem Header="输出报告" Click="ExportReport_Click" />
|
||||
<MenuItem Header="关于" Click="About_Click" />
|
||||
</Menu>
|
||||
|
||||
<Border DockPanel.Dock="Bottom"
|
||||
Padding="12,7"
|
||||
Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<TextBlock x:Name="DataStatusText" Text="当前未加载数据" />
|
||||
</Border>
|
||||
|
||||
<Grid Margin="28" ColumnDefinitions="220,24,*" RowDefinitions="Auto,*">
|
||||
<StackPanel Grid.ColumnSpan="3" Margin="0,0,0,24">
|
||||
<TextBlock Text="水准网平差程序" FontSize="28" FontWeight="SemiBold" />
|
||||
<TextBlock Margin="0,6,0,0"
|
||||
Text="基于 Avalonia 的跨平台水准测量间接平差工具"
|
||||
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="12">
|
||||
<Button Height="52" Content="数据输入" Click="OpenDataInput_Click" />
|
||||
<Button Height="52" Content="数据检查" Click="CheckData_Click" />
|
||||
<Button Height="52" Content="开始平差" Click="RunAdjustment_Click" />
|
||||
<Button Height="52" Content="查看结果" Click="ShowResult_Click" />
|
||||
<Button Height="52" Content="输出报告" Click="ExportReport_Click" />
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Padding="20"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||
CornerRadius="8">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,0,0,12">
|
||||
<TextBlock Text="运行日志" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" Content="清除日志" Click="ClearLog_Click" />
|
||||
</Grid>
|
||||
<TextBox x:Name="LogTextBox"
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
AcceptsReturn="True"
|
||||
TextWrapping="Wrap"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
FontFamily="Consolas,Menlo,monospace" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,221 @@
|
||||
using System.Text;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using kcsj.Models;
|
||||
using kcsj.Services;
|
||||
|
||||
namespace kcsj.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private TextBox LogControl => this.FindControl<TextBox>("LogTextBox")!;
|
||||
private TextBlock DataStatusControl => this.FindControl<TextBlock>("DataStatusText")!;
|
||||
private LeastSquaresResult? _lastResult;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
LogService.OnLog += ShowLog;
|
||||
Closed += (_, _) => LogService.OnLog -= ShowLog;
|
||||
LogService.AddLog("程序启动成功。当前界面框架:Avalonia。");
|
||||
UpdateDataStatus();
|
||||
}
|
||||
|
||||
private async void OpenDataInput_Click(object? sender, RoutedEventArgs e) =>
|
||||
await OpenDataInputAsync(0);
|
||||
|
||||
private async void OpenFileInput_Click(object? sender, RoutedEventArgs e) =>
|
||||
await OpenDataInputAsync(0);
|
||||
|
||||
private async void OpenManualInput_Click(object? sender, RoutedEventArgs e) =>
|
||||
await OpenDataInputAsync(1);
|
||||
|
||||
private async Task OpenDataInputAsync(int selectedTab)
|
||||
{
|
||||
DataInputWindow window = new(selectedTab);
|
||||
bool saved = await window.ShowDialog<bool>(this);
|
||||
if (saved)
|
||||
{
|
||||
_lastResult = null;
|
||||
UpdateDataStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private async void CheckData_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
DataCheckResult result = DataCheck.Check(DataStore.KnownPoints, DataStore.Observations);
|
||||
LogService.AddLog(result.ToLogText());
|
||||
|
||||
string message = result.IsValid
|
||||
? "数据检查通过,可以进行平差计算。"
|
||||
: "数据检查未通过,请查看日志信息。";
|
||||
await DialogWindow.ShowMessageAsync(this, "数据检查", message);
|
||||
}
|
||||
|
||||
private async void RunAdjustment_Click(object? sender, RoutedEventArgs e) =>
|
||||
await RunAdjustmentAsync();
|
||||
|
||||
private async Task<bool> RunAdjustmentAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!DataStore.HasData)
|
||||
{
|
||||
await DialogWindow.ShowMessageAsync(this, "开始平差", "请先导入或输入已知点和观测数据。");
|
||||
return false;
|
||||
}
|
||||
|
||||
_lastResult = LeastSquaresAdjustmentService.Adjust(
|
||||
DataStore.KnownPoints,
|
||||
DataStore.Observations);
|
||||
|
||||
foreach ((string pointName, double elevation) in _lastResult.UnknownElevations)
|
||||
{
|
||||
double error = _lastResult.UnknownElevationErrors[pointName];
|
||||
LogService.AddLog(
|
||||
$"{pointName} 平差高程 = {elevation:F4} m,中误差 = ±{FormatValue(error, 4)} m");
|
||||
}
|
||||
|
||||
foreach (ObservationAdjustmentResult item in _lastResult.ObservationResults)
|
||||
{
|
||||
LogService.AddLog(
|
||||
$"第{item.Index}段 {item.FromPoint}->{item.ToPoint}:" +
|
||||
$"v = {item.Residual:F6},平差后高差 = {item.AdjustedHeightDiff:F6}," +
|
||||
$"中误差 = ±{FormatValue(item.AdjustedHeightDiffError, 6)} m");
|
||||
}
|
||||
|
||||
await DialogWindow.ShowMessageAsync(this, "开始平差", "间接平差计算完成。");
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
LogService.AddLog("平差失败:" + exception.Message);
|
||||
await DialogWindow.ShowMessageAsync(this, "平差失败", exception.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ShowResult_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
LeastSquaresResult? result = await GetResultOrRunAdjustmentAsync();
|
||||
if (result is not null)
|
||||
{
|
||||
await new ResultWindow(result).ShowDialog(this);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<LeastSquaresResult?> GetResultOrRunAdjustmentAsync()
|
||||
{
|
||||
if (_lastResult is not null)
|
||||
{
|
||||
return _lastResult;
|
||||
}
|
||||
|
||||
bool shouldRun = await DialogWindow.ConfirmAsync(
|
||||
this,
|
||||
"平差结果",
|
||||
"当前还没有平差结果,是否立即进行平差计算?");
|
||||
|
||||
return shouldRun && await RunAdjustmentAsync() ? _lastResult : null;
|
||||
}
|
||||
|
||||
private async void ExportReport_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
LeastSquaresResult? result = await GetResultOrRunAdjustmentAsync();
|
||||
if (result is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IReadOnlyList<IStorageFolder> folders = await StorageProvider.OpenFolderPickerAsync(
|
||||
new FolderPickerOpenOptions
|
||||
{
|
||||
Title = "选择成果文件和报告文件的输出目录",
|
||||
AllowMultiple = false
|
||||
});
|
||||
|
||||
string? outputFolder = folders.FirstOrDefault()?.Path.LocalPath;
|
||||
if (string.IsNullOrWhiteSpace(outputFolder))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string heightPath = Path.Combine(outputFolder, "height.txt");
|
||||
string surveyPath = Path.Combine(outputFolder, "survey.txt");
|
||||
string reportPath = Path.Combine(outputFolder, "report.txt");
|
||||
|
||||
await File.WriteAllTextAsync(heightPath, Report.BuildHeightText(result), Encoding.UTF8);
|
||||
await File.WriteAllTextAsync(surveyPath, Report.BuildSurveyText(result), Encoding.UTF8);
|
||||
await File.WriteAllTextAsync(
|
||||
reportPath,
|
||||
Report.BuildReportText(DataStore.KnownPoints, DataStore.Observations, result),
|
||||
Encoding.UTF8);
|
||||
|
||||
LogService.AddLog($"高程成果文件已输出:{heightPath}");
|
||||
LogService.AddLog($"高差成果文件已输出:{surveyPath}");
|
||||
LogService.AddLog($"平差报告已输出:{reportPath}");
|
||||
|
||||
bool shouldOpen = await DialogWindow.ConfirmAsync(
|
||||
this,
|
||||
"输出报告",
|
||||
"输出完成,是否打开输出目录?");
|
||||
if (shouldOpen)
|
||||
{
|
||||
PlatformLauncher.OpenFolder(outputFolder);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
LogService.AddLog("输出报告失败:" + exception.Message);
|
||||
await DialogWindow.ShowMessageAsync(this, "输出报告失败", exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ClearData_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
bool confirmed = await DialogWindow.ConfirmAsync(this, "二次确认", "确定要清空当前数据吗?");
|
||||
if (!confirmed)
|
||||
{
|
||||
LogService.AddLog("用户取消清空数据。");
|
||||
return;
|
||||
}
|
||||
|
||||
DataStore.Clear();
|
||||
_lastResult = null;
|
||||
UpdateDataStatus();
|
||||
}
|
||||
|
||||
private async void About_Click(object? sender, RoutedEventArgs e) =>
|
||||
await DialogWindow.ShowMessageAsync(
|
||||
this,
|
||||
"关于",
|
||||
"水准测量间接平差程序\n使用 Avalonia 构建,支持 Windows、macOS 和 Linux。\n运行时:.NET 8");
|
||||
|
||||
private void ClearLog_Click(object? sender, RoutedEventArgs e) => LogControl.Text = string.Empty;
|
||||
|
||||
private void ShowLog(string message)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
LogControl.Text = string.IsNullOrEmpty(LogControl.Text)
|
||||
? message
|
||||
: LogControl.Text + Environment.NewLine + message;
|
||||
LogControl.CaretIndex = LogControl.Text?.Length ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateDataStatus()
|
||||
{
|
||||
DataStatusControl.Text = DataStore.HasData
|
||||
? $"数据来源:{DataStore.DataSource} 已知点:{DataStore.KnownPoints.Count} 观测:{DataStore.Observations.Count}"
|
||||
: "当前未加载数据";
|
||||
}
|
||||
|
||||
private static string FormatValue(double value, int digits) =>
|
||||
double.IsFinite(value) ? value.ToString($"F{digits}") : "无法计算";
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="kcsj.Views.ResultWindow"
|
||||
Title="平差结果"
|
||||
Width="860"
|
||||
Height="600"
|
||||
MinWidth="680"
|
||||
MinHeight="440"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="20" RowDefinitions="*,Auto" RowSpacing="14">
|
||||
<TextBox x:Name="ResultTextBox"
|
||||
IsReadOnly="True"
|
||||
AcceptsReturn="True"
|
||||
TextWrapping="NoWrap"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
FontFamily="Consolas,Menlo,monospace" />
|
||||
<Button Grid.Row="1"
|
||||
HorizontalAlignment="Right"
|
||||
MinWidth="92"
|
||||
Content="关闭"
|
||||
Click="Close_Click" />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,23 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using kcsj.Models;
|
||||
using kcsj.Services;
|
||||
|
||||
namespace kcsj.Views;
|
||||
|
||||
public partial class ResultWindow : Window
|
||||
{
|
||||
public ResultWindow()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public ResultWindow(LeastSquaresResult result)
|
||||
: this()
|
||||
{
|
||||
this.FindControl<TextBox>("ResultTextBox")!.Text = Report.BuildResultText(result);
|
||||
}
|
||||
|
||||
private void Close_Click(object? sender, RoutedEventArgs e) => Close();
|
||||
}
|
||||
Reference in New Issue
Block a user