「Windowsのクリップボードを用いた選択コンテンツの取得」の版間の差分

Notion-MW
 
Notion-MW
 
(同じ利用者による、間の7版が非表示)
47行目: 47行目:
コード例は以下である。
コード例は以下である。


<pre class="dummy_str_c#">class CBSample
<syntaxhighlight lang="c#">class CBSample
{
{
     static string[] unsupported_formats = new string[] {&quot;Object Descriptor&quot;};
     static string[] unsupported_formats = new string[] {"Object Descriptor"};


     [STAThread]
     [STAThread]
60行目: 60行目:
         System.Windows.Forms.IDataObject cb1 = new DataObject();
         System.Windows.Forms.IDataObject cb1 = new DataObject();
         System.Windows.Forms.IDataObject cb2 = new DataObject();
         System.Windows.Forms.IDataObject cb2 = new DataObject();
         MessageBox.Show(&quot;This app will save clipboard two times and then restore two times.&quot;);
         MessageBox.Show("This app will save clipboard two times and then restore two times.");
         this.backupCBto(ref cb1);
         this.backupCBto(ref cb1);
         MessageBox.Show(&quot;clipboard 1 is saved.&quot;);
         MessageBox.Show("clipboard 1 is saved.");
         this.backupCBto(ref cb2);
         this.backupCBto(ref cb2);
         MessageBox.Show(&quot;clipboard 2 is saved.&quot;);
         MessageBox.Show("clipboard 2 is saved.");
         this.restoreCBfrom(ref cb1);
         this.restoreCBfrom(ref cb1);
         MessageBox.Show(&quot;clipboard 1 is restored.&quot;);
         MessageBox.Show("clipboard 1 is restored.");
         this.restoreCBfrom(ref cb2);
         this.restoreCBfrom(ref cb2);
         MessageBox.Show(&quot;clipboard 2 is restored.&quot;);
         MessageBox.Show("clipboard 2 is restored.");
     }
     }
     private void backupCBto(ref System.Windows.Forms.IDataObject cb)
     private void backupCBto(ref System.Windows.Forms.IDataObject cb)
77行目: 77行目:
         foreach (string fmt in cb_raw.GetFormats(false))
         foreach (string fmt in cb_raw.GetFormats(false))
         {
         {
             if (!Array.Exists(unsupported_formats, s =&gt; s == fmt)) { Console.WriteLine(fmt); cb.SetData(fmt, cb_raw.GetData(fmt)); }
             if (!Array.Exists(unsupported_formats, s => s == fmt)) { Console.WriteLine(fmt); cb.SetData(fmt, cb_raw.GetData(fmt)); }
         }
         }
     }
     }
85行目: 85行目:
         Clipboard.SetDataObject(cb, true);
         Clipboard.SetDataObject(cb, true);
     }
     }
}</pre>
}</syntaxhighlight>
このサンプルでは、クリップボードを2回にわたって別々の変数に格納し、それを再び復元することができる。
このサンプルでは、クリップボードを2回にわたって別々の変数に格納し、それを再び復元することができる。


91行目: 91行目:


* ペイントやWordは以前からテストにもよく使っていたし、以前は問題なく動いていた気がするのだが…時期としては<u>2023年6月時点</u>で不具合に気付いたので修正を行った。
* ペイントやWordは以前からテストにもよく使っていたし、以前は問題なく動いていた気がするのだが…時期としては<u>2023年6月時点</u>で不具合に気付いたので修正を行った。
* ところで、GetDataObject(に限らずClipboard関連関数全般?)はMainに<code>&#91;STAThread&#93;</code>が指定されていないとnullしか返ってこないので注意。


==== 実験 ====
==== 実験 ====
167行目: 168行目:
に従い、コンストラクタの冒頭に
に従い、コンストラクタの冒頭に


<pre class="dummy_str_visual_basic">this.Visible = false;
<syntaxhighlight lang="c#">this.Visible = false;
this.WindowState = FormWindowState.Minimized;
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;</pre>
this.ShowInTaskbar = false;</syntaxhighlight>
を入れると、一見消えているように見えるが、Alt+Tabなどをすると表示されているのが見えてしまうので、自分のHWNDを対象にSW_HIDEを指定してShowWindowをすると完全に消える。
を入れると、一見消えているように見えるが、Alt+Tabなどをすると表示されているのが見えてしまうので、自分のHWNDを対象にSW_HIDEを指定してShowWindowをすると完全に消える。


<pre class="dummy_str_visual_basic">case WM_SHOWWINDOW:
<syntaxhighlight lang="c#">case WM_SHOWWINDOW:
                 this.CenterToScreen();
                 this.CenterToScreen();
                 if ((int)message.WParam == 1)
                 if ((int)message.WParam == 1)
178行目: 179行目:
                     if (!window_init_done)
                     if (!window_init_done)
                     {//being shown
                     {//being shown
                         Console.WriteLine(&quot;Hiding&quot;);
                         Console.WriteLine("Hiding");
                         window_init_done = true;
                         window_init_done = true;
                         ShowWindow(this.Handle, SW_HIDE);
                         ShowWindow(this.Handle, SW_HIDE);
                     }
                     }
                 }
                 }
                 break;</pre>
                 break;</syntaxhighlight>
==== conhostでの実行 ====
==== conhostでの実行 ====


しばしば実行が止まってしまうのでconhostで実行しないこと。Windows Terminalなどを用いる。詳細は[[Windowsでのターミナル環境|Windowsでのターミナル環境]]を参照。
しばしば実行が止まってしまうのでconhostで実行しないこと。Windows Terminalなどを用いる。詳細は[[Windowsでのターミナル環境|Windowsでのターミナル環境]]を参照。
<span id="net-frameworkのバージョン"></span>
==== .NET (Framework)のバージョン ====
Windows 11などで普通にVisual Studioを入れると使われるバージョンが.NET 6などになってしまい、ランタイムが無いWindowsでは動かない。そこで、.NET Framework 4.6.2や3.5など、デフォルトのWindowsで動くバージョンを使ったビルドも提供している。
Windowsのバージョンと.NETのバージョンの対応状況はだいたい以下のような感じ。
[https://qiita.com/kawaidainf/items/82cb8db41299587f30e6 Windowsや IIS や .NET などのバージョン対応メモ &#35;Windows &#45; Qiita]
[https://learn.microsoft.com/ja-jp/dotnet/framework/migration-guide/versions-and-dependencies .NET Framework および Windows OS バージョン &#45; .NET Framework | Microsoft Learn]
[https://learn.microsoft.com/ja-jp/dotnet/framework/install/guide-for-developers .NET Framework Developer Pack または再頒布可能パッケージをインストールするには &#45; .NET Framework | Microsoft Learn]
3.5は古く、Windows 7を含む最も多くの環境で動作するが、クリップボードの一部の内容が失われる(Wordでやったときにフォント情報が消えるとか)ことがある。4.0以降ではこの問題はなさそうなので、4.0系の中で現在もサポートされている最も古いバージョンである4.6.2を使うのが良さそうである。
.NET Framework向けにビルドするには、まず該当のバージョンをVisual Studio Installerあるいは[https://dotnet.microsoft.com/ja-jp/download/visual-studio-sdks?cid=getdotnetsdk Visual Studio 用の .NET SDK のダウンロード]などからインストールする(SDKがあればTargeting Packは多分不要)。プロジェクト作成時には「(.NET Framework)」と付いているものを選ぶ(参考&#58; [https://learn.microsoft.com/en-us/answers/questions/959314/targeted-frameworks-not-showing-options-for-net-fr Targeted frameworks not showing options for .NET Framework 4.8 VS2022 &#45; Microsoft Q&amp;A])。ソースファイルは複数プロジェクトで共有できる(「既存の項目」の追加時に「リンクとして追加」する)(参考&#58; [https://stackoverflow.com/questions/1116465/how-do-you-share-code-between-projects-solutions-in-visual-studio .net &#45; How do you share code between projects/solutions in Visual Studio? &#45; Stack Overflow])。また、これに伴ってTupleなど一部の新しい機能を使わないようにソースを変更した。
* ValueTupleを.NET Frameworkで使うのは面倒そう。特に4.6.2以前。[https://stackoverflow.com/questions/38382971/predefined-type-system-valuetuple%C2%B42%C2%B4-is-not-defined-or-imported c&#35; &#45; Predefined type &#39;System.ValueTuple´2´ is not defined or imported &#45; Stack Overflow]など


<span id="クリップボード履歴windows10以降"></span>
<span id="クリップボード履歴windows10以降"></span>
215行目: 235行目:
C&#35;の<code>NamedPipeClientStream</code>ではパイプの名前を<code>&quot;.&quot;</code> <code>&quot;test&quot;</code>とコンピュータ名/パイプ名で分けているのに対して(AutoHotkeyで呼び出されている)Win32 APIの関数<code>CreateNamedPipe</code>では<code>&quot;\\.\</code><strong><code>pipe\</code></strong><code>test&quot;</code>と書く必要があるようなので注意。
C&#35;の<code>NamedPipeClientStream</code>ではパイプの名前を<code>&quot;.&quot;</code> <code>&quot;test&quot;</code>とコンピュータ名/パイプ名で分けているのに対して(AutoHotkeyで呼び出されている)Win32 APIの関数<code>CreateNamedPipe</code>では<code>&quot;\\.\</code><strong><code>pipe\</code></strong><code>test&quot;</code>と書く必要があるようなので注意。


<pre class="dummy_str_visual_basic">using System;
<syntaxhighlight lang="c#">using System;
using System.IO.Pipes;
using System.IO.Pipes;


226行目: 246行目:
     public static void Main(string[] args)
     public static void Main(string[] args)
     {
     {
       NamedPipeClientStream npcs = new NamedPipeClientStream(&quot;.&quot;, &quot;test&quot;, PipeDirection.InOut);
       NamedPipeClientStream npcs = new NamedPipeClientStream(".", "test", PipeDirection.InOut);
       npcs.Connect();
       npcs.Connect();
       byte[] dat = System.Text.Encoding.Unicode.GetBytes(&quot;XYZ&quot;);
       byte[] dat = System.Text.Encoding.Unicode.GetBytes("XYZ");
       byte[] buf = new byte[6];
       byte[] buf = new byte[6];
       if (npcs.IsConnected == true) {
       if (npcs.IsConnected == true) {
240行目: 260行目:
     }
     }
   }
   }
}</pre>
}</syntaxhighlight>
<pre class="dummy_str_visual_basic">ptr := A_PtrSize ? &quot;Ptr&quot; : &quot;UInt&quot;
<syntaxhighlight lang="vb.net">ptr := A_PtrSize ? "Ptr" : "UInt"
char_size := A_IsUnicode ? 2 : 1
char_size := A_IsUnicode ? 2 : 1
pipe := DllCall(&quot;CreateNamedPipe&quot;, &quot;str&quot;, &quot;\\.\pipe\test&quot;, &quot;uint&quot;, 3, &quot;uint&quot;, 4, &quot;uint&quot;, 10, &quot;uint&quot;, 100, &quot;uint&quot;, 100, &quot;uint&quot;, 0, ptr, 0, ptr)
pipe := DllCall("CreateNamedPipe", "str", "\\.\pipe\test", "uint", 3, "uint", 4, "uint", 10, "uint", 100, "uint", 100, "uint", 0, ptr, 0, ptr)
DllCall(&quot;ConnectNamedPipe&quot;, ptr, pipe, ptr, 0)
DllCall("ConnectNamedPipe", ptr, pipe, ptr, 0)
MsgBox ConnectNamedPipe(0 is OK): %ErrorLevel%/%A_LastError%
MsgBox ConnectNamedPipe(0 is OK): %ErrorLevel%/%A_LastError%
VarSetCapacity(Buffer, char_size * 3)
VarSetCapacity(Buffer, char_size * 3)
DllCall(&quot;WriteFile&quot;, ptr, pipe, &quot;str&quot;, &quot;345&quot;, &quot;uint&quot;, char_size * 3, &quot;uint*&quot;, 0, ptr, 0)
DllCall("WriteFile", ptr, pipe, "str", "345", "uint", char_size * 3, "uint*", 0, ptr, 0)
DllCall(&quot;ReadFile&quot;, &quot;UInt&quot;, pipe, &quot;Str&quot;, Buffer, &quot;UInt&quot;, char_size * 3, &quot;UIntP&quot;, BytesRead, &quot;UInt&quot;, 0)
DllCall("ReadFile", "UInt", pipe, "Str", Buffer, "UInt", char_size * 3, "UIntP", BytesRead, "UInt", 0)
MsgBox, 64, %BytesRead%, %Buffer%</pre>
MsgBox, 64, %BytesRead%, %Buffer%</syntaxhighlight>
なお、これは一応(Windows11で)動いた例というだけで、使用されている関数の細かいパラメータやエラーハンドリング、Closeの仕方、Unicode/非Unicodeや32bit/64bitの扱い、バイト数の計算の仕方など、不完全な部分がいくつもあると思われる。
なお、これは一応(Windows11で)動いた例というだけで、使用されている関数の細かいパラメータやエラーハンドリング、Closeの仕方、Unicode/非Unicodeや32bit/64bitの扱い、バイト数の計算の仕方など、不完全な部分がいくつもあると思われる。