剪切板是Windows系統提供的功能,從我最早接觸到的Windows 3.2版本開始,就一直帶着了。以前使用C++的時候,是直接使用Windows API對其進行操作的,到了.NET下,在WinForm中也有一個對剪切板的封裝類,即System.Windows.Forms.Clipboard,這個類其實是通過COM組件間接地使用剪切板的,我個人覺得COM是一個設計非常糟糕的東西,難懂坑多還不可移植,但微軟現存的大量代碼又是基於COM的,所以又無法徹底舍棄,關於不可移植這個並不難理解,前面說了,剪切板是Windows提供的功能,你在Linux下,或者在MacOS下,盡管有類似的功能,但跟Windows的肯定不同,所以最新的.NET Core中是不能使用剪切板功能的。
往剪切板里存取字符串
字符串是最最常用的數據對象了,我們就往剪切板里寫一個字符串吧,我總結了一下,見下表:
方法1 | Clipboard.SetText(str); | 很可能有問題 |
方法2 | Clipboard.SetData(DataFormats.Text,str); | 很可能有問題 |
方法3 | Clipboard.SetDataObject(str); | 大多數時候沒問題 |
嗯?怎么這么不確定?確實如此,這是我進行了大量試驗的結果,且程序在調試和非調試中還有不同的表現,可能出現的異常有以下兩個:
(異常1:COMException)
(異常2:ExternalException)
兩個異常都沒有進一步的提示信息,異常的原因很類似,就是剪切板訪問不了,而我使用方法3的時候,在非調試狀態下還沒發現過什么問題。我實在找不到進一步的規律了,先這樣用吧。
那么如何從剪切板獲取字符串呢?對應的,有兩種方法:
方法1 | string str = (string)Clipboard.GetData(DataFormats.Text) | 很可能有問題 |
方法2 | string str = Clipboard.GetText(); | 大多數時候沒問題 |
具體原因我同樣不太清楚,這似乎是微軟留下的一個bug,SO上有個討論,可以去看看:StackOverflow
另外還有兩點需要注意:
- 方法3這種往剪切板里寫文本內容的方式,在這個程序結束之后,剪切板內容將會失效,要使得程序結束后剪切板內容繼續有效的話,得使用Clipboard.SetDataObject(str, true);這個方法,第二個參數true表示讓剪切板內容在程序結束后繼續有效,但我發現加上這個參數之后,增加了出現異常的可能性。
- 必須在給程序的入口函數(通常是Main函數)加上STAThreadAttribute這個注解,否則對剪切板的訪問會報錯:在可以調用 OLE 之前,必須將當前線程設置為單線程單元(STA)模式。
往剪切板里存取自定義數據
C#的對象的數據結構並不能為剪切板所理解,所以你要把你自定義的數據放到剪切板去的話要把它序列化,在實際操作中,是要你提供一個“可序列化”的對象,下面是個簡單的例子:
[Serializable] public class User { public int age { get; set; } public string name { get; set; } } class Program { [STAThread] static void Main(string[] args) { User userIn = new User(); userIn.name = "Jack"; userIn.age = 18; Clipboard.SetData("mydata", userIn); User userOut = (User)Clipboard.GetData("mydata"); Console.WriteLine(userOut.name +" | " + userOut.age); } }
注意User這個類前面的Serializable注解,如果沒有這個注解,是沒法成功將對象寫入剪切板的。如果數據比較復雜,可以考慮把數據自行序列化到一個Stream對象去,再把Stream對象寫入剪切板,獲取的時候對Stream對象自行反序列化,還原數據。例子就不寫了。
最后要注意的一點是由於這里的數據類型是“mydata”,你也可以指定別的名字,這種類型數據只有你自己的程序能讀懂,也就是說,你是不能打開記事本或者Photoshop,直接把你這個User對象貼上去的。