如何在控制台应用程序中正确使用剪贴板?
How do I properly use Clipboard in a console application?
我是 C# 初学者,我正在尝试创建一个 Windows 服务(带 Topshelf 的控制台应用程序)(.Net Framwork 4.8),每秒获取和设置剪贴板(是的,无用的服务,它是仅供学习)。
在我的服务 class 中使用 System.Windows.Forms 作为参考时,计时器 class 停止工作(“'Timer' 是 'System.Windows.Forms.Timer' 和 'System.Timers.Timer'") 并且应用程序在我使用剪贴板 class 的行抛出 System.Threading.ThreadStateException:“当前线程必须设置为单线程单元 (STA) 模式才能调用 OLE制作完成后,请确保您的 Main 函数上标有 STAThreadAttribute。
using System.Timers;
using System.Windows.Forms;
namespace ClipboardProject
{
public class TimerClipboard
{
private readonly Timer _timer;
public TimerClipboard()
{
_timer = new Timer(1000) { AutoReset = true };
_timer.Elapsed += TimerElapsed;
}
private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
}
public void Start()
{
_timer.Start();
}
public void Stop()
{
_timer.Stop();
}
}
}
我做错了什么?
已编辑:
这是我的主要方法。
using System;
using Topshelf;
namespace ClipboardProject
{
public class Program
{
static void Main(string[] args)
{
var exitCode = HostFactory.Run(x =>
{
x.Service<TimerClipboard>(s =>
{
s.ConstructUsing(timerClipboard=> new TimerClipboard());
s.WhenStarted(timerClipboard=> timerClipboard.Start());
s.WhenStopped(timerClipboard=> timerClipboard.Stop());
});
x.RunAsLocalSystem();
x.SetServiceName("Random ServiceName");
x.SetDisplayName("Random DisplayName");
x.SetDescription("Random Description");
});
int exitCodeValue = (int)Convert.ChangeType(exitCode, exitCode.GetTypeCode());
Environment.ExitCode = exitCodeValue;
}
}
}
System.Timers.Timer.Elapsed
处理程序总是在后台线程中 运行,因此 Clipboard
OLE 调用导致 System.Threading.ThreadStateException
.
您可以通过创建新的 System.Threading.Thread
来处理它,并通过 SetApartmentStart(ApartmentState.STA)
强制它作为 STA 线程启动,但这是 低效的 ,可怕的和错误的解决方案:
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
System.Threading.Thread t = new System.Threading.Thread(() =>
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
});
t.SetApartmentState(System.Threading.ApartmentState.STA);
t.Start();
}
因为在每个间隔刻度上它都会创建新的 Thread
。 Thread
s 会带来巨大的性能成本,尤其是在循环中创建时。
因此,正确的解决方案可能是使用 PresentationFramework
库中的 System.Windows.Threading.DispatcherTimer
:
public class TimerClipboard
{
private readonly System.Windows.Threading.DispatcherTimer _timer;
public TimerClipboard()
{
_timer = new System.Windows.Threading.DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += OnDispatcherTimerTick;
}
private void OnDispatcherTimerTick(object sender, EventArgs e)
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
}
public void Start() => _timer.Start();
public void Stop() => _timer.Stop();
}
关于 "'Timer' 是 System.Windows.Forms.Timer
和 System.Timers.Timer
" 之间的模糊引用:那是因为两个命名空间都有 class Timer
,因此您的 Studio 不知道您想要和需要使用哪一个。可以通过删除另一个 using
或明确指定来解决:
using Timer = System.Timers.Timer;
// or
using Timer = System.Windows.Forms.Timer;
namespace ClipboardProject
{
// ...
}
编辑。
正如@Hans Passant 所注意到的,DispatcherTimer.Tick
事件在没有调度程序的情况下无法触发,因此上面的解决方案不适用于 Topshelf。所以我提议重写 TimerClipboard
class 以从那里删除任何计时器并使用简单的基于标志的 while
循环。由于没有计时器,我将其重命名为 ClipboardWorker
.
完整的解决方案如下所示:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using Topshelf;
namespace ClipboardProject
{
class Program
{
// Add STA attribute to Main method
[STAThread]
static void Main(string[] args)
{
var topshelfExitCode = HostFactory.Run(x =>
{
x.Service<ClipboardWorker>(s =>
{
s.ConstructUsing(cw => new ClipboardWorker());
s.WhenStarted(cw => cw.Start());
s.WhenStopped(cw => cw.Stop());
});
x.RunAsLocalSystem();
x.SetServiceName("ClipboardWorkerServiceName");
x.SetDisplayName("ClipboardWorkerDisplayName");
x.SetDescription("ClipboardWorkerDescription");
});
Environment.ExitCode = (int)topshelfExitCode;
}
}
public class ClipboardWorker
{
// Flag that would indicate our Worker in running or not
private bool _isRunning;
private int _interval = 1000; // Default value would be 1000 ms
public bool IsRunning { get => _isRunning; }
public int Interval
{
get => _interval;
// Check value which sets is greater than 0. Elseway set default 1000 ms
set => _interval = value > 0 ? value : 1000;
}
// Constructor
public ClipboardWorker()
{
Console.WriteLine();
Console.WriteLine("ClipboardWorker initialized.");
}
// "Tick" simulation.
private void DoWorkWithClipboard()
{
// Loop runs until Stop method would be called, which would set _isRunning to false
while (_isRunning)
{
Console.WriteLine(); // <--- just line break for readability
string userClipboard = Clipboard.GetText();
Console.WriteLine($"Captured from Clipboard value: {userClipboard}");
Clipboard.SetText($"Latest copy: {userClipboard}");
Console.WriteLine($"Latest copy: {userClipboard}");
// Use delay as interval between "ticks"
Task.Delay(Interval).Wait();
}
}
public void Start()
{
// Set to true so while loop in DoWorkWithClipboard method be able to run
_isRunning = true;
Console.WriteLine("ClipboardWorker started.");
// Run "ticking"
DoWorkWithClipboard();
}
public void Stop()
{
// Set to false to break while loop in DoWorkWithClipboard method
_isRunning = false;
Console.WriteLine("ClipboardWorker stopped.");
}
}
}
示例输出:
我是 C# 初学者,我正在尝试创建一个 Windows 服务(带 Topshelf 的控制台应用程序)(.Net Framwork 4.8),每秒获取和设置剪贴板(是的,无用的服务,它是仅供学习)。
在我的服务 class 中使用 System.Windows.Forms 作为参考时,计时器 class 停止工作(“'Timer' 是 'System.Windows.Forms.Timer' 和 'System.Timers.Timer'") 并且应用程序在我使用剪贴板 class 的行抛出 System.Threading.ThreadStateException:“当前线程必须设置为单线程单元 (STA) 模式才能调用 OLE制作完成后,请确保您的 Main 函数上标有 STAThreadAttribute。
using System.Timers;
using System.Windows.Forms;
namespace ClipboardProject
{
public class TimerClipboard
{
private readonly Timer _timer;
public TimerClipboard()
{
_timer = new Timer(1000) { AutoReset = true };
_timer.Elapsed += TimerElapsed;
}
private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
}
public void Start()
{
_timer.Start();
}
public void Stop()
{
_timer.Stop();
}
}
}
我做错了什么?
已编辑:
这是我的主要方法。
using System;
using Topshelf;
namespace ClipboardProject
{
public class Program
{
static void Main(string[] args)
{
var exitCode = HostFactory.Run(x =>
{
x.Service<TimerClipboard>(s =>
{
s.ConstructUsing(timerClipboard=> new TimerClipboard());
s.WhenStarted(timerClipboard=> timerClipboard.Start());
s.WhenStopped(timerClipboard=> timerClipboard.Stop());
});
x.RunAsLocalSystem();
x.SetServiceName("Random ServiceName");
x.SetDisplayName("Random DisplayName");
x.SetDescription("Random Description");
});
int exitCodeValue = (int)Convert.ChangeType(exitCode, exitCode.GetTypeCode());
Environment.ExitCode = exitCodeValue;
}
}
}
System.Timers.Timer.Elapsed
处理程序总是在后台线程中 运行,因此 Clipboard
OLE 调用导致 System.Threading.ThreadStateException
.
您可以通过创建新的 System.Threading.Thread
来处理它,并通过 SetApartmentStart(ApartmentState.STA)
强制它作为 STA 线程启动,但这是 低效的 ,可怕的和错误的解决方案:
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
System.Threading.Thread t = new System.Threading.Thread(() =>
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
});
t.SetApartmentState(System.Threading.ApartmentState.STA);
t.Start();
}
因为在每个间隔刻度上它都会创建新的 Thread
。 Thread
s 会带来巨大的性能成本,尤其是在循环中创建时。
因此,正确的解决方案可能是使用 PresentationFramework
库中的 System.Windows.Threading.DispatcherTimer
:
public class TimerClipboard
{
private readonly System.Windows.Threading.DispatcherTimer _timer;
public TimerClipboard()
{
_timer = new System.Windows.Threading.DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += OnDispatcherTimerTick;
}
private void OnDispatcherTimerTick(object sender, EventArgs e)
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
}
public void Start() => _timer.Start();
public void Stop() => _timer.Stop();
}
关于 "'Timer' 是 System.Windows.Forms.Timer
和 System.Timers.Timer
" 之间的模糊引用:那是因为两个命名空间都有 class Timer
,因此您的 Studio 不知道您想要和需要使用哪一个。可以通过删除另一个 using
或明确指定来解决:
using Timer = System.Timers.Timer;
// or
using Timer = System.Windows.Forms.Timer;
namespace ClipboardProject
{
// ...
}
编辑。
正如@Hans Passant 所注意到的,DispatcherTimer.Tick
事件在没有调度程序的情况下无法触发,因此上面的解决方案不适用于 Topshelf。所以我提议重写 TimerClipboard
class 以从那里删除任何计时器并使用简单的基于标志的 while
循环。由于没有计时器,我将其重命名为 ClipboardWorker
.
完整的解决方案如下所示:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using Topshelf;
namespace ClipboardProject
{
class Program
{
// Add STA attribute to Main method
[STAThread]
static void Main(string[] args)
{
var topshelfExitCode = HostFactory.Run(x =>
{
x.Service<ClipboardWorker>(s =>
{
s.ConstructUsing(cw => new ClipboardWorker());
s.WhenStarted(cw => cw.Start());
s.WhenStopped(cw => cw.Stop());
});
x.RunAsLocalSystem();
x.SetServiceName("ClipboardWorkerServiceName");
x.SetDisplayName("ClipboardWorkerDisplayName");
x.SetDescription("ClipboardWorkerDescription");
});
Environment.ExitCode = (int)topshelfExitCode;
}
}
public class ClipboardWorker
{
// Flag that would indicate our Worker in running or not
private bool _isRunning;
private int _interval = 1000; // Default value would be 1000 ms
public bool IsRunning { get => _isRunning; }
public int Interval
{
get => _interval;
// Check value which sets is greater than 0. Elseway set default 1000 ms
set => _interval = value > 0 ? value : 1000;
}
// Constructor
public ClipboardWorker()
{
Console.WriteLine();
Console.WriteLine("ClipboardWorker initialized.");
}
// "Tick" simulation.
private void DoWorkWithClipboard()
{
// Loop runs until Stop method would be called, which would set _isRunning to false
while (_isRunning)
{
Console.WriteLine(); // <--- just line break for readability
string userClipboard = Clipboard.GetText();
Console.WriteLine($"Captured from Clipboard value: {userClipboard}");
Clipboard.SetText($"Latest copy: {userClipboard}");
Console.WriteLine($"Latest copy: {userClipboard}");
// Use delay as interval between "ticks"
Task.Delay(Interval).Wait();
}
}
public void Start()
{
// Set to true so while loop in DoWorkWithClipboard method be able to run
_isRunning = true;
Console.WriteLine("ClipboardWorker started.");
// Run "ticking"
DoWorkWithClipboard();
}
public void Stop()
{
// Set to false to break while loop in DoWorkWithClipboard method
_isRunning = false;
Console.WriteLine("ClipboardWorker stopped.");
}
}
}
示例输出: