最近在做一个云剪贴板的程序,需要用一个c#桌面应用程序来监控剪贴板。本来想的是,新建一个线程用while循环检查剪贴板内容的改动,不过发现不成功,如果用GetText()访问剪贴板则不管剪贴板内有没有文字都返回空字符串,用SetText()设置剪贴板则出错:“在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。”
所以我google了一下,发现貌似监视剪贴板可以不用“幕后线程”去不断检查,有专门的API来进行监视,并通过触发事件来告诉我们。
方法一:
首先要using一个System.Runtime.InteropServices.
然后写下下面的声明:
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetClipboardViewer(IntPtr hWnd);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
IntPtr ClipboardViewerNext;
private void RegisterClipboardViewer()
{
ClipboardViewerNext = SetClipboardViewer(this.Handle);
}
private void UnregisterClipboardViewer()
{
ChangeClipboardChain(this.Handle, ClipboardViewerNext);
}
然后在Form_Load中进行RegisterClipboardViewer:
private void Form1_Load(object sender, EventArgs e)
{
RegisterClipboardViewer();
}
然后重写Form.WndProc方法:
protected override void WndProc(ref Message m)
{
switch ((int)m.Msg)
{
case 0x308: //WM_DRAWCLIPBOARD
{
//Do something here..
break;
}
default:
{
base.WndProc(ref m);
break;
}
}
}
然后就OK了。
但是我对这种API调用很不爽,看着十分别扭,而且不google一下的话不可能知道。难道就真的不能用“幕后线程”去监控剪贴板么?
方法二:
答案当然是否定的。经过我的探究,既然访问ClipBoard需要单线程模式,那咱就设一下:
System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ThreadStart(func));
th.SetApartmentState(System.Threading.ApartmentState.STA);
th.Start();
然后在func()方法里写上个while(true)的循环去监视就没有之前的问题了。
kyle
说:
你好,按照你的方法做了测试,代码如下,但是出错,不知道什么问题? 麻烦帮忙看看。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Net.Mail;
using System.Runtime.InteropServices;
namespace email
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
string textInClipboard = null;
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
try
{ //using System.Net.Mail; based on .NET 3.5
SmtpClient smtp = new SmtpClient();
smtp.Host = "mail.agriview.com.au";
smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
smtp.Credentials = new System.Net.NetworkCredential("kyle.l@agriview.com.au", "joyce");
MailMessage mm = new MailMessage("kyle.l@agriview.com.au", "kyle30542@gmail.com");
mm.Body = "this is the body of email";
mm.Subject = "mail subject";
smtp.Send(mm);
}
catch
{
throw;
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ThreadStart(viewClipboard));
th.SetApartmentState(System.Threading.ApartmentState.STA);
th.Start();
textInClipboard = Clipboard.GetText();
}
private void button2_Click(object sender, RoutedEventArgs e)
{
//轮询,改变则弹出
if (!Clipboard.GetText().Equals(textInClipboard))
{
MessageBox.Show(Clipboard.GetText() + "" + textInClipboard);
textInClipboard = Clipboard.GetText();
}
}
public void viewClipboard()
{
while (true)
{
try
{
string currentTxt = Clipboard.GetText();
if (currentTxt != textInClipboard)
{
//MessageBox.Show(currentTxt);
button2.Content = currentTxt;
textInClipboard = currentTxt;
}
else
{
//MessageBox.Show(Clipboard.GetText());
}
}
catch
{ }
}
}
}
}
Anran
说:
等我回家给你试试的。。请问错误信息是什么呢?
Anran
说:
这是个wpf程序吧。。我只装了vs2008所以没法调试wpf,不过我认为是因为安全原因所以不允许wpf执行win32 api,也就不能访问剪贴板。google到的用threading.dispatcher的invoke试试。。
kyle
说:
多谢你这么快的回复。
提示 “由于其他线程拥有此对象,因此调用线程无法对其进行访问”
能否给出完整的例子,谢谢。
第一种方法也好像不行的。提示找不到this.handle方法。
kyle
说:
多谢回复。回头我自己再试试