提交初始文件
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class AnimationsHelper
|
||||
{
|
||||
public static void ShowWithFadeIn(UIElement element, double duration = 0.15)
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeInAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0.5,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
sb.Children.Add(fadeInAnimation);
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
|
||||
public static void ShowWithSlideFromBottomAndFade(UIElement element, double duration = 0.15)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeInAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0.5,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
fadeInAnimation.EasingFunction = new CubicEase();
|
||||
|
||||
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
// 滑动动画
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = element.RenderTransform.Value.OffsetY + 10, // 滑动距离
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
||||
|
||||
slideAnimation.EasingFunction = new CubicEase();
|
||||
|
||||
sb.Children.Add(fadeInAnimation);
|
||||
sb.Children.Add(slideAnimation);
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
element.RenderTransform = new TranslateTransform();
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static void ShowWithSlideFromLeftAndFade(UIElement element, double duration = 0.25)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeInAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0.5,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
// 滑动动画
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = element.RenderTransform.Value.OffsetX - 20, // 滑动距离
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
|
||||
|
||||
sb.Children.Add(fadeInAnimation);
|
||||
sb.Children.Add(slideAnimation);
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
element.RenderTransform = new TranslateTransform();
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static void ShowWithScaleFromLeft(UIElement element, double duration = 0.2)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 水平方向的缩放动画
|
||||
var scaleXAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)"));
|
||||
|
||||
// 垂直方向的缩放动画
|
||||
var scaleYAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
scaleYAnimation.EasingFunction = new CubicEase();
|
||||
scaleXAnimation.EasingFunction = new CubicEase();
|
||||
Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));
|
||||
|
||||
sb.Children.Add(scaleXAnimation);
|
||||
sb.Children.Add(scaleYAnimation);
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
element.RenderTransformOrigin = new Point(0, 0.5); // 左侧中心点为基准
|
||||
element.RenderTransform = new ScaleTransform(0, 0);
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static void ShowWithScaleFromRight(UIElement element, double duration = 0.2)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 水平方向的缩放动画
|
||||
var scaleXAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)"));
|
||||
|
||||
// 垂直方向的缩放动画
|
||||
var scaleYAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));
|
||||
|
||||
scaleYAnimation.EasingFunction = new CubicEase();
|
||||
scaleXAnimation.EasingFunction = new CubicEase();
|
||||
|
||||
sb.Children.Add(scaleXAnimation);
|
||||
sb.Children.Add(scaleYAnimation);
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
element.RenderTransformOrigin = new Point(1, 0.5); // 右侧中心点为基准
|
||||
element.RenderTransform = new ScaleTransform(0, 0);
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static void HideWithSlideAndFade(UIElement element, double duration = 0.15)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Collapsed) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeOutAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 1,
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
fadeOutAnimation.EasingFunction = new CubicEase();
|
||||
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
// 滑动动画
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = element.RenderTransform.Value.OffsetY + 10, // 滑动距离
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
slideAnimation.EasingFunction = new CubicEase();
|
||||
|
||||
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
||||
|
||||
sb.Children.Add(fadeOutAnimation);
|
||||
sb.Children.Add(slideAnimation);
|
||||
|
||||
sb.Completed += (s, e) =>
|
||||
{
|
||||
element.Visibility = Visibility.Collapsed;
|
||||
};
|
||||
|
||||
element.RenderTransform = new TranslateTransform();
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static void HideWithFadeOut(UIElement element, double duration = 0.15)
|
||||
{
|
||||
if (element.Visibility == Visibility.Collapsed) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeOutAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 1,
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
sb.Children.Add(fadeOutAnimation);
|
||||
|
||||
sb.Completed += (s, e) =>
|
||||
{
|
||||
element.Visibility = Visibility.Collapsed;
|
||||
};
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class AutoUpdateHelper
|
||||
{
|
||||
public static async Task<string> CheckForUpdates(string proxy = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
string localVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
string remoteAddress = proxy;
|
||||
remoteAddress += "https://gitea.bliemhax.com/kriastans/InkCanvasForClass/raw/branch/master/AutomaticUpdateVersionControl.txt";
|
||||
string remoteVersion = await GetRemoteVersion(remoteAddress);
|
||||
|
||||
if (remoteVersion != null)
|
||||
{
|
||||
Version local = new Version(localVersion);
|
||||
Version remote = new Version(remoteVersion);
|
||||
if (remote > local)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | New version Availble: " + remoteVersion);
|
||||
return remoteVersion;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("Failed to retrieve remote version.", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"AutoUpdate | Error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> GetRemoteVersion(string fileUrl)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await client.GetAsync(fileUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | HTTP request error: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Error: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string updatesFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Ink Canvas Annotation", "AutoUpdate");
|
||||
private static string statusFilePath = null;
|
||||
|
||||
public static async Task<bool> DownloadSetupFileAndSaveStatus(string version, string proxy = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{version}Status.txt");
|
||||
|
||||
if (File.Exists(statusFilePath) && File.ReadAllText(statusFilePath).Trim().ToLower() == "true")
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Setup file already downloaded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
string downloadUrl = $"{proxy}https://github.com/ChangSakura/Ink-Canvas/releases/download/v{version}/Ink.Canvas.Annotation.V{version}.Setup.exe";
|
||||
|
||||
SaveDownloadStatus(false);
|
||||
await DownloadFile(downloadUrl, $"{updatesFolderPath}\\Ink.Canvas.Annotation.V{version}.Setup.exe");
|
||||
SaveDownloadStatus(true);
|
||||
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Setup file successfully downloaded.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Error downloading and installing update: {ex.Message}", LogHelper.LogType.Error);
|
||||
|
||||
SaveDownloadStatus(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task DownloadFile(string fileUrl, string destinationPath)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await client.GetAsync(fileUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using (FileStream fileStream = File.Create(destinationPath))
|
||||
{
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
fileStream.Close();
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Console.WriteLine($"AutoUpdate | HTTP request error: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"AutoUpdate | Error: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveDownloadStatus(bool isSuccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (statusFilePath == null) return;
|
||||
|
||||
string directory = Path.GetDirectoryName(statusFilePath);
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.WriteAllText(statusFilePath, isSuccess.ToString());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Error saving download status: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InstallNewVersionApp(string version, bool isInSilence)
|
||||
{
|
||||
try
|
||||
{
|
||||
string setupFilePath = Path.Combine(updatesFolderPath, $"Ink.Canvas.Annotation.V{version}.Setup.exe");
|
||||
|
||||
if (!File.Exists(setupFilePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Setup file not found: {setupFilePath}", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
string InstallCommand = $"\"{setupFilePath}\" /SILENT";
|
||||
if (isInSilence) InstallCommand += " /VERYSILENT";
|
||||
ExecuteCommandLine(InstallCommand);
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Error installing update: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void ExecuteCommandLine(string command)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessStartInfo processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/c {command}",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (Process process = new Process { StartInfo = processStartInfo })
|
||||
{
|
||||
process.Start();
|
||||
Application.Current.Shutdown();
|
||||
/*process.WaitForExit();
|
||||
int exitCode = process.ExitCode;*/
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static void DeleteUpdatesFolder()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(updatesFolderPath))
|
||||
{
|
||||
Directory.Delete(updatesFolderPath, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate clearing| Error deleting updates folder: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class AutoUpdateWithSilenceTimeComboBox
|
||||
{
|
||||
public static ObservableCollection<string> Hours { get; set; } = new ObservableCollection<string>();
|
||||
public static ObservableCollection<string> Minutes { get; set; } = new ObservableCollection<string>();
|
||||
|
||||
public static void InitializeAutoUpdateWithSilenceTimeComboBoxOptions(ComboBox startTimeComboBox, ComboBox endTimeComboBox)
|
||||
{
|
||||
for (int hour = 0; hour <= 23; ++hour)
|
||||
{
|
||||
Hours.Add(hour.ToString("00"));
|
||||
}
|
||||
for (int minute = 0; minute <= 59; minute += 20)
|
||||
{
|
||||
Minutes.Add(minute.ToString("00"));
|
||||
}
|
||||
startTimeComboBox.ItemsSource = Hours.SelectMany(h => Minutes.Select(m => $"{h}:{m}"));
|
||||
endTimeComboBox.ItemsSource = Hours.SelectMany(h => Minutes.Select(m => $"{h}:{m}"));
|
||||
}
|
||||
|
||||
public static bool CheckIsInSilencePeriod(string startTime, string endTime)
|
||||
{
|
||||
if (startTime == endTime) return true;
|
||||
DateTime currentTime = DateTime.Now;
|
||||
|
||||
DateTime StartTime = DateTime.ParseExact(startTime, "HH:mm", null);
|
||||
DateTime EndTime = DateTime.ParseExact(endTime, "HH:mm", null);
|
||||
if (StartTime <= EndTime)
|
||||
{ // 单日时间段
|
||||
return currentTime >= StartTime && currentTime <= EndTime;
|
||||
}
|
||||
else
|
||||
{ // 跨越两天的时间段
|
||||
return currentTime >= StartTime || currentTime <= EndTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Ink_Canvas.Converter
|
||||
{
|
||||
public class BooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value == true)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value == true)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
public class VisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
Visibility visibility = (Visibility)value;
|
||||
if (visibility == Visibility.Visible)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
Visibility visibility = (Visibility)value;
|
||||
if (visibility == Visibility.Visible)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IntNumberToString : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "无限制";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "人";
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "无限制";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "人";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IntNumberToString2 : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "自动截图";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "条";
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "自动截图";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "条";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IsEnabledToOpacityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
bool isChecked = (bool)value;
|
||||
if (isChecked == true)
|
||||
{
|
||||
return 1d;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.35;
|
||||
}
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class DelAutoSavedFiles {
|
||||
public static void DeleteFilesOlder(string directoryPath, int daysThreshold) {
|
||||
string[] extensionsToDel = { ".icstk", ".png" };
|
||||
if (Directory.Exists(directoryPath)) {
|
||||
// 获取目录中的所有子目录
|
||||
string[] subDirectories = Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories);
|
||||
foreach (string subDirectory in subDirectories) {
|
||||
try {
|
||||
// 获取子目录下的所有文件
|
||||
string[] files = Directory.GetFiles(subDirectory);
|
||||
foreach (string filePath in files) {
|
||||
// 获取文件的创建日期
|
||||
DateTime creationDate = File.GetCreationTime(filePath);
|
||||
// 获取文件的扩展名
|
||||
string fileExtension = Path.GetExtension(filePath);
|
||||
// 如果文件的创建日期早于指定天数且是要删除的扩展名,则删除文件
|
||||
if (creationDate < DateTime.Now.AddDays(-daysThreshold)) {
|
||||
if (Array.Exists(extensionsToDel, ext => ext.Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
|| Path.GetFileName(filePath).Equals("Position", StringComparison.OrdinalIgnoreCase)) {
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex.ToString(), LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
try { // 递归删除空文件夹
|
||||
DeleteEmptyFolders(directoryPath);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex.ToString(), LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteEmptyFolders(string directoryPath) {
|
||||
foreach (string dir in Directory.GetDirectories(directoryPath)) {
|
||||
DeleteEmptyFolders(dir);
|
||||
if (Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0) {
|
||||
Directory.Delete(dir, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class DelayAction
|
||||
{
|
||||
Timer _timerDebounce;
|
||||
|
||||
/// <summary>
|
||||
/// 防抖函式
|
||||
/// </summary>
|
||||
/// <param name="inv">同步的對象,一般傳入控件,不需要可null</param>
|
||||
public void DebounceAction(int timeMs, ISynchronizeInvoke inv, Action action)
|
||||
{
|
||||
lock (this) {
|
||||
if (_timerDebounce == null) {
|
||||
_timerDebounce = new Timer(timeMs) { AutoReset = false };
|
||||
_timerDebounce.Elapsed += (o, e) => {
|
||||
_timerDebounce.Stop(); _timerDebounce.Close(); _timerDebounce = null;
|
||||
InvokeAction(action, inv);
|
||||
};
|
||||
}
|
||||
_timerDebounce.Stop();
|
||||
_timerDebounce.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void InvokeAction(Action action, ISynchronizeInvoke inv)
|
||||
{
|
||||
if (inv == null)
|
||||
{
|
||||
action();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inv.InvokeRequired)
|
||||
{
|
||||
inv.Invoke(action, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualBasic;
|
||||
using System.Collections;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
|
||||
public static class EdgeGestureUtil
|
||||
{
|
||||
|
||||
private static Guid DISABLE_TOUCH_SCREEN = new Guid("32CE38B2-2C9A-41B1-9BC5-B3784394AA44");
|
||||
private static Guid IID_PROPERTY_STORE = new Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99");
|
||||
|
||||
private static short VT_BOOL = 11;
|
||||
#region "Structures"
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct PropertyKey
|
||||
{
|
||||
public PropertyKey(Guid guid, UInt32 pid)
|
||||
{
|
||||
fmtid = guid;
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
[MarshalAs(UnmanagedType.Struct)]
|
||||
public Guid fmtid;
|
||||
public uint pid;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct PropVariant
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public short vt;
|
||||
[FieldOffset(2)]
|
||||
private short wReserved1;
|
||||
[FieldOffset(4)]
|
||||
private short wReserved2;
|
||||
[FieldOffset(6)]
|
||||
private short wReserved3;
|
||||
[FieldOffset(8)]
|
||||
private sbyte cVal;
|
||||
[FieldOffset(8)]
|
||||
private byte bVal;
|
||||
[FieldOffset(8)]
|
||||
private short iVal;
|
||||
[FieldOffset(8)]
|
||||
public ushort uiVal;
|
||||
[FieldOffset(8)]
|
||||
private int lVal;
|
||||
[FieldOffset(8)]
|
||||
private uint ulVal;
|
||||
[FieldOffset(8)]
|
||||
private int intVal;
|
||||
[FieldOffset(8)]
|
||||
private uint uintVal;
|
||||
[FieldOffset(8)]
|
||||
private long hVal;
|
||||
[FieldOffset(8)]
|
||||
private long uhVal;
|
||||
[FieldOffset(8)]
|
||||
private float fltVal;
|
||||
[FieldOffset(8)]
|
||||
private double dblVal;
|
||||
[FieldOffset(8)]
|
||||
public bool boolVal;
|
||||
[FieldOffset(8)]
|
||||
private int scode;
|
||||
[FieldOffset(8)]
|
||||
private DateTime date;
|
||||
[FieldOffset(8)]
|
||||
private System.Runtime.InteropServices.ComTypes.FILETIME filetime;
|
||||
|
||||
[FieldOffset(8)]
|
||||
private Blob blobVal;
|
||||
[FieldOffset(8)]
|
||||
private IntPtr pwszVal;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to gets blob data
|
||||
/// </summary>
|
||||
private byte[] GetBlob()
|
||||
{
|
||||
byte[] Result = new byte[blobVal.Length];
|
||||
Marshal.Copy(blobVal.Data, Result, 0, Result.Length);
|
||||
return Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Property value
|
||||
/// </summary>
|
||||
public object Value
|
||||
{
|
||||
get
|
||||
{
|
||||
VarEnum ve = (VarEnum)vt;
|
||||
switch (ve)
|
||||
{
|
||||
case VarEnum.VT_I1:
|
||||
return bVal;
|
||||
case VarEnum.VT_I2:
|
||||
return iVal;
|
||||
case VarEnum.VT_I4:
|
||||
return lVal;
|
||||
case VarEnum.VT_I8:
|
||||
return hVal;
|
||||
case VarEnum.VT_INT:
|
||||
return iVal;
|
||||
case VarEnum.VT_UI4:
|
||||
return ulVal;
|
||||
case VarEnum.VT_LPWSTR:
|
||||
return Marshal.PtrToStringUni(pwszVal);
|
||||
case VarEnum.VT_BLOB:
|
||||
return GetBlob();
|
||||
}
|
||||
throw new NotImplementedException("PropVariant " + ve.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct Blob
|
||||
{
|
||||
public int Length;
|
||||
|
||||
public IntPtr Data;
|
||||
//Code Should Compile at warning level4 without any warnings,
|
||||
//However this struct will give us Warning CS0649: Field [Fieldname]
|
||||
//is never assigned to, and will always have its default value
|
||||
//You can disable CS0649 in the project options but that will disable
|
||||
//the warning for the whole project, it's a nice warning and we do want
|
||||
//it in other places so we make a nice dummy function to keep the compiler
|
||||
//happy.
|
||||
// 代码应该在警告级别 4 下编译而不会出现任何警告,但是此结构将给出警告 CS0649:字段 [Fieldname] 从未分配,并且始终具有其默认值。您可以在项目选项中禁用 CS0649,但这将禁用整个项目的警告,这是一个很好的警告,我们确实希望它在其他地方,所以我们制作了一个不错的虚拟函数来让编译器满意。
|
||||
private void FixCS0649()
|
||||
{
|
||||
Length = 0;
|
||||
Data = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Interfaces"
|
||||
|
||||
[ComImport(), Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IPropertyStore
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetCount([Out(), In()] ref uint cProps);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetAt([In()] uint iProp, ref PropertyKey pkey);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetValue([In()] ref PropertyKey key, ref PropVariant pv);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void SetValue([In()] ref PropertyKey key, [In()] ref PropVariant pv);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void Commit();
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void Release();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods"
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
private static extern int SHGetPropertyStoreForWindow(IntPtr handle, ref Guid riid, ref IPropertyStore propertyStore);
|
||||
|
||||
public static void DisableEdgeGestures(IntPtr hwnd, bool enable)
|
||||
{
|
||||
IPropertyStore pPropStore = null;
|
||||
int hr = 0;
|
||||
hr = SHGetPropertyStoreForWindow(hwnd, ref IID_PROPERTY_STORE, ref pPropStore);
|
||||
if (hr == 0)
|
||||
{
|
||||
PropertyKey propKey = new PropertyKey();
|
||||
propKey.fmtid = DISABLE_TOUCH_SCREEN;
|
||||
propKey.pid = 2;
|
||||
PropVariant var = new PropVariant();
|
||||
var.vt = VT_BOOL;
|
||||
var.boolVal = enable;
|
||||
pPropStore.SetValue(ref propKey, ref var);
|
||||
Marshal.FinalReleaseComObject(pPropStore);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class ForegroundWindowInfo
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT {
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
public int Width => Right - Left;
|
||||
public int Height => Bottom - Top;
|
||||
}
|
||||
|
||||
public static string WindowTitle() {
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
const int nChars = 256;
|
||||
StringBuilder windowTitle = new StringBuilder(nChars);
|
||||
GetWindowText(foregroundWindowHandle, windowTitle, nChars);
|
||||
|
||||
return windowTitle.ToString();
|
||||
}
|
||||
|
||||
public static string WindowClassName() {
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
const int nChars = 256;
|
||||
StringBuilder className = new StringBuilder(nChars);
|
||||
GetClassName(foregroundWindowHandle, className, nChars);
|
||||
|
||||
return className.ToString();
|
||||
}
|
||||
|
||||
public static RECT WindowRect() {
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
RECT windowRect;
|
||||
GetWindowRect(foregroundWindowHandle, out windowRect);
|
||||
|
||||
return windowRect;
|
||||
}
|
||||
|
||||
public static string ProcessName() {
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
uint processId;
|
||||
GetWindowThreadProcessId(foregroundWindowHandle, out processId);
|
||||
|
||||
try {
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
return process.ProcessName;
|
||||
} catch (ArgumentException) {
|
||||
// Process with the given ID not found
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static string ProcessPath()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
uint processId;
|
||||
GetWindowThreadProcessId(foregroundWindowHandle, out processId);
|
||||
|
||||
try
|
||||
{
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
return process.MainModule.FileName;
|
||||
}
|
||||
catch {
|
||||
// Process with the given ID not found
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
// 由衷感謝 lindexi 提供的 《WPF 稳定的全屏化窗口方法》
|
||||
// 文章鏈接:https://blog.lindexi.com/post/WPF-%E7%A8%B3%E5%AE%9A%E7%9A%84%E5%85%A8%E5%B1%8F%E5%8C%96%E7%AA%97%E5%8F%A3%E6%96%B9%E6%B3%95.html
|
||||
// lindexi 的部落格:https://blog.lindexi.com/
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static partial class FullScreenHelper
|
||||
{
|
||||
static class Win32
|
||||
{
|
||||
[Flags]
|
||||
public enum ShowWindowCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximizes the specified window.
|
||||
/// </summary>
|
||||
SW_MAXIMIZE = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Activates and displays the window. If the window is minimized or maximized, the system restores it to its original
|
||||
/// size and position. An application should specify this flag when restoring a minimized window.
|
||||
/// </summary>
|
||||
SW_RESTORE = 9,
|
||||
}
|
||||
|
||||
|
||||
internal static class Properties
|
||||
{
|
||||
#if !ANSI
|
||||
public const CharSet BuildCharSet = CharSet.Unicode;
|
||||
#else
|
||||
public const CharSet BuildCharSet = CharSet.Ansi;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static class Dwmapi
|
||||
{
|
||||
public const string LibraryName = "Dwmapi.dll";
|
||||
|
||||
[DllImport(LibraryName, ExactSpelling = true, PreserveSig = false)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool DwmIsCompositionEnabled();
|
||||
|
||||
[DllImport("Dwmapi.dll", ExactSpelling = true, SetLastError = true)]
|
||||
public static extern int DwmSetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE dwAttribute,
|
||||
in int pvAttribute, uint cbAttribute);
|
||||
}
|
||||
|
||||
public static class User32
|
||||
{
|
||||
public const string LibraryName = "user32";
|
||||
|
||||
[DllImport(LibraryName, CharSet = Properties.BuildCharSet)]
|
||||
public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfo lpmi);
|
||||
|
||||
[DllImport(LibraryName, ExactSpelling = true)]
|
||||
public static extern IntPtr MonitorFromRect(in Rectangle lprc, MonitorFlag dwFlags);
|
||||
|
||||
[DllImport(LibraryName, ExactSpelling = true)]
|
||||
public static extern bool IsIconic(IntPtr hwnd);
|
||||
|
||||
[DllImport(LibraryName, ExactSpelling = true)]
|
||||
public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommands nCmdShow);
|
||||
|
||||
[DllImport(LibraryName, ExactSpelling = true)]
|
||||
public static extern bool SetWindowPlacement(IntPtr hWnd,
|
||||
[In] ref WINDOWPLACEMENT lpwndpl);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
[DllImport(LibraryName, ExactSpelling = true)]
|
||||
public static extern bool GetWindowRect(IntPtr hWnd, out Rectangle lpRect);
|
||||
|
||||
[DllImport(LibraryName, ExactSpelling = true, SetLastError = true)]
|
||||
public static extern Int32 SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, Int32 x, Int32 y, Int32 cx,
|
||||
Int32 cy, Int32 wFlagslong);
|
||||
|
||||
[DllImport(LibraryName, ExactSpelling = true)]
|
||||
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
|
||||
|
||||
public static IntPtr GetWindowLongPtr(IntPtr hWnd, GetWindowLongFields nIndex) =>
|
||||
GetWindowLongPtr(hWnd, (int) nIndex);
|
||||
|
||||
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
|
||||
{
|
||||
return IntPtr.Size > 4
|
||||
#pragma warning disable CS0618 // 类型或成员已过时
|
||||
? GetWindowLongPtr_x64(hWnd, nIndex)
|
||||
: new IntPtr(GetWindowLong(hWnd, nIndex));
|
||||
#pragma warning restore CS0618 // 类型或成员已过时
|
||||
}
|
||||
|
||||
[DllImport(LibraryName, CharSet = Properties.BuildCharSet)]
|
||||
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport(LibraryName, CharSet = Properties.BuildCharSet, EntryPoint = "GetWindowLongPtr")]
|
||||
public static extern IntPtr GetWindowLongPtr_x64(IntPtr hWnd, int nIndex);
|
||||
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, GetWindowLongFields nIndex, IntPtr dwNewLong) =>
|
||||
SetWindowLongPtr(hWnd, (int) nIndex, dwNewLong);
|
||||
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
return IntPtr.Size > 4
|
||||
#pragma warning disable CS0618 // 类型或成员已过时
|
||||
? SetWindowLongPtr_x64(hWnd, nIndex, dwNewLong)
|
||||
: new IntPtr(SetWindowLong(hWnd, nIndex, dwNewLong.ToInt32()));
|
||||
#pragma warning restore CS0618 // 类型或成员已过时
|
||||
}
|
||||
|
||||
[DllImport(LibraryName, CharSet = Properties.BuildCharSet, EntryPoint = "SetWindowLongPtr")]
|
||||
public static extern IntPtr SetWindowLongPtr_x64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
[DllImport(LibraryName, CharSet = Properties.BuildCharSet)]
|
||||
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct MonitorInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the structure, in bytes.
|
||||
/// </summary>
|
||||
public uint Size;
|
||||
|
||||
/// <summary>
|
||||
/// A RECT structure that specifies the display monitor rectangle, expressed in virtual-screen coordinates. Note that
|
||||
/// if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
|
||||
/// </summary>
|
||||
public Rectangle MonitorRect;
|
||||
|
||||
/// <summary>
|
||||
/// A RECT structure that specifies the work area rectangle of the display monitor, expressed in virtual-screen
|
||||
/// coordinates. Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may
|
||||
/// be negative values.
|
||||
/// </summary>
|
||||
public Rectangle WorkRect;
|
||||
|
||||
/// <summary>
|
||||
/// A set of flags that represent attributes of the display monitor.
|
||||
/// </summary>
|
||||
public MonitorInfoFlag Flags;
|
||||
}
|
||||
|
||||
enum MonitorInfoFlag
|
||||
{
|
||||
}
|
||||
|
||||
enum MonitorFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a handle to the primary display monitor.
|
||||
/// </summary>
|
||||
MONITOR_DEFAULTTOPRIMARY = 1,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct WindowPosition
|
||||
{
|
||||
public IntPtr Hwnd;
|
||||
public IntPtr HwndZOrderInsertAfter;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public WindowPositionFlags Flags;
|
||||
}
|
||||
|
||||
enum HwndZOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// Places the window at the top of the Z order.
|
||||
/// </summary>
|
||||
HWND_TOP = 0,
|
||||
HWND_TOPMOST = -1,
|
||||
}
|
||||
|
||||
enum DWMWINDOWATTRIBUTE : uint
|
||||
{
|
||||
DWMWA_TRANSITIONS_FORCEDISABLED = 3,
|
||||
}
|
||||
|
||||
enum GetWindowLongFields
|
||||
{
|
||||
/// <summary>
|
||||
/// 设定一个新的窗口风格
|
||||
/// Retrieves the window styles
|
||||
/// </summary>
|
||||
GWL_STYLE = -16,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct WINDOWPLACEMENT // WindowPlacement
|
||||
{
|
||||
public uint Size;
|
||||
public WindowPlacementFlags Flags;
|
||||
public Win32.ShowWindowCommands ShowCmd;
|
||||
public Point MinPosition;
|
||||
public Point MaxPosition;
|
||||
public Rectangle NormalPosition;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum WindowPositionFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 清除客户区的所有内容。如果未设置该标志,客户区的有效内容被保存并且在窗口尺寸更新和重定位后拷贝回客户区
|
||||
/// </para>
|
||||
/// Discards the entire contents of the client area. If this flag is not specified, the valid contents of the client
|
||||
/// area are saved and copied back into the client area after the window is sized or repositioned.
|
||||
/// </summary>
|
||||
SWP_NOCOPYBITS = 0x0100,
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 维持当前位置(忽略X和Y参数)
|
||||
/// </para>
|
||||
/// Retains the current position (ignores X and Y parameters).
|
||||
/// </summary>
|
||||
SWP_NOMOVE = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 不重画改变的内容。如果设置了这个标志,则不发生任何重画动作。适用于客户区和非客户区(包括标题栏和滚动条)和任何由于窗回移动而露出的父窗口的所有部分。如果设置了这个标志,应用程序必须明确地使窗口无效并区重画窗口的任何部分和父窗口需要重画的部分
|
||||
/// </para>
|
||||
/// Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to the client area,
|
||||
/// the nonclient area (including the title bar and scroll bars), and any part of the parent window uncovered as a
|
||||
/// result of the window being moved. When this flag is set, the application must explicitly invalidate or redraw any
|
||||
/// parts of the window and parent window that need redrawing.
|
||||
/// </summary>
|
||||
SWP_NOREDRAW = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 维持当前尺寸(忽略 cx 和 cy 参数)
|
||||
/// </para>
|
||||
/// Retains the current size (ignores the cx and cy parameters).
|
||||
/// </summary>
|
||||
SWP_NOSIZE = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 维持当前 Z 序(忽略 hWndlnsertAfter 参数)
|
||||
/// </para>
|
||||
/// Retains the current Z order (ignores the hWndInsertAfter parameter).
|
||||
/// </summary>
|
||||
SWP_NOZORDER = 0x0004,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct Rectangle
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
/// <summary>
|
||||
/// 矩形的宽度
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
get { return unchecked((int) (Right - Left)); }
|
||||
set { Right = unchecked((int) (Left + value)); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 矩形的高度
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
get { return unchecked((int) (Bottom - Top)); }
|
||||
set { Bottom = unchecked((int) (Top + value)); }
|
||||
}
|
||||
|
||||
public bool Equals(Rectangle other)
|
||||
{
|
||||
return (Left == other.Left) && (Right == other.Right) && (Top == other.Top) && (Bottom == other.Bottom);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Rectangle rectangle && Equals(rectangle);
|
||||
}
|
||||
|
||||
public static bool operator ==(Rectangle left, Rectangle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (int) Left;
|
||||
hashCode = (hashCode * 397) ^ (int) Top;
|
||||
hashCode = (hashCode * 397) ^ (int) Right;
|
||||
hashCode = (hashCode * 397) ^ (int) Bottom;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator !=(Rectangle left, Rectangle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct Point
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum WindowPlacementFlags
|
||||
{
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum WindowStyles
|
||||
{
|
||||
/// <summary>
|
||||
/// The window is initially maximized.
|
||||
/// </summary>
|
||||
WS_MAXIMIZE = 0x01000000,
|
||||
|
||||
/// <summary>
|
||||
/// The window has a maximize button. Cannot be combined with the WS_EX_CONTEXTHELP style. The WS_SYSMENU style must
|
||||
/// also be specified.
|
||||
/// </summary>
|
||||
WS_MAXIMIZEBOX = 0x00010000,
|
||||
|
||||
/// <summary>
|
||||
/// The window is initially minimized. Same as the WS_ICONIC style.
|
||||
/// </summary>
|
||||
WS_MINIMIZE = 0x20000000,
|
||||
|
||||
/// <summary>
|
||||
/// The window has a sizing border. Same as the WS_SIZEBOX style.
|
||||
/// </summary>
|
||||
WS_THICKFRAME = 0x00040000,
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("602D4995-B13A-429b-A66E-1935E44F4317")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface ITaskbarList2
|
||||
{
|
||||
[PreserveSig]
|
||||
int HrInit();
|
||||
|
||||
[PreserveSig]
|
||||
int AddTab(IntPtr hwnd);
|
||||
|
||||
[PreserveSig]
|
||||
int DeleteTab(IntPtr hwnd);
|
||||
|
||||
[PreserveSig]
|
||||
int ActivateTab(IntPtr hwnd);
|
||||
|
||||
[PreserveSig]
|
||||
int SetActiveAlt(IntPtr hwnd);
|
||||
|
||||
[PreserveSig]
|
||||
int MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
||||
// 由衷感謝 lindexi 提供的 《WPF 稳定的全屏化窗口方法》
|
||||
// 文章鏈接:https://blog.lindexi.com/post/WPF-%E7%A8%B3%E5%AE%9A%E7%9A%84%E5%85%A8%E5%B1%8F%E5%8C%96%E7%AA%97%E5%8F%A3%E6%96%B9%E6%B3%95.html
|
||||
// lindexi 的部落格:https://blog.lindexi.com/
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 用来使窗口变得全屏的辅助类
|
||||
/// 采用设置窗口位置和尺寸,确保盖住整个屏幕的方式来实现全屏
|
||||
/// 目前已知需要满足的条件是:窗口盖住整个屏幕、窗口没有WS_THICKFRAME样式、窗口不能有标题栏且最大化
|
||||
/// </summary>
|
||||
public static partial class FullScreenHelper
|
||||
{
|
||||
public static void MarkFullscreenWindowTaskbarList(IntPtr hwnd, bool isFullscreen)
|
||||
{
|
||||
try
|
||||
{
|
||||
var CLSID_TaskbarList = new Guid("56FDF344-FD6D-11D0-958A-006097C9A090");
|
||||
var obj = Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_TaskbarList));
|
||||
(obj as ITaskbarList2)?.MarkFullscreenWindow(hwnd, isFullscreen);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//应该不会挂
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于记录窗口全屏前位置的附加属性
|
||||
/// </summary>
|
||||
private static readonly DependencyProperty BeforeFullScreenWindowPlacementProperty =
|
||||
DependencyProperty.RegisterAttached("BeforeFullScreenWindowPlacement", typeof(WINDOWPLACEMENT?),
|
||||
typeof(Window));
|
||||
|
||||
/// <summary>
|
||||
/// 用于记录窗口全屏前样式的附加属性
|
||||
/// </summary>
|
||||
private static readonly DependencyProperty BeforeFullScreenWindowStyleProperty =
|
||||
DependencyProperty.RegisterAttached("BeforeFullScreenWindowStyle", typeof(WindowStyles?), typeof(Window));
|
||||
|
||||
/// <summary>
|
||||
/// 开始进入全屏模式
|
||||
/// 进入全屏模式后,窗口可通过 API 方式(也可以用 Win + Shift + Left/Right)移动,调整大小,但会根据目标矩形寻找显示器重新调整到全屏状态。
|
||||
/// 进入全屏后,不要修改样式等窗口属性,在退出时,会恢复到进入前的状态
|
||||
/// 进入全屏模式后会禁用 DWM 过渡动画
|
||||
/// </summary>
|
||||
/// <param name="window"></param>
|
||||
public static void StartFullScreen(Window window)
|
||||
{
|
||||
if (window == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(window), $"{nameof(window)} 不能为 null");
|
||||
}
|
||||
|
||||
//确保不在全屏模式
|
||||
if (window.GetValue(BeforeFullScreenWindowPlacementProperty) == null &&
|
||||
window.GetValue(BeforeFullScreenWindowStyleProperty) == null)
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(window).EnsureHandle();
|
||||
var hwndSource = HwndSource.FromHwnd(hwnd);
|
||||
|
||||
//获取当前窗口的位置大小状态并保存
|
||||
var placement = new WINDOWPLACEMENT();
|
||||
placement.Size = (uint) Marshal.SizeOf(placement);
|
||||
Win32.User32.GetWindowPlacement(hwnd, ref placement);
|
||||
window.SetValue(BeforeFullScreenWindowPlacementProperty, placement);
|
||||
|
||||
//修改窗口样式
|
||||
var style = (WindowStyles) Win32.User32.GetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE);
|
||||
window.SetValue(BeforeFullScreenWindowStyleProperty, style);
|
||||
//将窗口恢复到还原模式,在有标题栏的情况下最大化模式下无法全屏,
|
||||
//这里采用还原,不修改标题栏的方式
|
||||
//在退出全屏时,窗口原有的状态会恢复
|
||||
//去掉WS_THICKFRAME,在有该样式的情况下不能全屏
|
||||
//去掉WS_MAXIMIZEBOX,禁用最大化,如果最大化会退出全屏
|
||||
//去掉WS_MAXIMIZE,使窗口变成还原状态,不使用ShowWindow(hwnd, ShowWindowCommands.SW_RESTORE),避免看到窗口变成还原状态这一过程(也避免影响窗口的Visible状态)
|
||||
style &= (~(WindowStyles.WS_THICKFRAME | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_MAXIMIZE));
|
||||
Win32.User32.SetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE, (IntPtr) style);
|
||||
|
||||
//禁用 DWM 过渡动画 忽略返回值,若DWM关闭不做处理
|
||||
Win32.Dwmapi.DwmSetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 1,
|
||||
sizeof(int));
|
||||
|
||||
//添加Hook,在窗口尺寸位置等要发生变化时,确保全屏
|
||||
hwndSource.AddHook(KeepFullScreenHook);
|
||||
|
||||
if (Win32.User32.GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
//不能用 placement 的坐标,placement是工作区坐标,不是屏幕坐标。
|
||||
|
||||
//使用窗口当前的矩形调用下设置窗口位置和尺寸的方法,让Hook来进行调整窗口位置和尺寸到全屏模式
|
||||
Win32.User32.SetWindowPos(hwnd, (IntPtr) HwndZOrder.HWND_TOPMOST, rect.Left, rect.Top, rect.Width,
|
||||
rect.Height, (int) WindowPositionFlags.SWP_NOZORDER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退出全屏模式
|
||||
/// 窗口会回到进入全屏模式时保存的状态
|
||||
/// 退出全屏模式后会重新启用 DWM 过渡动画
|
||||
/// </summary>
|
||||
/// <param name="window"></param>
|
||||
public static void EndFullScreen(Window window)
|
||||
{
|
||||
if (window == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(window), $"{nameof(window)} 不能为 null");
|
||||
}
|
||||
|
||||
//确保在全屏模式并获取之前保存的状态
|
||||
if (window.GetValue(BeforeFullScreenWindowPlacementProperty) is WINDOWPLACEMENT placement
|
||||
&& window.GetValue(BeforeFullScreenWindowStyleProperty) is WindowStyles style)
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(window).Handle;
|
||||
|
||||
if (hwnd == IntPtr.Zero)
|
||||
{
|
||||
// 句柄为 0 只有两种情况:
|
||||
// 1. 虽然窗口已进入全屏,但窗口已被关闭;
|
||||
// 2. 窗口初始化前,在还没有调用 StartFullScreen 的前提下就调用了此方法。
|
||||
// 所以,直接 return 就好。
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var hwndSource = HwndSource.FromHwnd(hwnd);
|
||||
|
||||
//去除hook
|
||||
hwndSource.RemoveHook(KeepFullScreenHook);
|
||||
|
||||
//恢复保存的状态
|
||||
//不要改变Style里的WS_MAXIMIZE,否则会使窗口变成最大化状态,但是尺寸不对
|
||||
//也不要设置回Style里的WS_MINIMIZE,否则会导致窗口最小化按钮显示成还原按钮
|
||||
Win32.User32.SetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE,
|
||||
(IntPtr) (style & (~(WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE))));
|
||||
|
||||
if ((style & WindowStyles.WS_MINIMIZE) != 0)
|
||||
{
|
||||
//如果窗口进入全屏前是最小化的,这里不让窗口恢复到之前的最小化状态,而是到还原的状态。
|
||||
//大多数情况下,都不期望在退出全屏的时候,恢复到最小化。
|
||||
placement.ShowCmd = Win32.ShowWindowCommands.SW_RESTORE;
|
||||
}
|
||||
|
||||
if ((style & WindowStyles.WS_MAXIMIZE) != 0)
|
||||
{
|
||||
//提前调用 ShowWindow 使窗口恢复最大化,若通过 SetWindowPlacement 最大化会导致闪烁,只靠其恢复 RestoreBounds.
|
||||
Win32.User32.ShowWindow(hwnd, Win32.ShowWindowCommands.SW_MAXIMIZE);
|
||||
}
|
||||
|
||||
Win32.User32.SetWindowPlacement(hwnd, ref placement);
|
||||
|
||||
if ((style & WindowStyles.WS_MAXIMIZE) ==
|
||||
0) //如果窗口是最大化就不要修改WPF属性,否则会破坏RestoreBounds,且WPF窗口自身在最大化时,不会修改 Left Top Width Height 属性
|
||||
{
|
||||
if (Win32.User32.GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
//不能用 placement 的坐标,placement是工作区坐标,不是屏幕坐标。
|
||||
|
||||
//确保窗口的 WPF 属性与 Win32 位置一致
|
||||
var logicalPos =
|
||||
hwndSource.CompositionTarget.TransformFromDevice.Transform(
|
||||
new System.Windows.Point(rect.Left, rect.Top));
|
||||
var logicalSize =
|
||||
hwndSource.CompositionTarget.TransformFromDevice.Transform(
|
||||
new System.Windows.Point(rect.Width, rect.Height));
|
||||
window.Left = logicalPos.X;
|
||||
window.Top = logicalPos.Y;
|
||||
window.Width = logicalSize.X;
|
||||
window.Height = logicalSize.Y;
|
||||
}
|
||||
}
|
||||
|
||||
//重新启用 DWM 过渡动画 忽略返回值,若DWM关闭不做处理
|
||||
Win32.Dwmapi.DwmSetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 0,
|
||||
sizeof(int));
|
||||
|
||||
//删除保存的状态
|
||||
window.ClearValue(BeforeFullScreenWindowPlacementProperty);
|
||||
window.ClearValue(BeforeFullScreenWindowStyleProperty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保窗口全屏的Hook
|
||||
/// 使用HandleProcessCorruptedStateExceptions,防止访问内存过程中因为一些致命异常导致程序崩溃
|
||||
/// </summary>
|
||||
[HandleProcessCorruptedStateExceptions]
|
||||
private static IntPtr KeepFullScreenHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
//处理WM_WINDOWPOSCHANGING消息
|
||||
const int WINDOWPOSCHANGING = 0x0046;
|
||||
if (msg == WINDOWPOSCHANGING)
|
||||
{
|
||||
try
|
||||
{
|
||||
//得到WINDOWPOS结构体
|
||||
var pos = (WindowPosition) Marshal.PtrToStructure(lParam, typeof(WindowPosition));
|
||||
|
||||
if ((pos.Flags & WindowPositionFlags.SWP_NOMOVE) != 0 &&
|
||||
(pos.Flags & WindowPositionFlags.SWP_NOSIZE) != 0)
|
||||
{
|
||||
//既然你既不改变位置,也不改变尺寸,我就不管了...
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (Win32.User32.IsIconic(hwnd))
|
||||
{
|
||||
// 如果在全屏期间最小化了窗口,那么忽略后续的位置调整。
|
||||
// 否则按后续逻辑,会根据窗口在 -32000 的位置,计算出错误的目标位置,然后就跳到主屏了。
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
//获取窗口现在的矩形,下面用来参考计算目标矩形
|
||||
if (Win32.User32.GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
var targetRect = rect; //窗口想要变化的目标矩形
|
||||
|
||||
if ((pos.Flags & WindowPositionFlags.SWP_NOMOVE) == 0)
|
||||
{
|
||||
//需要移动
|
||||
targetRect.Left = pos.X;
|
||||
targetRect.Top = pos.Y;
|
||||
}
|
||||
|
||||
if ((pos.Flags & WindowPositionFlags.SWP_NOSIZE) == 0)
|
||||
{
|
||||
//要改变尺寸
|
||||
targetRect.Right = targetRect.Left + pos.Width;
|
||||
targetRect.Bottom = targetRect.Top + pos.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
//不改变尺寸
|
||||
targetRect.Right = targetRect.Left + rect.Width;
|
||||
targetRect.Bottom = targetRect.Top + rect.Height;
|
||||
}
|
||||
|
||||
//使用目标矩形获取显示器信息
|
||||
var monitor = Win32.User32.MonitorFromRect(targetRect, MonitorFlag.MONITOR_DEFAULTTOPRIMARY);
|
||||
var info = new MonitorInfo();
|
||||
info.Size = (uint) Marshal.SizeOf(info);
|
||||
if (Win32.User32.GetMonitorInfo(monitor, ref info))
|
||||
{
|
||||
//基于显示器信息设置窗口尺寸位置
|
||||
pos.X = info.MonitorRect.Left;
|
||||
pos.Y = info.MonitorRect.Top;
|
||||
pos.Width = info.MonitorRect.Right - info.MonitorRect.Left;
|
||||
pos.Height = info.MonitorRect.Bottom - info.MonitorRect.Top;
|
||||
pos.Flags &= ~(WindowPositionFlags.SWP_NOSIZE | WindowPositionFlags.SWP_NOMOVE |
|
||||
WindowPositionFlags.SWP_NOREDRAW);
|
||||
pos.Flags |= WindowPositionFlags.SWP_NOCOPYBITS;
|
||||
|
||||
if (rect == info.MonitorRect)
|
||||
{
|
||||
var hwndSource = HwndSource.FromHwnd(hwnd);
|
||||
if (hwndSource?.RootVisual is Window window)
|
||||
{
|
||||
//确保窗口的 WPF 属性与 Win32 位置一致,防止有逗比全屏后改 WPF 的属性,发生一些诡异的行为
|
||||
//下面这样做其实不太好,会再次触发 WM_WINDOWPOSCHANGING 来着.....但是又没有其他时机了
|
||||
// WM_WINDOWPOSCHANGED 不能用
|
||||
//(例如:在进入全屏后,修改 Left 属性,会进入 WM_WINDOWPOSCHANGING,然后在这里将消息里的结构体中的 Left 改回,
|
||||
// 使对 Left 的修改无效,那么将不会进入 WM_WINDOWPOSCHANGED,窗口尺寸正常,但窗口的 Left 属性值错误。)
|
||||
var logicalPos =
|
||||
hwndSource.CompositionTarget.TransformFromDevice.Transform(
|
||||
new System.Windows.Point(pos.X, pos.Y));
|
||||
var logicalSize =
|
||||
hwndSource.CompositionTarget.TransformFromDevice.Transform(
|
||||
new System.Windows.Point(pos.Width, pos.Height));
|
||||
window.Left = logicalPos.X;
|
||||
window.Top = logicalPos.Y;
|
||||
window.Width = logicalSize.X;
|
||||
window.Height = logicalSize.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
//这个hwnd是前面从Window来的,如果现在他不是Window...... 你信么
|
||||
}
|
||||
}
|
||||
|
||||
//将修改后的结构体拷贝回去
|
||||
Marshal.StructureToPtr(pos, lParam, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 这里也不需要日志啥的,只是为了防止上面有逗比逻辑,在消息循环里面炸了
|
||||
}
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
static class Hotkey
|
||||
{
|
||||
#region 系统api
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
static extern bool RegisterHotKey(IntPtr hWnd, int id, HotkeyModifiers fsModifiers, uint vk);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 注册快捷键
|
||||
/// </summary>
|
||||
/// <param name="window">持有快捷键窗口</param>
|
||||
/// <param name="fsModifiers">组合键</param>
|
||||
/// <param name="key">快捷键</param>
|
||||
/// <param name="callBack">回调函数</param>
|
||||
public static bool Regist(Window window, HotkeyModifiers fsModifiers, Key key, HotKeyCallBackHanlder callBack)
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(window).Handle;
|
||||
var _hwndSource = HwndSource.FromHwnd(hwnd);
|
||||
|
||||
if (keyid == 10)
|
||||
{
|
||||
_hwndSource.AddHook(WndProc);
|
||||
}
|
||||
|
||||
int id = keyid++;
|
||||
|
||||
var vk = KeyInterop.VirtualKeyFromKey(key);
|
||||
if (!RegisterHotKey(hwnd, id, fsModifiers, (uint)vk))
|
||||
{
|
||||
//throw new Exception("regist hotkey fail.");
|
||||
return false;
|
||||
}
|
||||
keymap[id] = callBack;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快捷键消息处理
|
||||
/// </summary>
|
||||
static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
if (msg == WM_HOTKEY)
|
||||
{
|
||||
int id = wParam.ToInt32();
|
||||
if (keymap.TryGetValue(id, out var callback))
|
||||
{
|
||||
callback();
|
||||
}
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销快捷键
|
||||
/// </summary>
|
||||
/// <param name="hWnd">持有快捷键窗口的句柄</param>
|
||||
/// <param name="callBack">回调函数</param>
|
||||
public static void UnRegist(IntPtr hWnd, HotKeyCallBackHanlder callBack)
|
||||
{
|
||||
foreach (KeyValuePair<int, HotKeyCallBackHanlder> var in keymap)
|
||||
{
|
||||
if (var.Value == callBack)
|
||||
UnregisterHotKey(hWnd, var.Key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const int WM_HOTKEY = 0x312;
|
||||
static int keyid = 10;
|
||||
static Dictionary<int, HotKeyCallBackHanlder> keymap = new Dictionary<int, HotKeyCallBackHanlder>();
|
||||
|
||||
public delegate void HotKeyCallBackHanlder();
|
||||
}
|
||||
|
||||
enum HotkeyModifiers
|
||||
{
|
||||
MOD_ALT = 0x1,
|
||||
MOD_CONTROL = 0x2,
|
||||
MOD_SHIFT = 0x4,
|
||||
MOD_WIN = 0x8
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class InkRecognizeHelper
|
||||
{
|
||||
//识别形状
|
||||
public static ShapeRecognizeResult RecognizeShape(StrokeCollection strokes)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return default;
|
||||
|
||||
var analyzer = new InkAnalyzer();
|
||||
analyzer.AddStrokes(strokes);
|
||||
analyzer.SetStrokesType(strokes, System.Windows.Ink.StrokeType.Drawing);
|
||||
|
||||
AnalysisAlternate analysisAlternate = null;
|
||||
int strokesCount = strokes.Count;
|
||||
var sfsaf = analyzer.Analyze();
|
||||
if (sfsaf.Successful)
|
||||
{
|
||||
var alternates = analyzer.GetAlternates();
|
||||
if (alternates.Count > 0)
|
||||
{
|
||||
while ((!alternates[0].Strokes.Contains(strokes.Last()) ||
|
||||
!IsContainShapeType(((InkDrawingNode)alternates[0].AlternateNodes[0]).GetShapeName()))
|
||||
&& strokesCount >= 2)
|
||||
{
|
||||
analyzer.RemoveStroke(strokes[strokes.Count - strokesCount]);
|
||||
strokesCount--;
|
||||
sfsaf = analyzer.Analyze();
|
||||
if (sfsaf.Successful)
|
||||
{
|
||||
alternates = analyzer.GetAlternates();
|
||||
}
|
||||
}
|
||||
analysisAlternate = alternates[0];
|
||||
}
|
||||
}
|
||||
|
||||
analyzer.Dispose();
|
||||
|
||||
if (analysisAlternate != null && analysisAlternate.AlternateNodes.Count > 0)
|
||||
{
|
||||
var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode;
|
||||
return new ShapeRecognizeResult(node.Centroid, node.HotPoints, analysisAlternate, node);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static bool IsContainShapeType(string name)
|
||||
{
|
||||
if (name.Contains("Triangle") || name.Contains("Circle") ||
|
||||
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
||||
name.Contains("Parallelogram") || name.Contains("Square")
|
||||
|| name.Contains("Ellipse"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Recognizer 的实现
|
||||
|
||||
public enum RecognizeLanguage
|
||||
{
|
||||
SimplifiedChinese = 0x0804,
|
||||
TraditionalChinese = 0x7c03,
|
||||
English = 0x0809
|
||||
}
|
||||
|
||||
public class ShapeRecognizeResult
|
||||
{
|
||||
public ShapeRecognizeResult(Point centroid, PointCollection hotPoints, AnalysisAlternate analysisAlternate, InkDrawingNode node)
|
||||
{
|
||||
Centroid = centroid;
|
||||
HotPoints = hotPoints;
|
||||
AnalysisAlternate = analysisAlternate;
|
||||
InkDrawingNode = node;
|
||||
}
|
||||
|
||||
public AnalysisAlternate AnalysisAlternate { get; }
|
||||
|
||||
public Point Centroid { get; set; }
|
||||
|
||||
public PointCollection HotPoints { get; }
|
||||
|
||||
public InkDrawingNode InkDrawingNode { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图形识别类
|
||||
/// </summary>
|
||||
//public class ShapeRecogniser
|
||||
//{
|
||||
// public InkAnalyzer _inkAnalyzer = null;
|
||||
|
||||
// private ShapeRecogniser()
|
||||
// {
|
||||
// this._inkAnalyzer = new InkAnalyzer
|
||||
// {
|
||||
// AnalysisModes = AnalysisModes.AutomaticReconciliationEnabled
|
||||
// };
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 根据笔迹集合返回图形名称字符串
|
||||
// /// </summary>
|
||||
// /// <param name="strokeCollection"></param>
|
||||
// /// <returns></returns>
|
||||
// public InkDrawingNode Recognition(StrokeCollection strokeCollection)
|
||||
// {
|
||||
// if (strokeCollection == null)
|
||||
// {
|
||||
// //MessageBox.Show("dddddd");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// InkDrawingNode result = null;
|
||||
// try
|
||||
// {
|
||||
// this._inkAnalyzer.AddStrokes(strokeCollection);
|
||||
// if (this._inkAnalyzer.Analyze().Successful)
|
||||
// {
|
||||
// result = _internalAnalyzer(this._inkAnalyzer);
|
||||
// this._inkAnalyzer.RemoveStrokes(strokeCollection);
|
||||
// }
|
||||
// }
|
||||
// catch (System.Exception ex)
|
||||
// {
|
||||
// //result = ex.Message;
|
||||
// System.Diagnostics.Debug.WriteLine(ex.Message);
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 实现笔迹的分析,返回图形对应的字符串
|
||||
// /// 你在实际的应用中根据返回的字符串来生成对应的Shape
|
||||
// /// </summary>
|
||||
// /// <param name="ink"></param>
|
||||
// /// <returns></returns>
|
||||
// private InkDrawingNode _internalAnalyzer(InkAnalyzer ink)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// ContextNodeCollection nodecollections = ink.FindNodesOfType(ContextNodeType.InkDrawing);
|
||||
// foreach (ContextNode node in nodecollections)
|
||||
// {
|
||||
// InkDrawingNode drawingNode = node as InkDrawingNode;
|
||||
// if (drawingNode != null)
|
||||
// {
|
||||
// return drawingNode;//.GetShapeName();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (System.Exception ex)
|
||||
// {
|
||||
// System.Diagnostics.Debug.WriteLine(ex.Message);
|
||||
// }
|
||||
|
||||
// return null;
|
||||
// }
|
||||
|
||||
|
||||
// private static ShapeRecogniser instance = null;
|
||||
// public static ShapeRecogniser Instance
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// return instance == null ? (instance = new ShapeRecogniser()) : instance;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//用于自动控制其他形状相对于圆的位置
|
||||
|
||||
public class Circle
|
||||
{
|
||||
public Circle(Point centroid, double r, Stroke stroke)
|
||||
{
|
||||
Centroid = centroid;
|
||||
R = r;
|
||||
Stroke = stroke;
|
||||
}
|
||||
|
||||
public Point Centroid { get; set; }
|
||||
|
||||
public double R { get; set; }
|
||||
|
||||
public Stroke Stroke { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class IsOutsideOfScreenHelper {
|
||||
public static bool IsOutsideOfScreen(FrameworkElement target) {
|
||||
var hwndSource = (HwndSource)PresentationSource.FromVisual(target);
|
||||
if (hwndSource is null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var hWnd = hwndSource.Handle;
|
||||
var targetBounds = GetPixelBoundsToScreen(target);
|
||||
|
||||
var screens = System.Windows.Forms.Screen.AllScreens;
|
||||
return !screens.Any(x => x.Bounds.IntersectsWith(targetBounds));
|
||||
|
||||
System.Drawing.Rectangle GetPixelBoundsToScreen(FrameworkElement visual) {
|
||||
var pixelBoundsToScreen = Rect.Empty;
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, 0)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, 0)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, visual.ActualHeight)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, visual.ActualHeight)));
|
||||
return new System.Drawing.Rectangle(
|
||||
(int)pixelBoundsToScreen.X, (int)pixelBoundsToScreen.Y,
|
||||
(int)pixelBoundsToScreen.Width, (int)pixelBoundsToScreen.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
class LogHelper
|
||||
{
|
||||
public static string LogFile = "Log.txt";
|
||||
|
||||
public static void NewLog(string str)
|
||||
{
|
||||
WriteLogToFile(str, LogType.Info);
|
||||
}
|
||||
|
||||
public static void NewLog(Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static void WriteLogToFile(string str, LogType logType = LogType.Info)
|
||||
{
|
||||
string strLogType = "Info";
|
||||
switch (logType)
|
||||
{
|
||||
case LogType.Event:
|
||||
strLogType = "Event";
|
||||
break;
|
||||
case LogType.Trace:
|
||||
strLogType = "Trace";
|
||||
break;
|
||||
case LogType.Error:
|
||||
strLogType = "Error";
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
var file = App.RootPath + LogFile;
|
||||
if (!Directory.Exists(App.RootPath))
|
||||
{
|
||||
Directory.CreateDirectory(App.RootPath);
|
||||
}
|
||||
StreamWriter sw = new StreamWriter(file, true);
|
||||
sw.WriteLine(string.Format("{0} [{1}] {2}", DateTime.Now.ToString("O"), strLogType, str));
|
||||
sw.Close();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
internal static void WriteLogToFile(string v, object warning)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public enum LogType
|
||||
{
|
||||
Info,
|
||||
Trace,
|
||||
Error,
|
||||
Event,
|
||||
Warning
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class VisualCanvas : FrameworkElement
|
||||
{
|
||||
protected override Visual GetVisualChild(int index)
|
||||
{
|
||||
return Visual;
|
||||
}
|
||||
|
||||
protected override int VisualChildrenCount => 1;
|
||||
|
||||
public VisualCanvas(DrawingVisual visual)
|
||||
{
|
||||
Visual = visual;
|
||||
AddVisualChild(visual);
|
||||
}
|
||||
|
||||
public DrawingVisual Visual { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于显示笔迹的类
|
||||
/// </summary>
|
||||
public class StrokeVisual : DrawingVisual
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建显示笔迹的类
|
||||
/// </summary>
|
||||
public StrokeVisual() : this(new DrawingAttributes()
|
||||
{
|
||||
Color = Colors.Red,
|
||||
//FitToCurve = true,
|
||||
Width = 3,
|
||||
Height = 3
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建显示笔迹的类
|
||||
/// </summary>
|
||||
/// <param name="drawingAttributes"></param>
|
||||
public StrokeVisual(DrawingAttributes drawingAttributes)
|
||||
{
|
||||
_drawingAttributes = drawingAttributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置或获取显示的笔迹
|
||||
/// </summary>
|
||||
public Stroke Stroke { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 在笔迹中添加点
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
public void Add(StylusPoint point)
|
||||
{
|
||||
if (Stroke == null)
|
||||
{
|
||||
var collection = new StylusPointCollection { point };
|
||||
Stroke = new Stroke(collection) { DrawingAttributes = _drawingAttributes };
|
||||
}
|
||||
else
|
||||
{
|
||||
Stroke.StylusPoints.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新画出笔迹
|
||||
/// </summary>
|
||||
public void Redraw()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var dc = RenderOpen())
|
||||
{
|
||||
Stroke.Draw(dc);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private readonly DrawingAttributes _drawingAttributes;
|
||||
|
||||
public static implicit operator Stroke(StrokeVisual v)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class SoftwareLauncher
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
public static void LaunchEasiCamera(string softwareName)
|
||||
{
|
||||
string executablePath = FindEasiCameraExecutablePath(softwareName);
|
||||
|
||||
if (!string.IsNullOrEmpty(executablePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(executablePath);
|
||||
//Console.WriteLine(softwareName + " 启动成功!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("启动失败: " + ex.Message);
|
||||
//MessageBox.Show("启动失败: " + ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Console.WriteLine(softwareName + " 未找到可执行文件路径。");
|
||||
}
|
||||
}
|
||||
|
||||
private static string FindEasiCameraExecutablePath(string softwareName)
|
||||
{
|
||||
string executablePath = null;
|
||||
|
||||
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall"))
|
||||
{
|
||||
foreach (string subkeyName in key.GetSubKeyNames())
|
||||
{
|
||||
using (RegistryKey subkey = key.OpenSubKey(subkeyName))
|
||||
{
|
||||
string displayName = subkey.GetValue("DisplayName") as string;
|
||||
string installLocation = subkey.GetValue("InstallLocation") as string;
|
||||
string uninstallString = subkey.GetValue("UninstallString") as string;
|
||||
|
||||
if (!string.IsNullOrEmpty(displayName) && displayName.Contains(softwareName))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(installLocation))
|
||||
{
|
||||
executablePath = System.IO.Path.Combine(installLocation, "sweclauncher.exe");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(uninstallString))
|
||||
{
|
||||
int lastSlashIndex = uninstallString.LastIndexOf("\\");
|
||||
if (lastSlashIndex >= 0)
|
||||
{
|
||||
string folderPath = uninstallString.Substring(0, lastSlashIndex);
|
||||
executablePath = System.IO.Path.Combine(folderPath, "sweclauncher", "sweclauncher.exe");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return executablePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class TimeMachine
|
||||
{
|
||||
private readonly List<TimeMachineHistory> _currentStrokeHistory = new List<TimeMachineHistory>();
|
||||
|
||||
private int _currentIndex = -1;
|
||||
|
||||
public delegate void OnUndoStateChange(bool status);
|
||||
|
||||
public event OnUndoStateChange OnUndoStateChanged;
|
||||
|
||||
public delegate void OnRedoStateChange(bool status);
|
||||
|
||||
public event OnRedoStateChange OnRedoStateChanged;
|
||||
|
||||
public void CommitStrokeUserInputHistory(StrokeCollection stroke)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
_currentStrokeHistory.Add(new TimeMachineHistory(stroke, TimeMachineHistoryType.UserInput, false));
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
|
||||
public void CommitStrokeShapeHistory(StrokeCollection strokeToBeReplaced, StrokeCollection generatedStroke)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
_currentStrokeHistory.Add(new TimeMachineHistory(generatedStroke,
|
||||
TimeMachineHistoryType.ShapeRecognition,
|
||||
false,
|
||||
strokeToBeReplaced));
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
|
||||
public void CommitStrokeManipulationHistory(Dictionary<Stroke, Tuple<StylusPointCollection, StylusPointCollection>> stylusPointDictionary)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
_currentStrokeHistory.Add(
|
||||
new TimeMachineHistory(stylusPointDictionary,
|
||||
TimeMachineHistoryType.Manipulation));
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
public void CommitStrokeDrawingAttributesHistory(Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>> drawingAttributes)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
_currentStrokeHistory.Add(
|
||||
new TimeMachineHistory(drawingAttributes,
|
||||
TimeMachineHistoryType.DrawingAttributes));
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
|
||||
public void CommitStrokeEraseHistory(StrokeCollection stroke, StrokeCollection sourceStroke = null)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
_currentStrokeHistory.Add(new TimeMachineHistory(stroke, TimeMachineHistoryType.Clear, true, sourceStroke));
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
|
||||
public void ClearStrokeHistory()
|
||||
{
|
||||
_currentStrokeHistory.Clear();
|
||||
_currentIndex = -1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
|
||||
public TimeMachineHistory Undo()
|
||||
{
|
||||
var item = _currentStrokeHistory[_currentIndex];
|
||||
item.StrokeHasBeenCleared = !item.StrokeHasBeenCleared;
|
||||
_currentIndex--;
|
||||
OnUndoStateChanged?.Invoke(_currentIndex > -1);
|
||||
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
|
||||
return item;
|
||||
}
|
||||
|
||||
public TimeMachineHistory Redo()
|
||||
{
|
||||
var item = _currentStrokeHistory[++_currentIndex];
|
||||
item.StrokeHasBeenCleared = !item.StrokeHasBeenCleared;
|
||||
NotifyUndoRedoState();
|
||||
return item;
|
||||
}
|
||||
|
||||
public TimeMachineHistory[] ExportTimeMachineHistory()
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
return _currentStrokeHistory.ToArray();
|
||||
}
|
||||
|
||||
public bool ImportTimeMachineHistory(TimeMachineHistory[] sourceHistory)
|
||||
{
|
||||
_currentStrokeHistory.Clear();
|
||||
_currentStrokeHistory.AddRange(sourceHistory);
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
return true;
|
||||
}
|
||||
private void NotifyUndoRedoState()
|
||||
{
|
||||
OnUndoStateChanged?.Invoke(_currentIndex > -1);
|
||||
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
|
||||
}
|
||||
}
|
||||
|
||||
public class TimeMachineHistory
|
||||
{
|
||||
public TimeMachineHistoryType CommitType;
|
||||
public bool StrokeHasBeenCleared = false;
|
||||
public StrokeCollection CurrentStroke;
|
||||
public StrokeCollection ReplacedStroke;
|
||||
//这里说一下 Tuple的 Value1 是初始值 ; Value 2 是改变值
|
||||
public Dictionary<Stroke, Tuple<StylusPointCollection, StylusPointCollection>> StylusPointDictionary;
|
||||
public Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>> DrawingAttributes;
|
||||
public TimeMachineHistory(StrokeCollection currentStroke, TimeMachineHistoryType commitType, bool strokeHasBeenCleared)
|
||||
{
|
||||
CommitType = commitType;
|
||||
CurrentStroke = currentStroke;
|
||||
StrokeHasBeenCleared = strokeHasBeenCleared;
|
||||
ReplacedStroke = null;
|
||||
}
|
||||
public TimeMachineHistory(Dictionary<Stroke, Tuple<StylusPointCollection, StylusPointCollection>> stylusPointDictionary, TimeMachineHistoryType commitType)
|
||||
{
|
||||
CommitType = commitType;
|
||||
StylusPointDictionary = stylusPointDictionary;
|
||||
}
|
||||
public TimeMachineHistory(Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>> drawingAttributes, TimeMachineHistoryType commitType)
|
||||
{
|
||||
CommitType = commitType;
|
||||
DrawingAttributes = drawingAttributes;
|
||||
}
|
||||
public TimeMachineHistory(StrokeCollection currentStroke, TimeMachineHistoryType commitType, bool strokeHasBeenCleared, StrokeCollection replacedStroke)
|
||||
{
|
||||
CommitType = commitType;
|
||||
CurrentStroke = currentStroke;
|
||||
StrokeHasBeenCleared = strokeHasBeenCleared;
|
||||
ReplacedStroke = replacedStroke;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TimeMachineHistoryType
|
||||
{
|
||||
UserInput,
|
||||
ShapeRecognition,
|
||||
Clear,
|
||||
Manipulation,
|
||||
DrawingAttributes
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Windows.Automation;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class WinTabWindowsChecker {
|
||||
/*
|
||||
public static bool IsWindowMinimized(string windowName, bool matchFullName = true) {
|
||||
// 获取Win+Tab预览中的窗口
|
||||
AutomationElementCollection windows = AutomationElement.RootElement.FindAll(
|
||||
TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
|
||||
|
||||
foreach (AutomationElement window in windows) {
|
||||
//LogHelper.WriteLogToFile("" + window.Current.Name);
|
||||
|
||||
string windowTitle = window.Current.Name;
|
||||
|
||||
// 如果窗口标题包含 windowName,则进行检查
|
||||
if (!string.IsNullOrEmpty(windowTitle) && windowTitle.Contains(windowName)) {
|
||||
if (matchFullName) {
|
||||
if (windowTitle.Length == windowName.Length) {
|
||||
// 检查窗口是否最小化
|
||||
WindowPattern windowPattern = window.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
|
||||
if (windowPattern != null) {
|
||||
bool isMinimized = windowPattern.Current.WindowVisualState == WindowVisualState.Minimized;
|
||||
//LogHelper.WriteLogToFile("" + windowTitle + isMinimized);
|
||||
return isMinimized;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 检查窗口是否最小化
|
||||
WindowPattern windowPattern = window.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
|
||||
if (windowPattern != null) {
|
||||
bool isMinimized = windowPattern.Current.WindowVisualState == WindowVisualState.Minimized;
|
||||
return isMinimized;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 未找到软件白板窗口
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
public static bool IsWindowExisted(string windowName, bool matchFullName = true) {
|
||||
// 获取Win+Tab预览中的窗口
|
||||
AutomationElementCollection windows = AutomationElement.RootElement.FindAll(
|
||||
TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
|
||||
|
||||
foreach (AutomationElement window in windows) {
|
||||
//LogHelper.WriteLogToFile("" + window.Current.Name);
|
||||
|
||||
string windowTitle = window.Current.Name;
|
||||
|
||||
// 如果窗口标题包含 windowName,则进行检查
|
||||
if (!string.IsNullOrEmpty(windowTitle) && windowTitle.Contains(windowName)) {
|
||||
if (matchFullName) {
|
||||
if (windowTitle.Length == windowName.Length) {
|
||||
WindowPattern windowPattern = window.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
|
||||
if (windowPattern != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
WindowPattern windowPattern = window.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
|
||||
if (windowPattern != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user