tag:crieit.net,2005:https://crieit.net/tags/%E3%82%AF%E3%83%AA%E3%83%83%E3%83%97%E3%83%9C%E3%83%BC%E3%83%89/feed 「クリップボード」の記事 - Crieit Crieitでタグ「クリップボード」に投稿された最近の記事 2022-01-04T23:14:56+09:00 https://crieit.net/tags/%E3%82%AF%E3%83%AA%E3%83%83%E3%83%97%E3%83%9C%E3%83%BC%E3%83%89/feed tag:crieit.net,2005:PublicArticle/17922 2022-01-04T23:14:56+09:00 2022-01-04T23:14:56+09:00 https://crieit.net/posts/chrome-extension-navigator-clipboard-failed-in-http-site-20220104 (Chrome拡張機能) navigator.clipboard を利用したクリップボードへの貼り付けが httpサイト では失敗する <p>先日作成した Chrome拡張機能 が <code>http</code>サイト では上手く動かないことに気付いたので修正しました。</p> <h2 id="経緯"><a href="#%E7%B5%8C%E7%B7%AF">経緯</a></h2> <p>早速作成した Chrome拡張機能 を運用し始めたのですが、一部のサイトでは次のエラーを出力して上手く動かないことが分かりました。</p> <blockquote> <p>Unchecked runtime.lastError: The message port closed before a response was received.</p> </blockquote> <h2 id="原因"><a href="#%E5%8E%9F%E5%9B%A0">原因</a></h2> <p>いくつかのサイトを試験した結果、 <code>http</code>サイト ではこのエラーが出力されることが分かりました。</p> <p>そこで調べてみると……</p> <blockquote> <p>WebExtension の場合、clipboardRead や clipboardWrite パーミッションを要求することで clipboard.readText() や clipboard.writeText() を使うことができます。HTTPサイトに適用されたコンテンツスクリプトは、Clipboard オブジェクトにアクセスすることはできません。</p> <p><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/Clipboard">Clipboard - Web API | MDN</a></p> </blockquote> <p>ビンゴ。 <code>http</code>サイト の Content Script では <code>navigator.clipboard</code> へアクセスできないようです。</p> <h2 id="対処"><a href="#%E5%AF%BE%E5%87%A6">対処</a></h2> <p><code>navigator.clipboard</code> へアクセスできないとなると、手っ取り早いのは <code>document.execCommand('copy')</code> によるクリップぼ度貼り付けです。</p> <p>今回は <code>http</code>サイト という基本レガシーと想定される環境への対処なので、 <code>document.execCommand('copy')</code> で妥協しておきますかね……という感じです。</p> <h3 id="chain.js"><a href="#chain.js">chain.js</a></h3> <pre><code class="js">- chrome.tabs.sendMessage(tab.id, clipText, function(response) { - console.log(response.value); + chrome.tabs.sendMessage( + tab.id, + { + text: clipText, + url: tab.url, + }, + function(response) { + console.log(response.value); </code></pre> <p><code>chrome.tabs.sendMessage()</code> の第二引数は元はシンプルにクリップボードに貼り付けたいテキストを投げていましたが、今回は <code>http://</code> か <code>https://</code> かの判定をしたかったのでURLのみのシンプルなテキストも送ることにしました。そこで、Stringだった引数をObjectに変更。</p> <h3 id="smith.js"><a href="#smith.js">smith.js</a></h3> <pre><code class="js"> chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { // メッセージとして送信されたクリップボードに貼り付けたいテキストをそのままレスポンスに設定して返却 - navigator.clipboard.writeText(request); + if(request.url.toLowerCase().startsWith('https://')) { + navigator.clipboard.writeText(request.text); + } + else { + const inputID = 'chromeExtension-emeraldIsle-clipText'; + console.log(document.querySelector(`#${inputID}`)) + if(document.querySelector(`#${inputID}`) === null) { + const $input = document.createElement('input'); + $input.setAttribute('type', 'text'); + $input.setAttribute('id', inputID); + $input.setAttribute('style', 'position: absolute; left: -100vw; top: 0'); + document.body.appendChild($input); + } + const $inputDom = document.querySelector(`#${inputID}`); + $inputDom.value = request.text; + $inputDom.select(); + document.execCommand('copy'); + } sendResponse({ - value: request, + value: request.text, }); + return true; }); </code></pre> <p>content Scripts 側を大改造。元々は <code>navigator.clipboard.writeText(request);</code> のみのシンプルな内容でしたが、次のように処理を変更。</p> <ol> <li>URLに <code>https://</code> (大文字小文字区別せず) で始まるかどうかを判定</li> <li>始まるならば今まで通りの <code>navigator.clipboard.writeText(request.text);</code> で貼り付け</li> <li>始まらないならば次の処理を実行 <ul> <li>もし指定したIDの要素が存在しないならば <ul> <li>ID付与、画面外領域に <code>input</code>要素 を追加</li> </ul></li> <li>再度指定したIDの <code>input</code>要素 を取得、値にクリップボードに貼り付けたいリンク文字列をセット</li> <li><code>document.execCommand('copy')</code> でクリップボードにコピー</li> </ul></li> </ol> <p>これで <code>http</code>サイト でもリンク文字列をクリップボードに貼り付けられるようになりました。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="Unchecked runtime.lastError: The message port closed before a response was received."><a href="#Unchecked+runtime.lastError%3A+The+message+port+closed+before+a+response+was+received.">Unchecked runtime.lastError: The message port closed before a response was received.</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://onoredekaiketsu.com/unchecked-runtime-lasterror-with-chrome/">Chromeのデベロッパーツールで「Unchecked runtime.lastError:」 | 己で解決!泣かぬなら己で鳴こうホトトギス</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/noenture/items/3978f638f2ffb8ff0995">Unchecked runtime.lastError: The message port closed before a response was received. を回避した一例 - Qiita</a></li> </ul> <h3 id="navigator.clipboard へのアクセス"><a href="#navigator.clipboard+%E3%81%B8%E3%81%AE%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9">navigator.clipboard へのアクセス</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/Clipboard">Clipboard - Web API | MDN</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://note.affi-sapo-sv.com/js-clipbord-copy.php">【JavaScript】 クリップボードへのコピー</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://propg.ee-mall.info/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/javascript/js-http%E7%92%B0%E5%A2%83%E3%81%A7%E3%81%AFnavigator-clipboard-writetext%E3%81%8C%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AB%E3%81%AA%E3%82%8B/">[JS] http環境ではnavigator.clipboard.writeTextがエラーになる | プロプログラマ -Flex,Air,C#,Oracle,HTML5+JS-</a></li> </ul> <h3 id="document.execCommand"><a href="#document.execCommand">document.execCommand</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard#reading_from_the_clipboard">クリップボードとのやりとり - Mozilla | MDN</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand#commands">Document.execCommand() - Web API | MDN</a></li> </ul> <h3 id="JavaScript"><a href="#JavaScript">JavaScript</a></h3> <h4 id="DOM要素の有無"><a href="#DOM%E8%A6%81%E7%B4%A0%E3%81%AE%E6%9C%89%E7%84%A1">DOM要素の有無</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mitsuhiro_K/items/7d5cef1f4316c2080fa7">JavaScriptで要素が存在するかtrue\/falseで知りたい時どうするか? - Qiita</a></li> </ul> <h4 id="テキストボックスに値をセット"><a href="#%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AB%E5%80%A4%E3%82%92%E3%82%BB%E3%83%83%E3%83%88">テキストボックスに値をセット</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mitsuhiro_K/items/7d5cef1f4316c2080fa7">JavaScriptで要素が存在するかtrue\/falseで知りたい時どうするか? - Qiita</a></li> </ul> <h4 id="要素に属性をセット"><a href="#%E8%A6%81%E7%B4%A0%E3%81%AB%E5%B1%9E%E6%80%A7%E3%82%92%E3%82%BB%E3%83%83%E3%83%88">要素に属性をセット</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/Element/setAttribute">element.setAttribute - Web API | MDN</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Traversing_an_HTML_table_with_JavaScript_and_DOM_Interfaces">JavaScript と DOM インターフェイスによる HTML の表の操作 - Web API | MDN</a></li> </ul> <h4 id="body の末尾に DOM を挿入"><a href="#body+%E3%81%AE%E6%9C%AB%E5%B0%BE%E3%81%AB+DOM+%E3%82%92%E6%8C%BF%E5%85%A5">body の末尾に DOM を挿入</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.task-notes.com/entry/20161020/1476974565">【JavaScript】要素を追加するinsertBeforeとappendChildについて - TASK NOTES</a></li> </ul> <h4 id="startWith()"><a href="#startWith%28%29">startWith()</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith">String.prototype.startsWith() - JavaScript | MDN</a></li> </ul> <h4 id="toLowerCase()"><a href="#toLowerCase%28%29">toLowerCase()</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase">String.prototype.toLowerCase() - JavaScript | MDN</a></li> </ul> <h3 id="chrome.tabs.sendMessage, 第二引数 message の型"><a href="#chrome.tabs.sendMessage%2C+%E7%AC%AC%E4%BA%8C%E5%BC%95%E6%95%B0+message+%E3%81%AE%E5%9E%8B">chrome.tabs.sendMessage, 第二引数 message の型</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developer.chrome.com/docs/extensions/reference/tabs/">chrome.tabs - Chrome Developers</a></li> </ul> <p><code>any</code> だった。</p> <h3 id="(未使用) html-loader"><a href="#%28%E6%9C%AA%E4%BD%BF%E7%94%A8%29+html-loader">(未使用) html-loader</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://chocolat5.com/tips/webpack-html-template/">Webpackを使用してhtmlの共通部分をテンプレート化 | chocolat | Freelance Frontend Engineer</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2018/12/10/003219">【小ネタ】webpackでhtmlも扱う - くらげになりたい。</a></li> </ul> <p>一瞬 Background Page を作ってそのテキストボックスにいったん貼り付けて、その値を取得する方法を考えました。</p> <p>今回 Webpack 使用だったので <code>html-loader</code> 案件か、と思いましたが、回避しました。</p> arm-band tag:crieit.net,2005:PublicArticle/17306 2021-05-29T15:25:05+09:00 2021-05-29T15:25:05+09:00 https://crieit.net/posts/vscode-clipboard-format-off-20210529 VSCode からテキストをコピーする際に「書式なし」をデフォルトにする <p>VSCode 上のテキストをコピーして Word に貼り付けたら、背景色なども一緒に付いてきてしまったので「書式なし」をデフォルトに設定します。</p> <h2 id="現象"><a href="#%E7%8F%BE%E8%B1%A1">現象</a></h2> <p>例えば、以下のようなテキストを VSCode からコピーします。</p> <p><a href="https://crieit.now.sh/upload_images/4d113fece8d6baf5006292cbd44737ee60b1de1298e4a.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4d113fece8d6baf5006292cbd44737ee60b1de1298e4a.jpg?mw=700" alt="VSCode 上のテキストをコピー" /></a></p> <p>それを、 Word 等に貼り付けます。</p> <p><a href="https://crieit.now.sh/upload_images/8766f1a82a8dd7ac946249a1329b421760b1de1cdca13.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8766f1a82a8dd7ac946249a1329b421760b1de1cdca13.jpg?mw=700" alt="Word にテキストをコピー" /></a></p> <p>すると、ご覧の通り背景色や文字色、フォントなどの書式も一緒に貼り付けてしまいます。</p> <p>このままだと困るので、 VSCode からコピーする際に「書式なし」でコピーするように設定を変更します。</p> <h2 id="対処"><a href="#%E5%AF%BE%E5%87%A6">対処</a></h2> <p>VSCode の設定を編集します。</p> <p>「ファイル」→「ユーザー設定」→「設定」と進んで、「copy with」と検索すれば、 <code>Editor: Copy With Syntax Highlighting</code> の項目が表示されるはず。</p> <p>そのチェックを外し、 VSCode を再起動。これでコピー時に書式を引き継がないようになります。</p> <p>ちなみに、 <code>settings.json</code> では以下のキーと値を追加すればOK。</p> <pre><code class="json">{ // 略 "editor.copyWithSyntaxHighlighting": false // 略 } </code></pre> <p><a href="https://crieit.now.sh/upload_images/d790b6cb4cfced0d464d151975e5024460b1de27af059.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d790b6cb4cfced0d464d151975e5024460b1de27af059.jpg?mw=700" alt="対処を実施した後、 Word にテキストをコピー" /></a></p> <p>対処後、プレーンな状態でテキストを貼り付けることができるようになりました。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kaityo256/items/d39884c36bd5b35e6427">VSCodeでコピーする時に「書式なし」をデフォルトにする - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ayatokura/items/35fdd2fa828447a0ee6f">【Visual Studio Code】クリップボードに文字列だけを貼り付ける設定方法 - Qiita</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/15248 2019-07-16T10:10:15+09:00 2019-07-16T10:10:15+09:00 https://crieit.net/posts/WPF-5d2d23f770ce9 【WPF】右クリックメニューでデータグリッドの内容をコピーする <p>おはようございます。</p> <p>データグリッドで、<br /> 編集まではさせないけど内容のコピーが出来ると便利なシーンってありますよね。</p> <p>そんな時は、右クリックでコンテキストメニューを表示して操作させるのがいいかもしれません。<br /> ということでやってみました。</p> <p>ついでに、クリップボードの内容を貼り付ける操作と、行のコピーも実装してみます。</p> <p>プログラムは下記の記事のものを流用します。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.doraxdora.com/blog/2017/11/02/post-2971/" target="_blank" rel="noopener noreferrer" data-blogcard="1">【WPF】WebBrowser に PDFファイル を表示する</a></p> <h2 id="画面の修正"><a href="#%E7%94%BB%E9%9D%A2%E3%81%AE%E4%BF%AE%E6%AD%A3">画面の修正</a></h2> <p>まずは、データグリッドにコンテキストメニューを追加します。</p> <p>MainWindow.xaml</p> <pre><code> <DataGrid Name="dataGrid" HorizontalAlignment="Left" Margin="10,31,0,0" Width="497" Height="225" Style="{StaticResource grid-normal}" > <DataGrid.ContextMenu> <ContextMenu> <MenuItem x:Name="ctm_namecopy" Header="名前をコピー(_C)" Click="ctm_namecopy_Click"/> <MenuItem x:Name="ctm_namepaste" Header="名前を貼り付け(_V)" Click="ctm_namepaste_Click"/> <MenuItem x:Name="ctm_rowcopy" Header="行をCSVとしてコピー(_R)" Click="ctm_rowcopy_Click"/> </ContextMenu> </DataGrid.ContextMenu> <DataGrid.Columns> <DataGridTemplateColumn IsReadOnly="True" Header="選択" Width="50"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn Binding="{Binding No}" ClipboardContentBinding="{x:Null}" Header="No" IsReadOnly="True" Width="40" /> <DataGridTextColumn Binding="{Binding Name}" ClipboardContentBinding="{x:Null}" Header="名前" IsReadOnly="False" Width="80"/> <DataGridTemplateColumn IsReadOnly="True" Header="性別" CellStyle="{StaticResource dgc-combo}" Width="50"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <ComboBox Name="cmbGender" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid<span>}</span><span>}</span>, Path=DataContext.GenderList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" SelectedValuePath="Cd" SelectedValue="{Binding Sex}" Width="50" Background="Transparent"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn Binding="{Binding Age}" ClipboardContentBinding="{x:Null}" Header="年齢" IsReadOnly="False" Width="40"/> <DataGridTemplateColumn IsReadOnly="True" Header="種別" CellStyle="{StaticResource dgc-combo}" Width="110"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <ComboBox Name="cmbKind" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid<span>}</span><span>}</span>, Path=DataContext.KindList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="KindName" SelectedValuePath="KindCd" SelectedValue="{Binding Kind}" Width="110" Background="Transparent"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn Binding="{Binding Favorite}" ClipboardContentBinding="{x:Null}" Header="好物" IsReadOnly="False" Width="*"/> </DataGrid.Columns> </DataGrid> </code></pre> <h2 id="プログラムの修正"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E4%BF%AE%E6%AD%A3">プログラムの修正</a></h2> <h3 id="コピーメソッドの追加"><a href="#%E3%82%B3%E3%83%94%E3%83%BC%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E8%BF%BD%E5%8A%A0">コピーメソッドの追加</a></h3> <p>次のようにメソッドを実装します。<br /> MainWindow.xaml.cs</p> <pre><code> /// <summary> /// 名前をクリップボードにコピーします. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ctm_namecopy_Click(object sender, RoutedEventArgs e) { // 選択中のデータを取得 CatModel cat = this.dataGrid.SelectedItem as CatModel; if (cat != null) { try { // クリップボードにコピー Clipboard.SetText(cat.Name); } catch (Exception ex) { } } } </code></pre> <p> </p> <p>データベース上で選択された行の、名前列をクリップボードにコピーします。<br /> 簡単なものですね。</p> <h3 id="貼り付けメソッドの追加"><a href="#%E8%B2%BC%E3%82%8A%E4%BB%98%E3%81%91%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E8%BF%BD%E5%8A%A0">貼り付けメソッドの追加</a></h3> <p>次のようにメソッドを実装します。<br /> MainWindow.xaml.cs</p> <pre><code> /// <summary> /// クリップボードの名前を貼り付け /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ctm_namepaste_Click(object sender, RoutedEventArgs e) { // 選択中のデータを取得 CatModel cat = this.dataGrid.SelectedItem as CatModel; if (cat != null) { String name = Clipboard.GetText(); cat.Name = name; this.dataGrid.Items.Refresh(); } } </code></pre> <p> </p> <p>選択された行の名前列の内容をクリップボードの内容で上書きします。<br /> リフレッシュメソッドを呼び出すことで内容が即座に反映されます。</p> <h3 id="行コピーメソッドの追加"><a href="#%E8%A1%8C%E3%82%B3%E3%83%94%E3%83%BC%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E8%BF%BD%E5%8A%A0">行コピーメソッドの追加</a></h3> <p>次のようにメソッドを実装します。<br /> MainWindow.xaml.cs</p> <pre><code> /// <summary> /// 行をカンマ区切り文字列にしてクリップボードにコピーします. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ctm_rowcopy_Click(object sender, RoutedEventArgs e) { // 選択中のデータを取得 CatModel cat = this.dataGrid.SelectedItem as CatModel; if (cat != null) { // データをCSV形式に変換 String delmiter = ","; StringBuilder sb = new StringBuilder(); sb.Append(cat.No).Append(delmiter); sb.Append(cat.Name).Append(delmiter); sb.Append(cat.Sex).Append(delmiter); sb.Append(cat.Age).Append(delmiter); KindConverter kindConverter = new KindConverter(); sb.Append(kindConverter.Convert(cat.Kind, null,null,null)).Append(delmiter); sb.Append(cat.Favorite); try { // クリップボードにコピー Clipboard.SetText(sb.ToString()); } catch (Exception ex) { } } } </code></pre> <p> </p> <p>こちらはコピーの応用で、選択された行の内容をカンマ区切りに編集し<br /> クリップボードにコピーします。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>思っていたより簡単に実装できました。<br /> クライアントソフトならではの手軽さですかね。<br /> (WEBはセキュリティとかの問題でコピーとかが意外に面倒)</p> <p>コンテキストメニュー自体は階層も持たすことができますし、<br /> 色々なことに利用できそうですね。<br /> 何か思いついたらやってみようと思います。</p> <p>ではでは。</p> doraxdora