diff --git a/CRSim.Core/Models/Simulator/TrainStatus.cs b/CRSim.Core/Models/Simulator/TrainStatus.cs
new file mode 100644
index 0000000..995e298
--- /dev/null
+++ b/CRSim.Core/Models/Simulator/TrainStatus.cs
@@ -0,0 +1,14 @@
+namespace CRSim.Core.Models
+{
+ public static class TrainStatus
+ {
+ // 00:00:01 is reserved for "delay unknown"; normal input/export paths only use minute precision.
+ public static readonly TimeSpan DelayUnknown = TimeSpan.FromSeconds(1);
+
+ public static bool IsDelayUnknown(TimeSpan? status) =>
+ status.HasValue && status.Value == DelayUnknown;
+
+ public static TimeSpan GetScheduleOffset(TimeSpan? status) =>
+ IsDelayUnknown(status) ? TimeSpan.Zero : status ?? TimeSpan.Zero;
+ }
+}
diff --git a/CRSim.ScreenSimulator/Converters/TrainStateColorConverter.cs b/CRSim.ScreenSimulator/Converters/TrainStateColorConverter.cs
index 5f71f37..2e64163 100644
--- a/CRSim.ScreenSimulator/Converters/TrainStateColorConverter.cs
+++ b/CRSim.ScreenSimulator/Converters/TrainStateColorConverter.cs
@@ -38,6 +38,7 @@ namespace CRSim.ScreenSimulator.Converters
{
if (values[0] is DateTime arriveTime){
if (values.Length > 1 && values[1] is TimeSpan state){
+ if (TrainStatus.IsDelayUnknown(state)) return StopCheckInColor;
if (state.TotalMinutes > 0) return ArrivingLateText;
if (state.TotalMinutes < 0) return ArrivingEarlyText;
return now >= arriveTime ? ArrivedText : ArrivingText;
@@ -51,6 +52,10 @@ namespace CRSim.ScreenSimulator.Converters
if (values[2] is null)
return StopCheckInColor;
+ // 晚点未定
+ if (values[2] is TimeSpan unknownState && TrainStatus.IsDelayUnknown(unknownState))
+ return StopCheckInColor;
+
// 正点/晚点判断
if (values[0] != null && values[1] == null && values[2] is TimeSpan status)
return GetStatusColor(status);
diff --git a/CRSim.ScreenSimulator/Converters/TrainStateConverter.cs b/CRSim.ScreenSimulator/Converters/TrainStateConverter.cs
index a9a7a4e..7e6cae8 100644
--- a/CRSim.ScreenSimulator/Converters/TrainStateConverter.cs
+++ b/CRSim.ScreenSimulator/Converters/TrainStateConverter.cs
@@ -21,6 +21,7 @@ namespace CRSim.ScreenSimulator.Converters
public string CheckInText { get; set; } = "正在检票";
public string StopCheckInText { get; set; } = "停止检票";
public string SuspendText { get; set; } = "列车停运";
+ public string DelayUnknownText { get; set; } = "晚点未定";
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
_settings ??= StyleManager.ServiceProvider
@@ -39,6 +40,8 @@ namespace CRSim.ScreenSimulator.Converters
{
if (values[2] is TimeSpan state)
{
+ if (TrainStatus.IsDelayUnknown(state))
+ return DelayUnknownText;
// arriveTime += TimeSpan.FromMinutes(state.TotalMinutes);
if (now >= arriveTime)
{
@@ -67,6 +70,10 @@ namespace CRSim.ScreenSimulator.Converters
if (values[2] is null)
return SuspendText;
+ // 晚点未定
+ if (values[2] is TimeSpan unknownState && TrainStatus.IsDelayUnknown(unknownState))
+ return DelayUnknownText;
+
// 有出发时间
if (values[1] is DateTime departureTime && departureTime != DateTime.MinValue)
{
@@ -114,4 +121,4 @@ namespace CRSim.ScreenSimulator.Converters
return null;
}
}
-}
\ No newline at end of file
+}
diff --git a/CRSim.ScreenSimulator/Models/TrainInfo.cs b/CRSim.ScreenSimulator/Models/TrainInfo.cs
index 1c21e16..7d518d9 100644
--- a/CRSim.ScreenSimulator/Models/TrainInfo.cs
+++ b/CRSim.ScreenSimulator/Models/TrainInfo.cs
@@ -1,4 +1,6 @@
-namespace CRSim.ScreenSimulator.Models
+using CRSim.Core.Models;
+
+namespace CRSim.ScreenSimulator.Models
{
public class TrainInfo
{
@@ -9,11 +11,11 @@
private DateTime? departureTime { get; set; }
public DateTime? ArrivalTime
{
- get
+ get
{
- return State is null ? arrivalTime : arrivalTime + State;
- }
- set
+ return State is null || TrainStatus.IsDelayUnknown(State) ? arrivalTime : arrivalTime + State;
+ }
+ set
{
arrivalTime = value;
}
@@ -22,7 +24,7 @@
{
get
{
- return State is null || State < TimeSpan.Zero ? departureTime : departureTime + State;
+ return State is null || State < TimeSpan.Zero || TrainStatus.IsDelayUnknown(State) ? departureTime : departureTime + State;
}
set
{
diff --git a/CRSim.ScreenSimulator/ViewModels/BaseScreenViewModel.cs b/CRSim.ScreenSimulator/ViewModels/BaseScreenViewModel.cs
index 8165f3a..172f5a4 100644
--- a/CRSim.ScreenSimulator/ViewModels/BaseScreenViewModel.cs
+++ b/CRSim.ScreenSimulator/ViewModels/BaseScreenViewModel.cs
@@ -196,22 +196,27 @@ namespace CRSim.ScreenSimulator.ViewModels
{
var now = TimeService.GetDateTimeNow();
var today = now.Date;
+ var scheduleOffset = TrainStatus.GetScheduleOffset(trainNumber.Status);
- if (_settings.LoadTodayOnly && today.Add((trainNumber.DepartureTime ?? trainNumber.ArrivalTime)!.Value).Add(trainNumber.Status.Value) < now)
+ if (_settings.LoadTodayOnly && today.Add((trainNumber.DepartureTime ?? trainNumber.ArrivalTime)!.Value).Add(scheduleOffset) < now)
{
continue;
}
- DateTime? AdjustTime(TimeSpan? time, TimeSpan? status) =>
- time.HasValue ? (today.Add(time.Value).Add(status.Value) > now ? today.Add(time.Value) : today.Add(time.Value).AddDays(1)) : null;
+ DateTime? AdjustTime(TimeSpan? time, TimeSpan status) =>
+ time.HasValue ? (today.Add(time.Value).Add(status) > now ? today.Add(time.Value) : today.Add(time.Value).AddDays(1)) : null;
+
+ var departureOffset = trainNumber.Status is TimeSpan knownStatus && knownStatus > TimeSpan.Zero && !TrainStatus.IsDelayUnknown(knownStatus)
+ ? knownStatus
+ : TimeSpan.Zero;
TrainInfo.Add(new TrainInfo
{
TrainNumber = trainNumber.Number,
Terminal = trainNumber.Terminal,
Origin = trainNumber.Origin,
- ArrivalTime = AdjustTime(trainNumber.ArrivalTime, trainNumber.Status),
- DepartureTime = trainNumber.Status > TimeSpan.Zero ? AdjustTime(trainNumber.DepartureTime, trainNumber.Status) : AdjustTime(trainNumber.DepartureTime, TimeSpan.Zero),
+ ArrivalTime = AdjustTime(trainNumber.ArrivalTime, scheduleOffset),
+ DepartureTime = AdjustTime(trainNumber.DepartureTime, departureOffset),
TicketChecks = trainNumber.TicketCheckIds is null ? [] : [.. station.WaitingAreas
.SelectMany(w => w.TicketChecks)
.Where(tc => trainNumber.TicketCheckIds.Contains(tc.Id))
@@ -232,4 +237,4 @@ namespace CRSim.ScreenSimulator.ViewModels
DataLoaded.SetResult(true);
}
}
-}
\ No newline at end of file
+}
diff --git a/CRSim.ScreenSimulator/ViewModels/MetroPlatformScreenViewModel.cs b/CRSim.ScreenSimulator/ViewModels/MetroPlatformScreenViewModel.cs
index 8e1e822..d401f32 100644
--- a/CRSim.ScreenSimulator/ViewModels/MetroPlatformScreenViewModel.cs
+++ b/CRSim.ScreenSimulator/ViewModels/MetroPlatformScreenViewModel.cs
@@ -53,12 +53,13 @@ namespace CRSim.ScreenSimulator.ViewModels
{
var now = TimeService.GetDateTimeNow();
var today = now.Date;
- if (_settings.LoadTodayOnly && today.Add((trainNumber.DepartureTime ?? trainNumber.ArrivalTime)!.Value).Add(trainNumber.Status.Value) < now)
+ var scheduleOffset = TrainStatus.GetScheduleOffset(trainNumber.Status);
+ if (_settings.LoadTodayOnly && today.Add((trainNumber.DepartureTime ?? trainNumber.ArrivalTime)!.Value).Add(scheduleOffset) < now)
{
continue;
}
DateTime? AdjustTime(TimeSpan? time) =>
- time.HasValue ? (today.Add(time.Value).Add(trainNumber.Status.Value) > now ? today.Add(time.Value) : today.Add(time.Value).AddDays(1)) : null;
+ time.HasValue ? (today.Add(time.Value).Add(scheduleOffset) > now ? today.Add(time.Value) : today.Add(time.Value).AddDays(1)) : null;
TrainInfos.Add(new TrainInfo
{
diff --git a/CRSim/Converters/TimeSpanToStringConverter.cs b/CRSim/Converters/TimeSpanToStringConverter.cs
index a7a3980..65f68d2 100644
--- a/CRSim/Converters/TimeSpanToStringConverter.cs
+++ b/CRSim/Converters/TimeSpanToStringConverter.cs
@@ -1,4 +1,5 @@
-using Microsoft.UI.Xaml.Data;
+using CRSim.Core.Models;
+using Microsoft.UI.Xaml.Data;
using System.Globalization;
namespace CRSim.Converters
@@ -10,10 +11,15 @@ namespace CRSim.Converters
public string Culture { get; set; } = "zh-CN";
public string NullString { get; set; } = string.Empty;
public string ZeroString { get; set; } = string.Empty;
+ public string UnknownString { get; set; } = string.Empty;
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is TimeSpan timeSpan)
{
+ if (TrainStatus.IsDelayUnknown(timeSpan))
+ {
+ return UnknownString;
+ }
if (timeSpan == TimeSpan.Zero)
{
return ZeroString;
diff --git a/CRSim/ViewModels/StationManagementPageViewModel.cs b/CRSim/ViewModels/StationManagementPageViewModel.cs
index 816aa4b..4191f06 100644
--- a/CRSim/ViewModels/StationManagementPageViewModel.cs
+++ b/CRSim/ViewModels/StationManagementPageViewModel.cs
@@ -597,6 +597,7 @@ public partial class StationManagementPageViewModel : ObservableObject
Status = worksheet.Cells[row, 10].Text switch
{
"停运" => null,
+ "晚点未定" => TrainStatus.DelayUnknown,
"正点" => TimeSpan.Zero,
var t when t.StartsWith('-') && TimeSpan.TryParseExact(t[1..], @"hh\:mm", null, out var ts)
=> -ts,
@@ -645,6 +646,7 @@ public partial class StationManagementPageViewModel : ObservableObject
worksheet.Cells[i + 2, 10].Value = TrainStops[i].Status switch
{
null => "停运",
+ var ts when TrainStatus.IsDelayUnknown(ts) => "晚点未定",
var ts when ts == TimeSpan.Zero => "正点",
var ts when ts < TimeSpan.Zero => $"-{ts.Value.Duration():hh\\:mm}",
var ts => ts.Value.ToString(@"hh\:mm")
@@ -855,4 +857,4 @@ public partial class StationManagementPageViewModel : ObservableObject
{
OnPropertyChanged(nameof(FilteredStationNames));
}
-}
\ No newline at end of file
+}
diff --git a/CRSim/Views/DialogContents/TrainStopDialog.xaml b/CRSim/Views/DialogContents/TrainStopDialog.xaml
index cd08d30..49a0377 100644
--- a/CRSim/Views/DialogContents/TrainStopDialog.xaml
+++ b/CRSim/Views/DialogContents/TrainStopDialog.xaml
@@ -10,24 +10,29 @@
xmlns:selectors="using:CRSim.Selectors"
mc:Ignorable="d">
-
-
+
+
-
-
+
+
+ TicketCheckTemplate="{StaticResource TicketCheckTemplate}"/>
-
+
-
+
@@ -38,47 +43,136 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ 正点
+ 早点
+ 晚点
+ 停运
+ 晚点未定
+
+
-
+
始发站
中间站
终到站
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
diff --git a/CRSim/Views/DialogContents/TrainStopDialog.xaml.cs b/CRSim/Views/DialogContents/TrainStopDialog.xaml.cs
index b392449..63b1aff 100644
--- a/CRSim/Views/DialogContents/TrainStopDialog.xaml.cs
+++ b/CRSim/Views/DialogContents/TrainStopDialog.xaml.cs
@@ -1,5 +1,3 @@
-using System.Text.RegularExpressions;
-
namespace CRSim.Views.DialogContents;
public sealed partial class TrainStopDialog : Page
{
@@ -24,6 +22,8 @@ public sealed partial class TrainStopDialog : Page
}
InitializeComponent();
_onValidityChanged = onValidityChanged;
+ StatusComboBox.SelectedIndex = 0;
+ UpdateStatusInputState();
Validate(this, EventArgs.Empty);
}
public TrainStopDialog(List waitingAreas, List platforms, TrainStop trainStop, Action onValidityChanged)
@@ -63,14 +63,35 @@ public sealed partial class TrainStopDialog : Page
EndMinute.IsEnabled = false;
StationKindPanelRadioButtons.SelectedIndex = 2;
}
+
if (trainStop.Status is null)
{
- SuspendToggleSwitch.IsOn = true;
+ StatusComboBox.SelectedItem = "停运";
+ StatusMinutesTextBox.Text = "0";
+ }
+ else if (TrainStatus.IsDelayUnknown(trainStop.Status))
+ {
+ StatusComboBox.SelectedItem = "晚点未定";
+ StatusMinutesTextBox.Text = "0";
+ }
+ else if (trainStop.Status.Value > TimeSpan.Zero)
+ {
+ StatusComboBox.SelectedItem = "晚点";
+ StatusMinutesTextBox.Text = Math.Abs((int)Math.Round(trainStop.Status.Value.TotalMinutes)).ToString();
+ }
+ else if (trainStop.Status.Value < TimeSpan.Zero)
+ {
+ StatusComboBox.SelectedItem = "早点";
+ StatusMinutesTextBox.Text = Math.Abs((int)Math.Round(trainStop.Status.Value.TotalMinutes)).ToString();
}
else
{
- StatusTextBox.Text = trainStop.Status.Value.TotalMinutes.ToString();
+ StatusComboBox.SelectedItem = "正点";
+ StatusMinutesTextBox.Text = "0";
}
+
+ UpdateStatusInputState();
+ Validate(this, EventArgs.Empty);
originalTrainStop = trainStop;
}
private static bool ValidateTime(string input, int maxValue)
@@ -81,6 +102,13 @@ public sealed partial class TrainStopDialog : Page
}
return false;
}
+
+ private bool IsEarlyOrLateSelected()
+ {
+ if (StatusComboBox?.SelectedItem is not string status) return false;
+ return status == "早点" || status == "晚点";
+ }
+
private void Validate(object sender, object e)
{
if (StartHour is null) return;
@@ -89,6 +117,11 @@ public sealed partial class TrainStopDialog : Page
(!StartMinute.IsEnabled || ValidateTime(StartMinute.Text, 60)) &&
(!EndHour.IsEnabled || ValidateTime(EndHour.Text, 24)) &&
(!EndMinute.IsEnabled || ValidateTime(EndMinute.Text, 60));
+
+ bool isStatusValid =
+ !IsEarlyOrLateSelected() ||
+ (int.TryParse(StatusMinutesTextBox.Text, out int minutes) && minutes > 0);
+
bool isValid =
(!WaitingAreasList.IsEnabled ||
WaitingAreasList.SelectedItems?.Count != 0) &&
@@ -98,7 +131,8 @@ public sealed partial class TrainStopDialog : Page
!string.IsNullOrWhiteSpace(DepartureTextBox.Text) &&
int.TryParse(LengthTextBox.Text, out int i) && i > 0 &&
PlatformComboBox.SelectedItem != null &&
- int.TryParse(StatusTextBox.Text, out int m);
+ StatusComboBox.SelectedItem != null &&
+ isStatusValid;
_onValidityChanged?.Invoke(isValid);
}
@@ -119,8 +153,25 @@ public sealed partial class TrainStopDialog : Page
Validate(this, EventArgs.Empty);
if (EndHour.Text.Length == 2) EndMinute.Focus(FocusState.Programmatic);
}
+
+ private void StatusComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ UpdateStatusInputState();
+ Validate(this, EventArgs.Empty);
+ }
+
public void GenerateTrainStop()
{
+ var selectedStatus = StatusComboBox.SelectedItem as string ?? "正点";
+ TimeSpan? statusValue = selectedStatus switch
+ {
+ "停运" => null,
+ "晚点未定" => TrainStatus.DelayUnknown,
+ "晚点" => TimeSpan.FromMinutes(int.Parse(StatusMinutesTextBox.Text)),
+ "早点" => -TimeSpan.FromMinutes(int.Parse(StatusMinutesTextBox.Text)),
+ _ => TimeSpan.Zero
+ };
+
GeneratedTrainStop = new TrainStop
{
Number = NumberTextBox.Text,
@@ -134,10 +185,16 @@ public sealed partial class TrainStopDialog : Page
Platform = (string)PlatformComboBox.SelectedItem,
Length = int.Parse(LengthTextBox.Text),
Landmark = (string)LandmarkComboBox.SelectedItem == "无" ? null : (string)LandmarkComboBox.SelectedItem,
- Status = SuspendToggleSwitch.IsOn ? null : TimeSpan.FromMinutes(int.Parse(StatusTextBox.Text))
+ Status = statusValue
};
}
+ private void UpdateStatusInputState()
+ {
+ if (StatusMinutesTextBox is null || StatusComboBox is null) return;
+ StatusMinutesTextBox.IsEnabled = IsEarlyOrLateSelected();
+ }
+
private void StationKindPanelRadioButtons_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (StartHour == null) return;
@@ -199,4 +256,4 @@ public sealed partial class TrainStopDialog : Page
}
}
-}
\ No newline at end of file
+}
diff --git a/CRSim/Views/StationManagementPage.xaml b/CRSim/Views/StationManagementPage.xaml
index e7e09f3..476682e 100644
--- a/CRSim/Views/StationManagementPage.xaml
+++ b/CRSim/Views/StationManagementPage.xaml
@@ -19,7 +19,7 @@
d:DataContext="{d:DesignInstance Type=viewmodels:StationManagementPageViewModel}">
-
+