@@ -79,6 +79,15 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PrefacedCorg",
|
||||
"name": "PrefacedCorg",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/129855423?v=4",
|
||||
"profile": "https://github.com/PrefacedCorg",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.4.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.4.0")]
|
||||
[assembly: AssemblyVersion("1.7.4.1")]
|
||||
[assembly: AssemblyFileVersion("1.7.4.1")]
|
||||
|
||||
@@ -447,7 +447,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用频率统计数据结构
|
||||
/// 使用频率统计数据结构(优化至秒级精度)
|
||||
/// </summary>
|
||||
private class UsageStats
|
||||
{
|
||||
@@ -460,10 +460,20 @@ namespace Ink_Canvas.Helpers
|
||||
[JsonProperty("launchCount")]
|
||||
public int LaunchCount { get; set; }
|
||||
|
||||
// 新的秒级精度字段
|
||||
[JsonProperty("totalUsageSeconds")]
|
||||
public long TotalUsageSeconds { get; set; }
|
||||
|
||||
[JsonProperty("averageSessionSeconds")]
|
||||
public double AverageSessionSeconds { get; set; }
|
||||
|
||||
// 保留旧字段以保持向后兼容性(已弃用)
|
||||
[JsonProperty("totalUsageMinutes")]
|
||||
[Obsolete("已弃用,请使用 TotalUsageSeconds")]
|
||||
public long TotalUsageMinutes { get; set; }
|
||||
|
||||
[JsonProperty("averageSessionMinutes")]
|
||||
[Obsolete("已弃用,请使用 AverageSessionSeconds")]
|
||||
public double AverageSessionMinutes { get; set; }
|
||||
|
||||
[JsonProperty("lastUpdateCheck")]
|
||||
@@ -481,12 +491,12 @@ namespace Ink_Canvas.Helpers
|
||||
[JsonProperty("lastModified")]
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
// 每周统计数据
|
||||
// 每周统计数据(秒级精度)
|
||||
[JsonProperty("weeklyLaunchCount")]
|
||||
public int WeeklyLaunchCount { get; set; }
|
||||
|
||||
[JsonProperty("weeklyUsageMinutes")]
|
||||
public long WeeklyUsageMinutes { get; set; }
|
||||
[JsonProperty("weeklyUsageSeconds")]
|
||||
public long WeeklyUsageSeconds { get; set; }
|
||||
|
||||
[JsonProperty("weekStartDate")]
|
||||
public DateTime WeekStartDate { get; set; }
|
||||
@@ -494,11 +504,58 @@ namespace Ink_Canvas.Helpers
|
||||
[JsonProperty("lastWeekLaunchCount")]
|
||||
public int LastWeekLaunchCount { get; set; }
|
||||
|
||||
[JsonProperty("lastWeekUsageSeconds")]
|
||||
public long LastWeekUsageSeconds { get; set; }
|
||||
|
||||
// 保留旧字段以保持向后兼容性(已弃用)
|
||||
[JsonProperty("weeklyUsageMinutes")]
|
||||
[Obsolete("已弃用,请使用 WeeklyUsageSeconds")]
|
||||
public long WeeklyUsageMinutes { get; set; }
|
||||
|
||||
[JsonProperty("lastWeekUsageMinutes")]
|
||||
[Obsolete("已弃用,请使用 LastWeekUsageSeconds")]
|
||||
public long LastWeekUsageMinutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 检查并重置每周统计数据
|
||||
/// 数据迁移:从分钟精度迁移到秒级精度
|
||||
/// </summary>
|
||||
public void MigrateToSecondsPrecision()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果新字段为空但旧字段有数据,进行迁移
|
||||
if (TotalUsageSeconds == 0 && TotalUsageMinutes > 0)
|
||||
{
|
||||
TotalUsageSeconds = TotalUsageMinutes * 60;
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移总使用时长: {TotalUsageMinutes}分钟 -> {TotalUsageSeconds}秒");
|
||||
}
|
||||
|
||||
if (AverageSessionSeconds == 0 && AverageSessionMinutes > 0)
|
||||
{
|
||||
AverageSessionSeconds = AverageSessionMinutes * 60;
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移平均会话时长: {AverageSessionMinutes}分钟 -> {AverageSessionSeconds}秒");
|
||||
}
|
||||
|
||||
if (WeeklyUsageSeconds == 0 && WeeklyUsageMinutes > 0)
|
||||
{
|
||||
WeeklyUsageSeconds = WeeklyUsageMinutes * 60;
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移每周使用时长: {WeeklyUsageMinutes}分钟 -> {WeeklyUsageSeconds}秒");
|
||||
}
|
||||
|
||||
if (LastWeekUsageSeconds == 0 && LastWeekUsageMinutes > 0)
|
||||
{
|
||||
LastWeekUsageSeconds = LastWeekUsageMinutes * 60;
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移上周使用时长: {LastWeekUsageMinutes}分钟 -> {LastWeekUsageSeconds}秒");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 数据迁移失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并重置每周统计数据(秒级精度)
|
||||
/// </summary>
|
||||
public void CheckAndResetWeeklyStats()
|
||||
{
|
||||
@@ -510,14 +567,18 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 保存上周数据
|
||||
LastWeekLaunchCount = WeeklyLaunchCount;
|
||||
LastWeekUsageMinutes = WeeklyUsageMinutes;
|
||||
LastWeekUsageSeconds = WeeklyUsageSeconds;
|
||||
|
||||
// 同时更新旧字段以保持兼容性
|
||||
LastWeekUsageMinutes = LastWeekUsageSeconds / 60;
|
||||
|
||||
// 重置本周数据
|
||||
WeeklyLaunchCount = 0;
|
||||
WeeklyUsageSeconds = 0;
|
||||
WeeklyUsageMinutes = 0;
|
||||
WeekStartDate = currentWeekStart;
|
||||
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 每周统计重置 - 上周启动: {LastWeekLaunchCount}次, 上周使用: {LastWeekUsageMinutes}分钟");
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 每周统计重置 - 上周启动: {LastWeekLaunchCount}次, 上周使用: {FormatDuration(LastWeekUsageSeconds)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,20 +602,25 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录本周的使用时长
|
||||
/// 记录本周的使用时长(秒级精度)
|
||||
/// </summary>
|
||||
public void RecordWeeklyUsage(long minutes)
|
||||
public void RecordWeeklyUsage(long seconds)
|
||||
{
|
||||
CheckAndResetWeeklyStats();
|
||||
WeeklyUsageMinutes += minutes;
|
||||
WeeklyUsageSeconds += seconds;
|
||||
// 同时更新旧字段以保持兼容性
|
||||
WeeklyUsageMinutes = WeeklyUsageSeconds / 60;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 计算数据哈希值用于完整性验证
|
||||
/// 计算数据哈希值用于完整性验证(秒级精度)
|
||||
/// </summary>
|
||||
public void UpdateDataHash()
|
||||
{
|
||||
var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}";
|
||||
// 使用秒级精度数据计算哈希
|
||||
var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageSeconds}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageSeconds}|{DataIntegrityKey}";
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString));
|
||||
@@ -564,17 +630,30 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证数据完整性
|
||||
/// 验证数据完整性(秒级精度)
|
||||
/// </summary>
|
||||
public bool VerifyDataIntegrity()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}";
|
||||
// 首先尝试使用秒级精度验证
|
||||
var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageSeconds}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageSeconds}|{DataIntegrityKey}";
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString));
|
||||
var expectedHash = Convert.ToBase64String(hashBytes);
|
||||
if (DataHash == expectedHash)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果秒级精度验证失败,尝试使用旧的分钟精度验证(向后兼容)
|
||||
var oldDataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}";
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(oldDataString));
|
||||
var expectedHash = Convert.ToBase64String(hashBytes);
|
||||
return DataHash == expectedHash;
|
||||
}
|
||||
}
|
||||
@@ -585,6 +664,37 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化时长显示(秒级精度)
|
||||
/// </summary>
|
||||
/// <param name="totalSeconds">总秒数</param>
|
||||
/// <returns>格式化的时长字符串</returns>
|
||||
public static string FormatDuration(long totalSeconds)
|
||||
{
|
||||
if (totalSeconds < 60)
|
||||
{
|
||||
return $"{totalSeconds}秒";
|
||||
}
|
||||
else if (totalSeconds < 3600)
|
||||
{
|
||||
var minutes = totalSeconds / 60;
|
||||
var seconds = totalSeconds % 60;
|
||||
return seconds > 0 ? $"{minutes}分{seconds}秒" : $"{minutes}分钟";
|
||||
}
|
||||
else
|
||||
{
|
||||
var hours = totalSeconds / 3600;
|
||||
var minutes = (totalSeconds % 3600) / 60;
|
||||
var seconds = totalSeconds % 60;
|
||||
|
||||
var result = $"{hours}小时";
|
||||
if (minutes > 0) result += $"{minutes}分";
|
||||
if (seconds > 0) result += $"{seconds}秒";
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新推送优先级枚举
|
||||
/// </summary>
|
||||
@@ -640,7 +750,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录应用退出(计算使用时长)
|
||||
/// 记录应用退出(计算使用时长 - 秒级精度)
|
||||
/// </summary>
|
||||
public static void RecordAppExit()
|
||||
{
|
||||
@@ -650,21 +760,31 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
var stats = LoadUsageStats();
|
||||
|
||||
// 计算本次会话时长
|
||||
long sessionMinutes = 0;
|
||||
// 执行数据迁移(如果需要)
|
||||
stats.MigrateToSecondsPrecision();
|
||||
|
||||
// 计算本次会话时长(秒级精度)
|
||||
long sessionSeconds = 0;
|
||||
if (stats.LastLaunchTime != DateTime.MinValue)
|
||||
{
|
||||
var sessionDuration = DateTime.Now - stats.LastLaunchTime;
|
||||
sessionMinutes = (long)sessionDuration.TotalMinutes;
|
||||
stats.TotalUsageMinutes += sessionMinutes;
|
||||
sessionSeconds = (long)sessionDuration.TotalSeconds;
|
||||
|
||||
// 记录每周使用时长
|
||||
stats.RecordWeeklyUsage(sessionMinutes);
|
||||
// 更新秒级精度数据
|
||||
stats.TotalUsageSeconds += sessionSeconds;
|
||||
|
||||
// 更新平均会话时长
|
||||
// 同时更新旧字段以保持兼容性
|
||||
stats.TotalUsageMinutes = stats.TotalUsageSeconds / 60;
|
||||
|
||||
// 记录每周使用时长(秒级精度)
|
||||
stats.RecordWeeklyUsage(sessionSeconds);
|
||||
|
||||
// 更新平均会话时长(秒级精度)
|
||||
if (stats.LaunchCount > 0)
|
||||
{
|
||||
stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount;
|
||||
stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount;
|
||||
// 同时更新旧字段以保持兼容性
|
||||
stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,7 +796,9 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
SaveUsageStats(stats);
|
||||
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出 - 本次会话: {sessionMinutes}分钟, 总时长: {stats.TotalUsageMinutes}分钟, 本周时长: {stats.WeeklyUsageMinutes}分钟");
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出 - 本次会话: {FormatDuration(sessionSeconds)}, " +
|
||||
$"总时长: {FormatDuration(stats.TotalUsageSeconds)}, " +
|
||||
$"本周时长: {FormatDuration(stats.WeeklyUsageSeconds)}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -699,16 +821,28 @@ namespace Ink_Canvas.Helpers
|
||||
// 计算最近活跃度
|
||||
var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays;
|
||||
|
||||
// 使用真实的每周数据
|
||||
// 使用真实的每周数据(秒级精度)
|
||||
var currentWeekLaunches = stats.WeeklyLaunchCount;
|
||||
var currentWeekMinutes = stats.WeeklyUsageMinutes;
|
||||
var currentWeekSeconds = stats.WeeklyUsageSeconds;
|
||||
|
||||
// 如果秒级数据为空但分钟数据存在,进行转换
|
||||
if (currentWeekSeconds == 0 && stats.WeeklyUsageMinutes > 0)
|
||||
{
|
||||
currentWeekSeconds = stats.WeeklyUsageMinutes * 60;
|
||||
}
|
||||
|
||||
// 如果本周数据不足,参考上周数据
|
||||
var weeklyLaunches = currentWeekLaunches > 0 ? currentWeekLaunches : stats.LastWeekLaunchCount;
|
||||
var weeklyMinutes = currentWeekMinutes > 0 ? currentWeekMinutes : stats.LastWeekUsageMinutes;
|
||||
var weeklySeconds = currentWeekSeconds > 0 ? currentWeekSeconds : stats.LastWeekUsageSeconds;
|
||||
|
||||
// 如果秒级数据仍为空,使用分钟数据转换
|
||||
if (weeklySeconds == 0 && stats.LastWeekUsageMinutes > 0)
|
||||
{
|
||||
weeklySeconds = stats.LastWeekUsageMinutes * 60;
|
||||
}
|
||||
|
||||
// 综合评分系统(0-100分)
|
||||
var frequencyScore = CalculateFrequencyScoreWithWeeklyData(stats, daysSinceLastUse, weeklyLaunches, weeklyMinutes);
|
||||
var frequencyScore = CalculateFrequencyScoreWithWeeklyData(stats, daysSinceLastUse, weeklyLaunches, weeklySeconds);
|
||||
|
||||
// 根据综合评分确定频率分类和更新优先级
|
||||
if (frequencyScore >= 80)
|
||||
@@ -728,7 +862,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率计算 - 评分: {frequencyScore}, 频率: {stats.UsageFrequency}, " +
|
||||
$"优先级: {stats.UpdatePriority}, 本周启动: {currentWeekLaunches}次, 本周时长: {currentWeekMinutes}分钟");
|
||||
$"优先级: {stats.UpdatePriority}, 本周启动: {currentWeekLaunches}次, 本周时长: {FormatDuration(currentWeekSeconds)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -740,16 +874,16 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于每周真实数据计算综合频率评分(0-100分)
|
||||
/// 基于每周真实数据计算综合频率评分(0-100分,秒级精度)
|
||||
/// 评分标准:≥80分=高频用户,40-79分=中频用户,<40分=低频用户
|
||||
/// </summary>
|
||||
/// <param name="stats">使用统计数据</param>
|
||||
/// <param name="daysSinceLastUse">距离最后使用的天数</param>
|
||||
/// <param name="weeklyLaunches">每周启动次数</param>
|
||||
/// <param name="weeklyMinutes">每周使用时长</param>
|
||||
/// <param name="weeklySeconds">每周使用时长(秒)</param>
|
||||
/// <returns>综合评分(0-100分)</returns>
|
||||
private static int CalculateFrequencyScoreWithWeeklyData(UsageStats stats, double daysSinceLastUse,
|
||||
long weeklyLaunches, long weeklyMinutes)
|
||||
long weeklyLaunches, long weeklySeconds)
|
||||
{
|
||||
var score = 0;
|
||||
|
||||
@@ -766,16 +900,17 @@ namespace Ink_Canvas.Helpers
|
||||
else if (weeklyLaunches >= 3) score += 15; // 3-4次:中频使用
|
||||
else if (weeklyLaunches >= 1) score += 10; // 1-2次:低频使用
|
||||
|
||||
// 每周使用时长评分(20分)- 基于真实的每周使用时长
|
||||
if (weeklyMinutes >= 600) score += 20; // 10小时以上:重度使用
|
||||
else if (weeklyMinutes >= 300) score += 15; // 5-10小时:中重度使用
|
||||
else if (weeklyMinutes >= 120) score += 10; // 2-5小时:中度使用
|
||||
else if (weeklyMinutes >= 60) score += 5; // 1-2小时:轻度使用
|
||||
// 每周使用时长评分(20分)- 基于真实的每周使用时长(秒级精度)
|
||||
if (weeklySeconds >= 36000) score += 20; // 10小时以上:重度使用
|
||||
else if (weeklySeconds >= 18000) score += 15; // 5-10小时:中重度使用
|
||||
else if (weeklySeconds >= 7200) score += 10; // 2-5小时:中度使用
|
||||
else if (weeklySeconds >= 3600) score += 5; // 1-2小时:轻度使用
|
||||
|
||||
// 历史使用深度评分(10分)- 反映用户的长期使用习惯
|
||||
if (stats.TotalUsageMinutes >= 3000) score += 10; // 50小时以上:资深用户
|
||||
else if (stats.TotalUsageMinutes >= 1200) score += 7; // 20-50小时:中等用户
|
||||
else if (stats.TotalUsageMinutes >= 300) score += 4; // 5-20小时:新手用户
|
||||
// 历史使用深度评分(10分)- 反映用户的长期使用习惯(秒级精度)
|
||||
var totalSeconds = stats.TotalUsageSeconds > 0 ? stats.TotalUsageSeconds : stats.TotalUsageMinutes * 60;
|
||||
if (totalSeconds >= 180000) score += 10; // 50小时以上:资深用户
|
||||
else if (totalSeconds >= 72000) score += 7; // 20-50小时:中等用户
|
||||
else if (totalSeconds >= 18000) score += 4; // 5-20小时:新手用户
|
||||
|
||||
return Math.Min(100, score);
|
||||
}
|
||||
@@ -817,14 +952,14 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取使用统计信息
|
||||
/// 获取使用统计信息(秒级精度)
|
||||
/// </summary>
|
||||
public static (int launchCount, long totalMinutes, double avgSession, UpdatePriority priority) GetUsageStats()
|
||||
public static (int launchCount, long totalSeconds, double avgSessionSeconds, UpdatePriority priority) GetUsageStats()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = LoadUsageStats();
|
||||
return (stats.LaunchCount, stats.TotalUsageMinutes, stats.AverageSessionMinutes, stats.UpdatePriority);
|
||||
return (stats.LaunchCount, stats.TotalUsageSeconds, stats.AverageSessionSeconds, stats.UpdatePriority);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -834,7 +969,27 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载使用统计 - 支持多重备份恢复和智能反篡改(强化版本)
|
||||
/// 获取使用统计信息(兼容性方法 - 分钟精度)
|
||||
/// </summary>
|
||||
[Obsolete("请使用 GetUsageStats() 获取秒级精度数据")]
|
||||
public static (int launchCount, long totalMinutes, double avgSessionMinutes, UpdatePriority priority) GetUsageStatsInMinutes()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = LoadUsageStats();
|
||||
var totalMinutes = stats.TotalUsageSeconds / 60;
|
||||
var avgMinutes = stats.AverageSessionSeconds / 60;
|
||||
return (stats.LaunchCount, totalMinutes, avgMinutes, stats.UpdatePriority);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 获取使用统计失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return (0, 0, 0, UpdatePriority.Medium);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载使用统计 - 支持多重备份恢复和智能反篡改
|
||||
/// </summary>
|
||||
private static UsageStats LoadUsageStats()
|
||||
{
|
||||
@@ -851,6 +1006,9 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 使用最可信数据源恢复使用统计: {bestData.Source}");
|
||||
|
||||
// 执行数据迁移(如果需要)
|
||||
bestData.Stats.MigrateToSecondsPrecision();
|
||||
|
||||
// 确保备份同步
|
||||
SaveUsageStatsToAllLocations(bestData.Stats);
|
||||
return bestData.Stats;
|
||||
@@ -864,6 +1022,10 @@ namespace Ink_Canvas.Helpers
|
||||
if (partiallyRecoveredData != null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 从部分损坏数据中恢复使用统计");
|
||||
|
||||
// 执行数据迁移(如果需要)
|
||||
partiallyRecoveredData.MigrateToSecondsPrecision();
|
||||
|
||||
SaveUsageStatsToAllLocations(partiallyRecoveredData);
|
||||
return partiallyRecoveredData;
|
||||
}
|
||||
@@ -873,14 +1035,16 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 加载使用统计失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 返回新的统计对象
|
||||
// 返回新的统计对象(秒级精度)
|
||||
var newStats = new UsageStats
|
||||
{
|
||||
DeviceId = DeviceId,
|
||||
LastLaunchTime = DateTime.Now,
|
||||
LaunchCount = 0,
|
||||
TotalUsageMinutes = 0,
|
||||
AverageSessionMinutes = 0,
|
||||
TotalUsageSeconds = 0,
|
||||
AverageSessionSeconds = 0,
|
||||
TotalUsageMinutes = 0, // 保持兼容性
|
||||
AverageSessionMinutes = 0, // 保持兼容性
|
||||
LastUpdateCheck = DateTime.MinValue,
|
||||
UpdatePriority = UpdatePriority.Medium,
|
||||
UsageFrequency = UsageFrequency.Medium
|
||||
@@ -1129,8 +1293,9 @@ namespace Ink_Canvas.Helpers
|
||||
UsageFrequency = UsageFrequency.Medium
|
||||
};
|
||||
|
||||
// 使用多数投票或最大值策略恢复关键数据
|
||||
// 使用多数投票或最大值策略恢复关键数据(秒级精度)
|
||||
var launchCounts = dataSources.Where(d => d.Stats.LaunchCount > 0).Select(d => d.Stats.LaunchCount).ToList();
|
||||
var usageSeconds = dataSources.Where(d => d.Stats.TotalUsageSeconds > 0).Select(d => d.Stats.TotalUsageSeconds).ToList();
|
||||
var usageMinutes = dataSources.Where(d => d.Stats.TotalUsageMinutes > 0).Select(d => d.Stats.TotalUsageMinutes).ToList();
|
||||
|
||||
if (launchCounts.Count > 0)
|
||||
@@ -1138,15 +1303,23 @@ namespace Ink_Canvas.Helpers
|
||||
recoveredStats.LaunchCount = (int)launchCounts.Average(); // 使用平均值
|
||||
}
|
||||
|
||||
if (usageMinutes.Count > 0)
|
||||
// 优先使用秒级数据,如果没有则使用分钟数据转换
|
||||
if (usageSeconds.Count > 0)
|
||||
{
|
||||
recoveredStats.TotalUsageSeconds = (long)usageSeconds.Average(); // 使用平均值
|
||||
recoveredStats.TotalUsageMinutes = recoveredStats.TotalUsageSeconds / 60; // 兼容性
|
||||
}
|
||||
else if (usageMinutes.Count > 0)
|
||||
{
|
||||
recoveredStats.TotalUsageMinutes = (long)usageMinutes.Average(); // 使用平均值
|
||||
recoveredStats.TotalUsageSeconds = recoveredStats.TotalUsageMinutes * 60; // 转换为秒
|
||||
}
|
||||
|
||||
// 重新计算平均会话时长
|
||||
// 重新计算平均会话时长(秒级精度)
|
||||
if (recoveredStats.LaunchCount > 0)
|
||||
{
|
||||
recoveredStats.AverageSessionMinutes = (double)recoveredStats.TotalUsageMinutes / recoveredStats.LaunchCount;
|
||||
recoveredStats.AverageSessionSeconds = (double)recoveredStats.TotalUsageSeconds / recoveredStats.LaunchCount;
|
||||
recoveredStats.AverageSessionMinutes = recoveredStats.AverageSessionSeconds / 60; // 兼容性
|
||||
}
|
||||
|
||||
// 重新计算使用频率
|
||||
@@ -1155,7 +1328,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 更新数据完整性哈希
|
||||
recoveredStats.UpdateDataHash();
|
||||
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复完成 - 启动次数: {recoveredStats.LaunchCount}, 使用时长: {recoveredStats.TotalUsageMinutes}分钟");
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复完成 - 启动次数: {recoveredStats.LaunchCount}, 使用时长: {FormatDuration(recoveredStats.TotalUsageSeconds)}");
|
||||
return recoveredStats;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1178,19 +1351,32 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
var deviceId = key.GetValue("DeviceId") as string;
|
||||
var launchCount = key.GetValue("LaunchCount");
|
||||
|
||||
// 秒级精度数据
|
||||
var totalSeconds = key.GetValue("TotalUsageSeconds");
|
||||
var avgSessionSeconds = key.GetValue("AverageSessionSeconds");
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
var totalMinutes = key.GetValue("TotalUsageMinutes");
|
||||
var avgSessionMinutes = key.GetValue("AverageSessionMinutes");
|
||||
|
||||
var lastLaunch = key.GetValue("LastLaunchTime") as string;
|
||||
var priority = key.GetValue("UpdatePriority");
|
||||
var frequency = key.GetValue("UsageFrequency");
|
||||
var dataHash = key.GetValue("DataHash") as string;
|
||||
var lastUpdate = key.GetValue("LastUpdate") as string;
|
||||
|
||||
// 每周统计数据
|
||||
// 每周统计数据(秒级精度)
|
||||
var weeklyLaunchCount = key.GetValue("WeeklyLaunchCount");
|
||||
var weeklyUsageSeconds = key.GetValue("WeeklyUsageSeconds");
|
||||
var lastWeekUsageSeconds = key.GetValue("LastWeekUsageSeconds");
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
var weeklyUsageMinutes = key.GetValue("WeeklyUsageMinutes");
|
||||
var lastWeekUsageMinutes = key.GetValue("LastWeekUsageMinutes");
|
||||
|
||||
var weekStartDate = key.GetValue("WeekStartDate") as string;
|
||||
var lastWeekLaunchCount = key.GetValue("LastWeekLaunchCount");
|
||||
var lastWeekUsageMinutes = key.GetValue("LastWeekUsageMinutes");
|
||||
|
||||
if (!string.IsNullOrEmpty(deviceId) && launchCount != null)
|
||||
{
|
||||
@@ -1198,26 +1384,42 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
LaunchCount = Convert.ToInt32(launchCount),
|
||||
|
||||
// 秒级精度数据
|
||||
TotalUsageSeconds = totalSeconds != null ? Convert.ToInt64(totalSeconds) : 0,
|
||||
AverageSessionSeconds = avgSessionSeconds != null ? Convert.ToDouble(avgSessionSeconds) : 0,
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0,
|
||||
AverageSessionMinutes = avgSessionMinutes != null ? Convert.ToDouble(avgSessionMinutes) : 0,
|
||||
|
||||
LastLaunchTime = DateTime.TryParse(lastLaunch, out var dt) ? dt : DateTime.Now,
|
||||
UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium,
|
||||
UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium,
|
||||
DataHash = dataHash,
|
||||
AverageSessionMinutes = 0,
|
||||
LastUpdateCheck = DateTime.MinValue,
|
||||
|
||||
// 每周统计数据
|
||||
// 每周统计数据(秒级精度)
|
||||
WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0,
|
||||
WeeklyUsageSeconds = weeklyUsageSeconds != null ? Convert.ToInt64(weeklyUsageSeconds) : 0,
|
||||
LastWeekUsageSeconds = lastWeekUsageSeconds != null ? Convert.ToInt64(lastWeekUsageSeconds) : 0,
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0,
|
||||
LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0,
|
||||
|
||||
WeekStartDate = DateTime.TryParse(weekStartDate, out var wsd) ? wsd : DateTime.MinValue,
|
||||
LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0,
|
||||
LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0
|
||||
LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0
|
||||
};
|
||||
|
||||
// 执行数据迁移(如果需要)
|
||||
stats.MigrateToSecondsPrecision();
|
||||
|
||||
// 重新计算平均会话时长
|
||||
if (stats.LaunchCount > 0)
|
||||
if (stats.LaunchCount > 0 && stats.AverageSessionSeconds == 0)
|
||||
{
|
||||
stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount;
|
||||
stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount;
|
||||
stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60;
|
||||
}
|
||||
|
||||
var dataSource = new DataSourceInfo
|
||||
@@ -1260,46 +1462,75 @@ namespace Ink_Canvas.Helpers
|
||||
if (key != null)
|
||||
{
|
||||
var launchCount = key.GetValue("LC");
|
||||
|
||||
// 秒级精度数据
|
||||
var totalSeconds = key.GetValue("TUS");
|
||||
var avgSessionSeconds = key.GetValue("ASS");
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
var totalMinutes = key.GetValue("TUM");
|
||||
var avgSessionMinutes = key.GetValue("ASM");
|
||||
|
||||
var lastLaunchBinary = key.GetValue("LLT");
|
||||
var priority = key.GetValue("UP");
|
||||
var frequency = key.GetValue("UF");
|
||||
var dataHash = key.GetValue("DH") as string;
|
||||
var lastUpdateBinary = key.GetValue("LU");
|
||||
|
||||
// 每周统计数据
|
||||
// 每周统计数据(秒级精度)
|
||||
var weeklyLaunchCount = key.GetValue("WLC");
|
||||
var weeklyUsageSeconds = key.GetValue("WUS");
|
||||
var lastWeekUsageSeconds = key.GetValue("LWUS");
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
var weeklyUsageMinutes = key.GetValue("WUM");
|
||||
var weekStartDateBinary = key.GetValue("WSD");
|
||||
var lastWeekLaunchCount = key.GetValue("LWLC");
|
||||
var lastWeekUsageMinutes = key.GetValue("LWUM");
|
||||
|
||||
if (launchCount != null && totalMinutes != null)
|
||||
var weekStartDateBinary = key.GetValue("WSD");
|
||||
var lastWeekLaunchCount = key.GetValue("LWLC");
|
||||
|
||||
if (launchCount != null && (totalSeconds != null || totalMinutes != null))
|
||||
{
|
||||
var stats = new UsageStats
|
||||
{
|
||||
DeviceId = DeviceId,
|
||||
LaunchCount = Convert.ToInt32(launchCount),
|
||||
TotalUsageMinutes = Convert.ToInt64(totalMinutes),
|
||||
|
||||
// 秒级精度数据
|
||||
TotalUsageSeconds = totalSeconds != null ? Convert.ToInt64(totalSeconds) : 0,
|
||||
AverageSessionSeconds = avgSessionSeconds != null ? Convert.ToDouble(avgSessionSeconds) : 0,
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0,
|
||||
AverageSessionMinutes = avgSessionMinutes != null ? Convert.ToDouble(avgSessionMinutes) : 0,
|
||||
|
||||
LastLaunchTime = lastLaunchBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastLaunchBinary)) : DateTime.Now,
|
||||
UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium,
|
||||
UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium,
|
||||
DataHash = dataHash,
|
||||
AverageSessionMinutes = 0,
|
||||
LastUpdateCheck = DateTime.MinValue,
|
||||
|
||||
// 每周统计数据
|
||||
// 每周统计数据(秒级精度)
|
||||
WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0,
|
||||
WeeklyUsageSeconds = weeklyUsageSeconds != null ? Convert.ToInt64(weeklyUsageSeconds) : 0,
|
||||
LastWeekUsageSeconds = lastWeekUsageSeconds != null ? Convert.ToInt64(lastWeekUsageSeconds) : 0,
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0,
|
||||
LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0,
|
||||
|
||||
WeekStartDate = weekStartDateBinary != null ? DateTime.FromBinary(Convert.ToInt64(weekStartDateBinary)) : DateTime.MinValue,
|
||||
LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0,
|
||||
LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0
|
||||
LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0
|
||||
};
|
||||
|
||||
// 执行数据迁移(如果需要)
|
||||
stats.MigrateToSecondsPrecision();
|
||||
|
||||
// 重新计算平均会话时长
|
||||
if (stats.LaunchCount > 0)
|
||||
if (stats.LaunchCount > 0 && stats.AverageSessionSeconds == 0)
|
||||
{
|
||||
stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount;
|
||||
stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount;
|
||||
stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60;
|
||||
}
|
||||
|
||||
var dataSource = new DataSourceInfo
|
||||
@@ -1497,21 +1728,35 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
key.SetValue("DeviceId", stats.DeviceId);
|
||||
key.SetValue("LaunchCount", stats.LaunchCount);
|
||||
|
||||
// 秒级精度数据
|
||||
key.SetValue("TotalUsageSeconds", stats.TotalUsageSeconds);
|
||||
key.SetValue("AverageSessionSeconds", stats.AverageSessionSeconds);
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
key.SetValue("TotalUsageMinutes", stats.TotalUsageMinutes);
|
||||
key.SetValue("AverageSessionMinutes", stats.AverageSessionMinutes);
|
||||
|
||||
key.SetValue("LastLaunchTime", stats.LastLaunchTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
key.SetValue("UpdatePriority", (int)stats.UpdatePriority);
|
||||
key.SetValue("UsageFrequency", (int)stats.UsageFrequency);
|
||||
key.SetValue("DataHash", stats.DataHash ?? "");
|
||||
key.SetValue("LastUpdate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
// 每周统计数据
|
||||
// 每周统计数据(秒级精度)
|
||||
key.SetValue("WeeklyLaunchCount", stats.WeeklyLaunchCount);
|
||||
key.SetValue("WeeklyUsageSeconds", stats.WeeklyUsageSeconds);
|
||||
key.SetValue("LastWeekUsageSeconds", stats.LastWeekUsageSeconds);
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
key.SetValue("WeeklyUsageMinutes", stats.WeeklyUsageMinutes);
|
||||
key.SetValue("WeekStartDate", stats.WeekStartDate.ToString("yyyy-MM-dd"));
|
||||
key.SetValue("LastWeekLaunchCount", stats.LastWeekLaunchCount);
|
||||
key.SetValue("LastWeekUsageMinutes", stats.LastWeekUsageMinutes);
|
||||
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到主注册表 - 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次, 总时长: {stats.TotalUsageMinutes}分钟, 本周时长: {stats.WeeklyUsageMinutes}分钟");
|
||||
key.SetValue("WeekStartDate", stats.WeekStartDate.ToString("yyyy-MM-dd"));
|
||||
key.SetValue("LastWeekLaunchCount", stats.LastWeekLaunchCount);
|
||||
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到主注册表 - 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次, " +
|
||||
$"总时长: {FormatDuration(stats.TotalUsageSeconds)}, 本周时长: {FormatDuration(stats.WeeklyUsageSeconds)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1548,21 +1793,34 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
if (key != null)
|
||||
{
|
||||
// 使用编码的键名来隐藏数据
|
||||
// 使用编码的键名来隐藏数据(秒级精度)
|
||||
key.SetValue("LC", stats.LaunchCount); // LaunchCount
|
||||
|
||||
// 秒级精度数据
|
||||
key.SetValue("TUS", stats.TotalUsageSeconds); // TotalUsageSeconds
|
||||
key.SetValue("ASS", stats.AverageSessionSeconds); // AverageSessionSeconds
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
key.SetValue("TUM", stats.TotalUsageMinutes); // TotalUsageMinutes
|
||||
key.SetValue("ASM", stats.AverageSessionMinutes); // AverageSessionMinutes
|
||||
|
||||
key.SetValue("LLT", stats.LastLaunchTime.ToBinary()); // LastLaunchTime
|
||||
key.SetValue("UP", (int)stats.UpdatePriority); // UpdatePriority
|
||||
key.SetValue("UF", (int)stats.UsageFrequency); // UsageFrequency
|
||||
key.SetValue("DH", stats.DataHash ?? ""); // DataHash
|
||||
key.SetValue("LU", DateTime.Now.ToBinary()); // LastUpdate
|
||||
|
||||
// 每周统计数据
|
||||
// 每周统计数据(秒级精度)
|
||||
key.SetValue("WLC", stats.WeeklyLaunchCount); // WeeklyLaunchCount
|
||||
key.SetValue("WUS", stats.WeeklyUsageSeconds); // WeeklyUsageSeconds
|
||||
key.SetValue("LWUS", stats.LastWeekUsageSeconds); // LastWeekUsageSeconds
|
||||
|
||||
// 兼容性:分钟精度数据
|
||||
key.SetValue("WUM", stats.WeeklyUsageMinutes); // WeeklyUsageMinutes
|
||||
key.SetValue("LWUM", stats.LastWeekUsageMinutes); // LastWeekUsageMinutes
|
||||
|
||||
key.SetValue("WSD", stats.WeekStartDate.ToBinary()); // WeekStartDate
|
||||
key.SetValue("LWLC", stats.LastWeekLaunchCount); // LastWeekLaunchCount
|
||||
key.SetValue("LWUM", stats.LastWeekUsageMinutes); // LastWeekUsageMinutes
|
||||
|
||||
successCount++;
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 成功保存到备用注册表位置: {path}");
|
||||
@@ -2013,15 +2271,15 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
var (launchCount, totalMinutes, avgSession, priority) = GetUsageStats();
|
||||
var (launchCount, totalSeconds, avgSessionSeconds, priority) = GetUsageStats();
|
||||
var frequency = GetUsageFrequency();
|
||||
var stats = LoadUsageStats();
|
||||
var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays;
|
||||
|
||||
return $"设备ID: {DeviceId}\n" +
|
||||
$"启动次数: {launchCount}\n" +
|
||||
$"总使用时长: {totalMinutes}分钟 ({totalMinutes / 60.0:F1}小时)\n" +
|
||||
$"平均会话时长: {avgSession:F1}分钟\n" +
|
||||
$"总使用时长: {FormatDuration(totalSeconds)}\n" +
|
||||
$"平均会话时长: {FormatDuration((long)avgSessionSeconds)}\n" +
|
||||
$"使用频率: {frequency}\n" +
|
||||
$"更新优先级: {priority}\n" +
|
||||
$"最后使用: {daysSinceLastUse:F1}天前\n" +
|
||||
@@ -2209,11 +2467,11 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
summary.AppendLine($"数据完整性: {(stats.VerifyDataIntegrity() ? "✓" : "✗")}");
|
||||
summary.AppendLine($"总启动次数: {stats.LaunchCount}");
|
||||
summary.AppendLine($"总使用时长: {stats.TotalUsageMinutes}分钟 ({stats.TotalUsageMinutes / 60.0:F1}小时)");
|
||||
summary.AppendLine($"总使用时长: {FormatDuration(stats.TotalUsageSeconds)}");
|
||||
summary.AppendLine($"本周启动次数: {stats.WeeklyLaunchCount}");
|
||||
summary.AppendLine($"本周使用时长: {stats.WeeklyUsageMinutes}分钟 ({stats.WeeklyUsageMinutes / 60.0:F1}小时)");
|
||||
summary.AppendLine($"本周使用时长: {FormatDuration(stats.WeeklyUsageSeconds)}");
|
||||
summary.AppendLine($"上周启动次数: {stats.LastWeekLaunchCount}");
|
||||
summary.AppendLine($"上周使用时长: {stats.LastWeekUsageMinutes}分钟 ({stats.LastWeekUsageMinutes / 60.0:F1}小时)");
|
||||
summary.AppendLine($"上周使用时长: {FormatDuration(stats.LastWeekUsageSeconds)}");
|
||||
summary.AppendLine($"本周开始日期: {(stats.WeekStartDate != DateTime.MinValue ? stats.WeekStartDate.ToString("yyyy-MM-dd") : "未设置")}");
|
||||
summary.AppendLine($"使用频率: {stats.UsageFrequency}");
|
||||
summary.AppendLine($"更新优先级: {stats.UpdatePriority}");
|
||||
|
||||
@@ -774,6 +774,12 @@
|
||||
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchClearCanvasAndClearTimeMachine_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="清空画布时同时清空图片" VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchClearCanvasAlsoClearImages"
|
||||
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchClearCanvasAlsoClearImages_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="插入图片时自动压缩(大于1920x1080)" VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchCompressPicturesUploaded"
|
||||
@@ -5787,7 +5793,6 @@
|
||||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="10" Text="/ 999" Name="PPTBtnPageTotal" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Width="50" Height="50" MouseUp="GridPPTControlNext_MouseUp"
|
||||
|
||||
@@ -404,6 +404,9 @@ namespace Ink_Canvas {
|
||||
// 确保开关和设置同步
|
||||
ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode;
|
||||
ApplyNoFocusMode();
|
||||
|
||||
// 初始化UIElement选择系统
|
||||
InitializeUIElementSelection();
|
||||
}
|
||||
|
||||
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e) {
|
||||
@@ -946,23 +949,12 @@ namespace Ink_Canvas {
|
||||
}
|
||||
UpdatePriorityTextBlock.Text = priorityText;
|
||||
|
||||
// 获取使用统计
|
||||
var (launchCount, totalMinutes, avgSession, _) = DeviceIdentifier.GetUsageStats();
|
||||
// 获取使用统计(秒级精度)
|
||||
var (launchCount, totalSeconds, avgSessionSeconds, _) = DeviceIdentifier.GetUsageStats();
|
||||
LaunchCountTextBlock.Text = launchCount.ToString();
|
||||
|
||||
string totalUsageText;
|
||||
if (totalMinutes < 60)
|
||||
{
|
||||
totalUsageText = $"{totalMinutes}分钟";
|
||||
}
|
||||
else if (totalMinutes < 1440)
|
||||
{
|
||||
totalUsageText = $"{totalMinutes / 60}小时{totalMinutes % 60}分钟";
|
||||
}
|
||||
else
|
||||
{
|
||||
totalUsageText = $"{totalMinutes / 1440}天{(totalMinutes % 1440) / 60}小时";
|
||||
}
|
||||
|
||||
// 使用新的格式化方法显示秒级精度的使用时长
|
||||
string totalUsageText = DeviceIdentifier.FormatDuration(totalSeconds);
|
||||
TotalUsageTextBlock.Text = totalUsageText;
|
||||
|
||||
LogHelper.WriteLogToFile($"MainWindow | 设备信息已刷新 - ID: {deviceId}, 频率: {frequencyText}, 优先级: {priorityText}");
|
||||
|
||||
@@ -29,6 +29,15 @@ namespace Ink_Canvas {
|
||||
TimeMachineHistories[0] = timeMachineHistory;
|
||||
timeMachine.ClearStrokeHistory();
|
||||
} else {
|
||||
// 新增:提交所有图片元素到时间机器历史记录
|
||||
foreach (var child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image img)
|
||||
{
|
||||
timeMachine.CommitElementInsertHistory(img);
|
||||
}
|
||||
}
|
||||
|
||||
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
||||
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
|
||||
timeMachine.ClearStrokeHistory();
|
||||
@@ -71,6 +80,14 @@ namespace Ink_Canvas {
|
||||
foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item);
|
||||
} else {
|
||||
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[CurrentWhiteboardIndex]);
|
||||
// 新增:通过时间机器历史恢复图片
|
||||
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex])
|
||||
{
|
||||
if (item.CommitType == TimeMachineHistoryType.ElementInsert)
|
||||
{
|
||||
inkCanvas.Children.Add(item.InsertedElement);
|
||||
}
|
||||
}
|
||||
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item);
|
||||
// 恢复当前页图片信息
|
||||
inkCanvas.Children.Clear();
|
||||
|
||||
@@ -716,12 +716,44 @@ namespace Ink_Canvas {
|
||||
private void BoardSymbolIconDelete_MouseUp(object sender, RoutedEventArgs e) {
|
||||
PenIcon_Click(null, null);
|
||||
SymbolIconDelete_MouseUp(null, null);
|
||||
|
||||
// 根据设置决定是否清空图片
|
||||
if (Settings.Canvas.ClearCanvasAlsoClearImages) {
|
||||
// 如果设置为清空图片,则直接清空所有子元素
|
||||
System.Diagnostics.Debug.WriteLine("BoardSymbolIconDelete: Clearing all children including images");
|
||||
inkCanvas.Children.Clear();
|
||||
} else {
|
||||
// 保存非笔画元素(如图片)
|
||||
System.Diagnostics.Debug.WriteLine("BoardSymbolIconDelete: Preserving non-stroke elements (images)");
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
System.Diagnostics.Debug.WriteLine($"BoardSymbolIconDelete: Preserved elements count: {preservedElements.Count}");
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
System.Diagnostics.Debug.WriteLine($"BoardSymbolIconDelete: inkCanvas.Children.Count after restore: {inkCanvas.Children.Count}");
|
||||
}
|
||||
}
|
||||
private void BoardSymbolIconDeleteInkAndHistories_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PenIcon_Click(null, null);
|
||||
SymbolIconDelete_MouseUp(null, null);
|
||||
if (Settings.Canvas.ClearCanvasAndClearTimeMachine == false) timeMachine.ClearStrokeHistory();
|
||||
|
||||
// 根据设置决定是否清空图片
|
||||
if (Settings.Canvas.ClearCanvasAlsoClearImages) {
|
||||
// 如果设置为清空图片,则直接清空所有子元素
|
||||
System.Diagnostics.Debug.WriteLine("BoardSymbolIconDeleteInkAndHistories: Clearing all children including images");
|
||||
inkCanvas.Children.Clear();
|
||||
} else {
|
||||
// 保存非笔画元素(如图片)
|
||||
System.Diagnostics.Debug.WriteLine("BoardSymbolIconDeleteInkAndHistories: Preserving non-stroke elements (images)");
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
System.Diagnostics.Debug.WriteLine($"BoardSymbolIconDeleteInkAndHistories: Preserved elements count: {preservedElements.Count}");
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
System.Diagnostics.Debug.WriteLine($"BoardSymbolIconDeleteInkAndHistories: inkCanvas.Children.Count after restore: {inkCanvas.Children.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
private void BoardLaunchEasiCamera_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Ink_Canvas
|
||||
@@ -41,6 +42,10 @@ namespace Ink_Canvas
|
||||
|
||||
inkCanvas.Children.Add(image);
|
||||
|
||||
// 添加鼠标事件处理,使图片可以被选择
|
||||
image.MouseDown += UIElement_MouseDown;
|
||||
image.IsManipulationEnabled = true;
|
||||
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
}
|
||||
}
|
||||
@@ -72,6 +77,9 @@ namespace Ink_Canvas
|
||||
int height = bitmapImage.PixelHeight;
|
||||
|
||||
Image image = new Image();
|
||||
// 设置拉伸模式为Fill,支持任意比例缩放
|
||||
image.Stretch = Stretch.Fill;
|
||||
|
||||
if (isLoaded && Settings.Canvas.IsCompressPicturesUploaded && (width > 1920 || height > 1080))
|
||||
{
|
||||
double scaleX = 1920.0 / width;
|
||||
@@ -116,6 +124,10 @@ namespace Ink_Canvas
|
||||
InkCanvas.SetTop(mediaElement, 0);
|
||||
inkCanvas.Children.Add(mediaElement);
|
||||
|
||||
// 添加鼠标事件处理,使媒体元素可以被选择
|
||||
mediaElement.MouseDown += UIElement_MouseDown;
|
||||
mediaElement.IsManipulationEnabled = true;
|
||||
|
||||
mediaElement.LoadedBehavior = MediaState.Manual;
|
||||
mediaElement.UnloadedBehavior = MediaState.Manual;
|
||||
mediaElement.Loaded += async (_, args) =>
|
||||
|
||||
@@ -1726,7 +1726,11 @@ namespace Ink_Canvas {
|
||||
}
|
||||
|
||||
ClearStrokes(false);
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
|
||||
CancelSingleFingerDragMode();
|
||||
|
||||
@@ -2021,5 +2025,7 @@ namespace Ink_Canvas {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas {
|
||||
@@ -272,12 +273,31 @@ namespace Ink_Canvas {
|
||||
if (isProgramChangeStrokeSelection) return;
|
||||
if (inkCanvas.GetSelectedStrokes().Count == 0) {
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
// 当没有选中笔画时,检查是否有选中的UIElement
|
||||
CheckUIElementSelection();
|
||||
}
|
||||
else {
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
|
||||
BorderStrokeSelectionClone.Background = Brushes.Transparent;
|
||||
isStrokeSelectionCloneOn = false;
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
// 当选中笔画时,取消UIElement选择
|
||||
DeselectUIElement();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckUIElementSelection()
|
||||
{
|
||||
// 检查InkCanvas中的UIElement是否被选中
|
||||
var selectedElements = inkCanvas.GetSelectedElements();
|
||||
if (selectedElements.Count > 0)
|
||||
{
|
||||
var element = selectedElements[0];
|
||||
SelectUIElement(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeselectUIElement();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,5 +456,374 @@ namespace Ink_Canvas {
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
}
|
||||
|
||||
#region UIElement Selection and Resize
|
||||
|
||||
private UIElement selectedUIElement = null;
|
||||
private System.Windows.Controls.Canvas resizeHandlesCanvas = null;
|
||||
private readonly List<Rectangle> resizeHandles = new List<Rectangle>();
|
||||
private bool isResizing = false;
|
||||
private ResizeDirection currentResizeDirection = ResizeDirection.None;
|
||||
private Point resizeStartPoint;
|
||||
private Rect originalElementBounds;
|
||||
|
||||
private enum ResizeDirection
|
||||
{
|
||||
None,
|
||||
TopLeft,
|
||||
TopCenter,
|
||||
TopRight,
|
||||
MiddleLeft,
|
||||
MiddleRight,
|
||||
BottomLeft,
|
||||
BottomCenter,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
private void InitializeUIElementSelection()
|
||||
{
|
||||
// 创建拖拽手柄画布
|
||||
if (resizeHandlesCanvas == null)
|
||||
{
|
||||
resizeHandlesCanvas = new System.Windows.Controls.Canvas
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
IsHitTestVisible = true,
|
||||
Visibility = Visibility.Collapsed
|
||||
};
|
||||
|
||||
// 将手柄画布添加到主网格中,确保它在InkCanvas之上
|
||||
var mainGrid = inkCanvas.Parent as Grid;
|
||||
if (mainGrid != null)
|
||||
{
|
||||
mainGrid.Children.Add(resizeHandlesCanvas);
|
||||
Panel.SetZIndex(resizeHandlesCanvas, 1000); // 确保在最上层
|
||||
}
|
||||
}
|
||||
|
||||
// 创建8个拖拽手柄
|
||||
CreateResizeHandles();
|
||||
}
|
||||
|
||||
private void CreateResizeHandles()
|
||||
{
|
||||
resizeHandles.Clear();
|
||||
resizeHandlesCanvas.Children.Clear();
|
||||
|
||||
var directions = new[]
|
||||
{
|
||||
ResizeDirection.TopLeft, ResizeDirection.TopCenter, ResizeDirection.TopRight,
|
||||
ResizeDirection.MiddleLeft, ResizeDirection.MiddleRight,
|
||||
ResizeDirection.BottomLeft, ResizeDirection.BottomCenter, ResizeDirection.BottomRight
|
||||
};
|
||||
|
||||
foreach (var direction in directions)
|
||||
{
|
||||
var handle = new Rectangle
|
||||
{
|
||||
Width = 12,
|
||||
Height = 12,
|
||||
Fill = Brushes.White,
|
||||
Stroke = Brushes.DodgerBlue,
|
||||
StrokeThickness = 2,
|
||||
Cursor = GetCursorForDirection(direction),
|
||||
Tag = direction
|
||||
};
|
||||
|
||||
handle.MouseDown += ResizeHandle_MouseDown;
|
||||
handle.MouseMove += ResizeHandle_MouseMove;
|
||||
handle.MouseUp += ResizeHandle_MouseUp;
|
||||
|
||||
resizeHandles.Add(handle);
|
||||
resizeHandlesCanvas.Children.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
private Cursor GetCursorForDirection(ResizeDirection direction)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case ResizeDirection.TopLeft:
|
||||
case ResizeDirection.BottomRight:
|
||||
return Cursors.SizeNWSE;
|
||||
case ResizeDirection.TopRight:
|
||||
case ResizeDirection.BottomLeft:
|
||||
return Cursors.SizeNESW;
|
||||
case ResizeDirection.TopCenter:
|
||||
case ResizeDirection.BottomCenter:
|
||||
return Cursors.SizeNS;
|
||||
case ResizeDirection.MiddleLeft:
|
||||
case ResizeDirection.MiddleRight:
|
||||
return Cursors.SizeWE;
|
||||
default:
|
||||
return Cursors.Arrow;
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectUIElement(UIElement element)
|
||||
{
|
||||
if (selectedUIElement == element) return;
|
||||
|
||||
// 取消之前的选择
|
||||
DeselectUIElement();
|
||||
|
||||
// 清除笔画选择
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
isProgramChangeStrokeSelection = true;
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
isProgramChangeStrokeSelection = false;
|
||||
}
|
||||
|
||||
selectedUIElement = element;
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
// 初始化选择系统(如果还没有初始化)
|
||||
if (resizeHandlesCanvas == null)
|
||||
{
|
||||
InitializeUIElementSelection();
|
||||
}
|
||||
|
||||
// 显示拖拽手柄
|
||||
ShowResizeHandles();
|
||||
}
|
||||
}
|
||||
|
||||
private void DeselectUIElement()
|
||||
{
|
||||
selectedUIElement = null;
|
||||
HideResizeHandles();
|
||||
}
|
||||
|
||||
private void ShowResizeHandles()
|
||||
{
|
||||
if (selectedUIElement == null || resizeHandlesCanvas == null) return;
|
||||
|
||||
var bounds = GetUIElementBounds(selectedUIElement);
|
||||
UpdateResizeHandlesPosition(bounds);
|
||||
resizeHandlesCanvas.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideResizeHandles()
|
||||
{
|
||||
if (resizeHandlesCanvas != null)
|
||||
{
|
||||
resizeHandlesCanvas.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private Rect GetUIElementBounds(UIElement element)
|
||||
{
|
||||
var left = InkCanvas.GetLeft(element);
|
||||
var top = InkCanvas.GetTop(element);
|
||||
|
||||
if (double.IsNaN(left)) left = 0;
|
||||
if (double.IsNaN(top)) top = 0;
|
||||
|
||||
var width = 0.0;
|
||||
var height = 0.0;
|
||||
|
||||
if (element is FrameworkElement fe)
|
||||
{
|
||||
width = fe.ActualWidth > 0 ? fe.ActualWidth : fe.Width;
|
||||
height = fe.ActualHeight > 0 ? fe.ActualHeight : fe.Height;
|
||||
}
|
||||
|
||||
return new Rect(left, top, width, height);
|
||||
}
|
||||
|
||||
private void UpdateResizeHandlesPosition(Rect bounds)
|
||||
{
|
||||
if (resizeHandles.Count != 8) return;
|
||||
|
||||
var handleSize = 12.0;
|
||||
var halfHandle = handleSize / 2;
|
||||
|
||||
// 计算手柄位置
|
||||
var positions = new[]
|
||||
{
|
||||
new Point(bounds.Left - halfHandle, bounds.Top - halfHandle), // TopLeft
|
||||
new Point(bounds.Left + bounds.Width / 2 - halfHandle, bounds.Top - halfHandle), // TopCenter
|
||||
new Point(bounds.Right - halfHandle, bounds.Top - halfHandle), // TopRight
|
||||
new Point(bounds.Left - halfHandle, bounds.Top + bounds.Height / 2 - halfHandle), // MiddleLeft
|
||||
new Point(bounds.Right - halfHandle, bounds.Top + bounds.Height / 2 - halfHandle), // MiddleRight
|
||||
new Point(bounds.Left - halfHandle, bounds.Bottom - halfHandle), // BottomLeft
|
||||
new Point(bounds.Left + bounds.Width / 2 - halfHandle, bounds.Bottom - halfHandle), // BottomCenter
|
||||
new Point(bounds.Right - halfHandle, bounds.Bottom - halfHandle) // BottomRight
|
||||
};
|
||||
|
||||
for (int i = 0; i < resizeHandles.Count && i < positions.Length; i++)
|
||||
{
|
||||
System.Windows.Controls.Canvas.SetLeft(resizeHandles[i], positions[i].X);
|
||||
System.Windows.Controls.Canvas.SetTop(resizeHandles[i], positions[i].Y);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeHandle_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (selectedUIElement == null) return;
|
||||
|
||||
var handle = sender as Rectangle;
|
||||
if (handle?.Tag is ResizeDirection direction)
|
||||
{
|
||||
isResizing = true;
|
||||
currentResizeDirection = direction;
|
||||
resizeStartPoint = e.GetPosition(inkCanvas);
|
||||
originalElementBounds = GetUIElementBounds(selectedUIElement);
|
||||
|
||||
handle.CaptureMouse();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeHandle_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isResizing || selectedUIElement == null) return;
|
||||
|
||||
var currentPoint = e.GetPosition(inkCanvas);
|
||||
var deltaX = currentPoint.X - resizeStartPoint.X;
|
||||
var deltaY = currentPoint.Y - resizeStartPoint.Y;
|
||||
|
||||
ResizeUIElement(deltaX, deltaY);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ResizeHandle_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (isResizing)
|
||||
{
|
||||
isResizing = false;
|
||||
currentResizeDirection = ResizeDirection.None;
|
||||
|
||||
var handle = sender as Rectangle;
|
||||
handle?.ReleaseMouseCapture();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeUIElement(double deltaX, double deltaY)
|
||||
{
|
||||
if (selectedUIElement == null) return;
|
||||
|
||||
var newBounds = originalElementBounds;
|
||||
const double minSize = 20.0;
|
||||
|
||||
switch (currentResizeDirection)
|
||||
{
|
||||
case ResizeDirection.TopLeft:
|
||||
var newWidth = originalElementBounds.Width - deltaX;
|
||||
var newHeight = originalElementBounds.Height - deltaY;
|
||||
if (newWidth >= minSize && newHeight >= minSize)
|
||||
{
|
||||
newBounds.X = originalElementBounds.X + deltaX;
|
||||
newBounds.Y = originalElementBounds.Y + deltaY;
|
||||
newBounds.Width = newWidth;
|
||||
newBounds.Height = newHeight;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.TopCenter:
|
||||
var newHeightTC = originalElementBounds.Height - deltaY;
|
||||
if (newHeightTC >= minSize)
|
||||
{
|
||||
newBounds.Y = originalElementBounds.Y + deltaY;
|
||||
newBounds.Height = newHeightTC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.TopRight:
|
||||
var newWidthTR = originalElementBounds.Width + deltaX;
|
||||
var newHeightTR = originalElementBounds.Height - deltaY;
|
||||
if (newWidthTR >= minSize && newHeightTR >= minSize)
|
||||
{
|
||||
newBounds.Y = originalElementBounds.Y + deltaY;
|
||||
newBounds.Width = newWidthTR;
|
||||
newBounds.Height = newHeightTR;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.MiddleLeft:
|
||||
var newWidthML = originalElementBounds.Width - deltaX;
|
||||
if (newWidthML >= minSize)
|
||||
{
|
||||
newBounds.X = originalElementBounds.X + deltaX;
|
||||
newBounds.Width = newWidthML;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.MiddleRight:
|
||||
var newWidthMR = originalElementBounds.Width + deltaX;
|
||||
if (newWidthMR >= minSize)
|
||||
{
|
||||
newBounds.Width = newWidthMR;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.BottomLeft:
|
||||
var newWidthBL = originalElementBounds.Width - deltaX;
|
||||
var newHeightBL = originalElementBounds.Height + deltaY;
|
||||
if (newWidthBL >= minSize && newHeightBL >= minSize)
|
||||
{
|
||||
newBounds.X = originalElementBounds.X + deltaX;
|
||||
newBounds.Width = newWidthBL;
|
||||
newBounds.Height = newHeightBL;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.BottomCenter:
|
||||
var newHeightBC = originalElementBounds.Height + deltaY;
|
||||
if (newHeightBC >= minSize)
|
||||
{
|
||||
newBounds.Height = newHeightBC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResizeDirection.BottomRight:
|
||||
var newWidthBR = originalElementBounds.Width + deltaX;
|
||||
var newHeightBR = originalElementBounds.Height + deltaY;
|
||||
if (newWidthBR >= minSize && newHeightBR >= minSize)
|
||||
{
|
||||
newBounds.Width = newWidthBR;
|
||||
newBounds.Height = newHeightBR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 应用新的尺寸和位置
|
||||
ApplyUIElementBounds(selectedUIElement, newBounds);
|
||||
|
||||
// 更新手柄位置
|
||||
UpdateResizeHandlesPosition(newBounds);
|
||||
}
|
||||
|
||||
private void ApplyUIElementBounds(UIElement element, Rect bounds)
|
||||
{
|
||||
InkCanvas.SetLeft(element, bounds.X);
|
||||
InkCanvas.SetTop(element, bounds.Y);
|
||||
|
||||
if (element is FrameworkElement fe)
|
||||
{
|
||||
fe.Width = bounds.Width;
|
||||
fe.Height = bounds.Height;
|
||||
}
|
||||
}
|
||||
|
||||
private void UIElement_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
|
||||
{
|
||||
var element = sender as UIElement;
|
||||
if (element != null)
|
||||
{
|
||||
// 切换到选择模式并选择这个元素
|
||||
inkCanvas.Select(new UIElement[] { element });
|
||||
SelectUIElement(element);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1507,18 +1507,22 @@ namespace Ink_Canvas {
|
||||
InkCanvasEditingMode currentEditingMode = inkCanvas.EditingMode;
|
||||
int currentDrawingShapeMode = drawingShapeMode;
|
||||
bool currentForceEraser = forceEraser;
|
||||
|
||||
|
||||
inkCanvas.StylusDown -= MainWindow_StylusDown;
|
||||
inkCanvas.StylusMove -= MainWindow_StylusMove;
|
||||
inkCanvas.StylusUp -= MainWindow_StylusUp;
|
||||
inkCanvas.TouchDown -= MainWindow_TouchDown;
|
||||
inkCanvas.TouchDown += Main_Grid_TouchDown;
|
||||
|
||||
|
||||
// 先设为None再设回原来的模式,避免可能的事件冲突
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = false;
|
||||
|
||||
|
||||
// 恢复到之前的编辑状态
|
||||
inkCanvas.EditingMode = currentEditingMode;
|
||||
drawingShapeMode = currentDrawingShapeMode;
|
||||
@@ -2217,6 +2221,13 @@ namespace Ink_Canvas {
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchClearCanvasAlsoClearImages_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Canvas.ClearCanvasAlsoClearImages = ToggleSwitchClearCanvasAlsoClearImages.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchCompressPicturesUploaded_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2247,5 +2258,7 @@ namespace Ink_Canvas {
|
||||
Settings.Canvas.EnablePalmEraser = ToggleSwitchEnablePalmEraser.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,6 +466,7 @@ namespace Ink_Canvas {
|
||||
|
||||
ToggleSwitchClearCanvasAndClearTimeMachine.IsOn =
|
||||
Settings.Canvas.ClearCanvasAndClearTimeMachine == true;
|
||||
ToggleSwitchClearCanvasAlsoClearImages.IsOn = Settings.Canvas.ClearCanvasAlsoClearImages;
|
||||
|
||||
switch (Settings.Canvas.EraserShapeType) {
|
||||
case 0: {
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Ink_Canvas {
|
||||
canvas.Strokes.Remove(currentStroke);
|
||||
}
|
||||
} else if (item.CommitType == TimeMachineHistoryType.ElementInsert) {
|
||||
if (!item.StrokeHasBeenCleared) {
|
||||
if (item.StrokeHasBeenCleared) {
|
||||
// Undo: 移除元素
|
||||
if (item.InsertedElement != null && inkCanvas.Children.Contains(item.InsertedElement))
|
||||
inkCanvas.Children.Remove(item.InsertedElement);
|
||||
|
||||
@@ -21,6 +21,46 @@ namespace Ink_Canvas {
|
||||
private Point centerPoint = new Point(0, 0);
|
||||
private InkCanvasEditingMode lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
|
||||
|
||||
/// <summary>
|
||||
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
||||
/// </summary>
|
||||
private List<UIElement> PreserveNonStrokeElements()
|
||||
{
|
||||
var preservedElements = new List<UIElement>();
|
||||
|
||||
// 遍历inkCanvas的所有子元素
|
||||
for (int i = inkCanvas.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var child = inkCanvas.Children[i];
|
||||
|
||||
// 保存图片、媒体元素等非笔画相关的UI元素
|
||||
if (child is Image || child is MediaElement ||
|
||||
(child is Border border && border.Name != "AdvancedEraserOverlay"))
|
||||
{
|
||||
preservedElements.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
return preservedElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复之前保存的非笔画元素到画布
|
||||
/// </summary>
|
||||
private void RestoreNonStrokeElements(List<UIElement> preservedElements)
|
||||
{
|
||||
if (preservedElements == null) return;
|
||||
|
||||
foreach (var element in preservedElements)
|
||||
{
|
||||
// 确保元素没有父容器再添加到inkCanvas
|
||||
if (element is FrameworkElement fe && fe.Parent == null)
|
||||
{
|
||||
inkCanvas.Children.Add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BorderMultiTouchMode_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
if (isInMultiTouchMode) {
|
||||
inkCanvas.StylusDown -= MainWindow_StylusDown;
|
||||
@@ -31,12 +71,16 @@ namespace Ink_Canvas {
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint) {
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = false;
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
|
||||
inkCanvas.StylusDown += MainWindow_StylusDown;
|
||||
inkCanvas.StylusMove += MainWindow_StylusMove;
|
||||
inkCanvas.StylusUp += MainWindow_StylusUp;
|
||||
@@ -45,7 +89,11 @@ namespace Ink_Canvas {
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint) {
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = true;
|
||||
}
|
||||
}
|
||||
@@ -118,7 +166,12 @@ namespace Ink_Canvas {
|
||||
VisualCanvasList.Remove(e.StylusDevice.Id);
|
||||
TouchDownPointsList.Remove(e.StylusDevice.Id);
|
||||
if (StrokeVisualList.Count == 0 || VisualCanvasList.Count == 0 || TouchDownPointsList.Count == 0) {
|
||||
inkCanvas.Children.Clear();
|
||||
// 只清除手写笔预览相关的Canvas,不清除所有子元素
|
||||
foreach (var canvas in VisualCanvasList.Values.ToList()) {
|
||||
if (inkCanvas.Children.Contains(canvas)) {
|
||||
inkCanvas.Children.Remove(canvas);
|
||||
}
|
||||
}
|
||||
StrokeVisualList.Clear();
|
||||
VisualCanvasList.Clear();
|
||||
TouchDownPointsList.Clear();
|
||||
@@ -446,7 +499,11 @@ namespace Ink_Canvas {
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = false;
|
||||
// 关闭多指书写时,恢复手掌擦开关
|
||||
if (palmEraserWasEnabledBeforeMultiTouch) {
|
||||
@@ -471,7 +528,11 @@ namespace Ink_Canvas {
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = true;
|
||||
// 启用多指书写时,自动禁用手掌擦
|
||||
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.4.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.4.0")]
|
||||
[assembly: AssemblyVersion("1.7.4.1")]
|
||||
[assembly: AssemblyFileVersion("1.7.4.1")]
|
||||
|
||||
@@ -87,6 +87,8 @@ namespace Ink_Canvas
|
||||
public bool IsCompressPicturesUploaded { get; set; } = false;
|
||||
[JsonProperty("enablePalmEraser")]
|
||||
public bool EnablePalmEraser { get; set; } = true;
|
||||
[JsonProperty("clearCanvasAlsoClearImages")]
|
||||
public bool ClearCanvasAlsoClearImages { get; set; } = true;
|
||||
}
|
||||
|
||||
public enum OptionalOperation
|
||||
|
||||
@@ -92,6 +92,9 @@
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MKStoler1024"><img src="https://avatars.githubusercontent.com/u/158786854?v=4?s=100" width="100px;" alt="MKStoler1024"/><br /><sub><b>MKStoler1024</b></sub></a><br /><a href="#doc-MKStoler1024" title="Documentation">📖</a> <a href="#code-MKStoler1024" title="Code">💻</a> <a href="#design-MKStoler1024" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/awesome-iwb"><img src="https://avatars.githubusercontent.com/u/184760810?v=4?s=100" width="100px;" alt="Awesome Iwb"/><br /><sub><b>Awesome Iwb</b></sub></a><br /><a href="#doc-awesome-iwb" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PrefacedCorg"><img src="https://avatars.githubusercontent.com/u/129855423?v=4?s=100" width="100px;" alt="PrefacedCorg"/><br /><sub><b>PrefacedCorg</b></sub></a><br /><a href="#code-PrefacedCorg" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user