2021年7月25日日曜日

3: C#でWindowsクリップボード: マルチフォーマットにて

<このシリーズの前の記事 | このシリーズの目次 |

.NET Frameworkが用いられ、したがって、Visual Basic.NETにも適用できます。多くのフォーマットがサポートされます。

話題


About: C#

この記事の目次


開始コンテキスト


  • 読者は、C#の基本的知識を持っている。

ターゲットコンテキスト



  • 読者は、フォーマット別データの複合体を、C#、またはViusual Basic.NETで、Windowsクリップボードから取得し、Windowsクリップボードへセットする方法を知る。.

オリエンテーション


多くの、しかし全てのではない、フォーマット群が、本テクニックではサポートされています。

全てのフォーマットが、LinuxまたはMicrosoft Windows用に、C++でのテクニックによってサポートされています、別シリーズの以降のある記事(Windows用)で紹介されるとおり。


本体


1: ねらい: あるワードプロセッサにコピーマルチバッファを実装すること


Hypothesizer 7
私はテキストエディターにVimを使っているが、その恩恵の1つは、コピーマルチバッファが備わっていることだ: あるテキスト断片をバッファ'a'へコピーし、別のテキスト断片をバッファ'b'へコピーするなどということができる。それは、大きな助けになる。

私はワードプロセッサにLibreOffice Writerを使っているが、そこにその機能が欲しい。

実は、その機能をPythonで実装しようとしたのだが、それは部分的には機能するが、理想的にではない。

そのテクニックについての2つの不満は、1) ありうるデータフォーマット群が先験的に知られていなければならない 2) サポートされるデータフォーマット群が限られている、ことである。

.NET Frameworkには、クリップボード操作機能があり、その機能にはWindowsクリップボードを可能な限りのあらゆる形で操作する能力があるものと期待した、なぜなら、.NET FrameworkはMicrosoft固有のものだから。

えーと、この.NET Framework機能が満足いくものだと仮定して、当該C#コードをPythonマクロからどうすれば呼べるのか(マクロはC#で書けないのに)?. . .HTTPサーバーをC#で作成し、それをPythonマクロがアクセスするようにするだろう。


2: .NET Frameworkクリップボード機能のいくつかの特徴


Hypothesizer 7
.NET Frameworkクリップボード機能は、いくつかの点において特徴的である、APIドキュメントに見られるとおり。

Win32 APIによれば各データフォーマットは番号によって同定されるのだが、そうした番号は、.NET Frameworkで見ることができない。.NET Frameworkでは、各データフォーマットは名前によって同定される。

クリップボードを明示的にオープン・クローズする必要はない。

クリップボードデータを取得するためにするべきことは、基本的にはまっすぐなものである: 'System.Windows.Forms.Clipboard.GetDataObject ()'をコールしてマルチフォーマット複合体を取得し、'System.Windows.IDataObject.GetFormats (Boolean)'をコールしてそのマルチフォーマット複合体からフォーマット名群を取得し、 'System.Windows.IDataObject.GetData (String, Boolean)'をコールして、各フォーマットのデータを取得する。

1つの問題は、多くのフォーマットの内のそれぞれのデータは'System.IO.MemoryStream'インスタンスとして取得されることだ。 . . . そのインスタンス自体をしまっておくというのはうまくいかないようなので、バイト配列を読み出してそれを保存する。

別の問題は、いくつかのフォーマット('EnhancedMetafile'や'MetaFilePict'のような)の内のそれぞれのデータを取得できない('System.Windows.IDataObject.GetData (String, Boolean)'がnullをリターンする)ことだ。 . . . なぜなのか?えーと、Microsoft Windowsにおいては、任意のフォーマットのデータが単純に一律の方法でバイト配列として取得できるというわけではない。あるフォーマットのデータは、それ特有の方法で取得されなければならないが、.NET Frameworkは、可能なあらゆるフォーマットをサポートする労をとっていない。それについて私はどうできるのか?えーと、何も、私が知る限り(Win32 APIを使わない限り)。

しまっておいたデータをクリップボードへセットするためにするべきことは、まっすぐなものである: 'System.Windows.DataObject'のインスタンスを生成し、その'System.Windows.IDataObject.SetData (String, Object, Boolean)'メソッドをフォーマット毎にコールしてマルチフォーマット複合体を用意し、'System.Windows.Forms.Clipboard.SetDataObject (Object, Boolean)'をコールして、そのマルチフォーマット複合体をクリップボードへセットする。

バイト配列は、そのまま入れることができるようだ。


3: 私のコード


Hypothesizer 7
私のねらいはクリップボードデータの履歴を格納することなので、そのストレージとしていくつかのクラスを作成した、以下のように。

theBiasPlanet/coreUtilities/clipboardHandling/ClipboardFormatSpecificDatum.cs

@C# ソースコード
namespace theBiasPlanet {
	namespace coreUtilities {
		namespace clipboardHandling {
			using System;
			
			public class ClipboardFormatSpecificDatum {
				private String i_formatName;
				private	Object i_datum;
				
				public ClipboardFormatSpecificDatum () {
				}
				
				public ClipboardFormatSpecificDatum (String a_formatName, Object a_datum) {
					i_formatName = a_formatName;
					i_datum = a_datum;
				}
				
				~ClipboardFormatSpecificDatum () {
				}
				
				public String getFormatName () {
					return i_formatName;
				}
				
				public Object getDatum () {
					return i_datum;
				}
			}
		}
	}
}

theBiasPlanet/coreUtilities/clipboardHandling/ClipboardFormatSpecificDataComposite.cs

@C# ソースコード
namespace theBiasPlanet {
	namespace coreUtilities {
		namespace clipboardHandling {
			using System;
			using System.Collections.Generic;
			using theBiasPlanet.coreUtilities.collections;
			
			public class ClipboardFormatSpecificDataComposite {
				private NavigableLinkedHashMap <String, ClipboardFormatSpecificDatum> i_formatNameToDatumMap = new NavigableLinkedHashMap <String, ClipboardFormatSpecificDatum> ();
				
				public ClipboardFormatSpecificDataComposite () {
				}
				
				~ClipboardFormatSpecificDataComposite () {
				}
				
				public Boolean addFormatSpecificDatum (ClipboardFormatSpecificDatum a_formatSpecificDatum) {
					try {
						i_formatNameToDatumMap [a_formatSpecificDatum.getFormatName ()] = a_formatSpecificDatum;
					}
					catch (KeyNotFoundException) {
						i_formatNameToDatumMap.Add (a_formatSpecificDatum.getFormatName (), a_formatSpecificDatum);
					}
					return true;
				}
				
				public List <String> getFormatNames () {
					List <String> l_formatNames = new List <String> ();
					
					foreach (KeyValuePair <String, ClipboardFormatSpecificDatum> l_formatNameToDatumMapEntry in i_formatNameToDatumMap) {
						l_formatNames.Add (l_formatNameToDatumMapEntry.Key);
					}
					return l_formatNames;
				}
				
				public ClipboardFormatSpecificDatum getFormatSpecificDatum (String a_formatName) {
					return i_formatNameToDatumMap [a_formatName];
				}
			}
		}
	}
}

theBiasPlanet/coreUtilities/clipboardHandling/ClipboardFormatSpecificDataCompositesHistory.cs

@C# ソースコード
namespace theBiasPlanet {
	namespace coreUtilities {
		namespace clipboardHandling {
			using System;
			using System.Collections.Generic;
			using theBiasPlanet.coreUtilities.collections;
			
			public class ClipboardFormatSpecificDataCompositesHistory {
				private NavigableLinkedHashMap <String, ClipboardFormatSpecificDataComposite> i_dataCompositeKeyToDataCompositeMap = new NavigableLinkedHashMap <String, ClipboardFormatSpecificDataComposite> ();
				
				public ClipboardFormatSpecificDataCompositesHistory () {
				}
				
				~ClipboardFormatSpecificDataCompositesHistory () {
				}
				
				public Boolean addDataComposite (String a_dataCompositeKey, ClipboardFormatSpecificDataComposite a_dataComposite) {
					try {
						i_dataCompositeKeyToDataCompositeMap [a_dataCompositeKey] = a_dataComposite;
					}
					catch (KeyNotFoundException) {
						i_dataCompositeKeyToDataCompositeMap.Add (a_dataCompositeKey, a_dataComposite);
					}
					return true;
				}
				
				public Boolean removeDataComposite (String a_dataCompositeKey) {
					i_dataCompositeKeyToDataCompositeMap.Remove (a_dataCompositeKey);
					return true;
				}
				
				public ClipboardFormatSpecificDataComposite getDataComposite (String a_dataCompositeKey) {
					return i_dataCompositeKeyToDataCompositeMap [a_dataCompositeKey];
				}
			}
		}
	}
}

クリップボード操作クラスは以下のものだ。

theBiasPlanet/coreUtilities/clipboardHandling/MicrosoftWindowsClipboard.cs

@C# ソースコード
namespace theBiasPlanet {
	namespace coreUtilities {
		namespace clipboardHandling {
			using System;
			using System.IO;
			using System.Windows;
			using System.Windows.Forms;
			using theBiasPlanet.coreUtilities.constantsGroups;
			
			public class MicrosoftWindowsClipboard {
				public static Boolean clearClipboard () {
					Clipboard.Clear ();
					return true;
				}
				
				public static ClipboardFormatSpecificDataComposite  getFormatSpecificDataComposite () {
					IDataObject l_dataObject = Clipboard.GetDataObject ();
					String [] l_datumFormatNames = l_dataObject.GetFormats (false);
					ClipboardFormatSpecificDataComposite l_formatSpecificDataComposite = new ClipboardFormatSpecificDataComposite ();
					foreach (String l_datumFormatName in l_datumFormatNames) {
						Object l_copiedFormatSpecificDatum = l_dataObject.GetData (l_datumFormatName, false);
						if (typeof (MemoryStream).IsInstanceOfType (l_copiedFormatSpecificDatum)) {
							MemoryStream l_copiedFormatSpecificDatumMemoryStream = (MemoryStream) l_copiedFormatSpecificDatum;
							Int64 l_copiedFormatSpecificDatumSizeMemoryStream = l_copiedFormatSpecificDatumMemoryStream.Length;
							byte [] l_copiedFormatSpecificDatumForMemoryStream = new byte [l_copiedFormatSpecificDatumSizeMemoryStream];
							for (int l_byteIndex = GeneralConstantsConstantsGroup.c_iterationStartNumber; l_byteIndex < l_copiedFormatSpecificDatumSizeMemoryStream; l_byteIndex ++) {
								l_copiedFormatSpecificDatumForMemoryStream [l_byteIndex] = Convert.ToByte (l_copiedFormatSpecificDatumMemoryStream.ReadByte ());
							}
							l_copiedFormatSpecificDatum = l_copiedFormatSpecificDatumForMemoryStream;
						}
						l_formatSpecificDataComposite.addFormatSpecificDatum (new ClipboardFormatSpecificDatum (l_datumFormatName, l_copiedFormatSpecificDatum));
					}
					return l_formatSpecificDataComposite;
				}
				
				public static Boolean setFormatSpecificDataComposite (ClipboardFormatSpecificDataComposite a_formatSpecificDataComposite) {
					IDataObject l_dataObject = new DataObject ();
					foreach (String l_datumFormatName  in a_formatSpecificDataComposite.getFormatNames ()) {
						ClipboardFormatSpecificDatum l_formatSpecificDatum = a_formatSpecificDataComposite.getFormatSpecificDatum (l_datumFormatName);
						Object l_copiedFormatSpecificDatum = l_formatSpecificDatum.getDatum ();
						l_dataObject.SetData (l_datumFormatName, true, l_copiedFormatSpecificDatum);
					}
					Clipboard.SetDataObject (l_dataObject, true);
					return true;
				}
			}
		}
	}
}


4: 制約


Hypothesizer 7
'EnhancedMetafile'のようないくつかのフォーマットのデータを取得できないという制約に加えて、実のところ、本テクニックでは、全てのフォーマットをリストすることもできない。

例えば、'DataObject'、'Ole Private Data'、'Locale'、'OEMText'というフォーマットは、'System.Windows.IDataObject.GetFormats (Boolean)'メソッドによってリストされない。


5: テストする


Hypothesizer 7
私のテストコードは以下のものだ。

@ ソースコード
namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace clipboardHandlingTest1 {
			using System;
			using System.Collections.Generic;
			using theBiasPlanet.coreUtilities.clipboardHandling;
			
			public class Test1Test {
				public static void main (String [] a_argumentsArray) {
					test ();
				}
				
				private static void test () {
					ClipboardFormatSpecificDataCompositesHistory l_clipbardFormatSpecificDataCompositesHistory = new ClipboardFormatSpecificDataCompositesHistory ();
					while (true) {
						Console.WriteLine ("### Input 'S' (Set), 'G' (Get), 'D (Display)', or 'Q' (Quit):");
						Console.Out.Flush ();
						String l_userInput;
						l_userInput = Console.ReadLine ();
						String l_clipboardFormatSpecificDataCompositeKey;
						if (l_userInput == "S" || l_userInput == "G" || l_userInput == "D") {
							Console.WriteLine ("### Input the key:");
							Console.Out.Flush ();
							l_clipboardFormatSpecificDataCompositeKey = Console.ReadLine ();
							if (l_userInput == "S") {
								MicrosoftWindowsClipboard.clearClipboard ();
								MicrosoftWindowsClipboard.setFormatSpecificDataComposite (l_clipbardFormatSpecificDataCompositesHistory.getDataComposite (l_clipboardFormatSpecificDataCompositeKey));
							}
							else if (l_userInput == "G") {
								l_clipbardFormatSpecificDataCompositesHistory.addDataComposite (l_clipboardFormatSpecificDataCompositeKey, MicrosoftWindowsClipboard.getFormatSpecificDataComposite ());
							}
							else if (l_userInput == "D") {
								ClipboardFormatSpecificDataComposite l_clipboardFormatSpecificDataComposite = l_clipbardFormatSpecificDataCompositesHistory.getDataComposite (l_clipboardFormatSpecificDataCompositeKey);
								List <String> l_clipboardDatumFormatNames = l_clipboardFormatSpecificDataComposite.getFormatNames ();
								foreach (String l_clipboardDatumFormatName in l_clipboardDatumFormatNames) {
									ClipboardFormatSpecificDatum l_clipboardFormatSpecificDatum = l_clipboardFormatSpecificDataComposite.getFormatSpecificDatum (l_clipboardDatumFormatName);
									Console.WriteLine (String.Format ("### clipboard datum format name: {0:s}", l_clipboardDatumFormatName));
									Console.Out.Flush ();
								}
							}
						}
						else {
							break;
						}
					}
				}
			}
		}
	}
}

注意として、'Main'(そこの「main (String [] a_argumentsArray)」ではない)メソッド(上には示されていない)には'、[STAThread]'のアノテーションをつけなければならない。

あるテキスト断片を'cmd'ターミナル上でコピーした後、'G' -> 'A' -> 'D' -> 'A'というインプットに対して、以下のようなアウトプットを得る。

@出力
### clipboard datum format name: UnicodeText
### clipboard datum format name: Locale
### clipboard datum format name: Text
### clipboard datum format name: OEMText

あるテキスト断片をLibreOffice Writerインスタンス上でコピーした後、'G' -> 'A' -> 'D' -> 'A'というインプットに対して、以下のようなアウトプットを得る。

@出力
### clipboard datum format name: Star Embed Source (XML)
### clipboard datum format name: Rich Text Format
### clipboard datum format name: Richtext Format
### clipboard datum format name: HTML (HyperText Markup Language)
### clipboard datum format name: HTML Format
### clipboard datum format name: UnicodeText
### clipboard datum format name: Text
### clipboard datum format name: Link
### clipboard datum format name: Star Object Descriptor (XML)


参考資料


<このシリーズの前の記事 | このシリーズの目次 |