5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PowerShell】Windows標準機能でつくるクリップボード履歴ツール

Posted at

やりたいこと

クリップボードが更新されると自動でリストに追加してくれるツールです。

クリップボード履歴ツール_demo.gif

世の中には高機能なクリップボード履歴ツールが数多く存在しますが、職場のセキュリティポリシーなどでフリーソフトの利用が制限され使えないケースも少なくありません(私の職場もその一つです…)。
そこで、Windows標準搭載のPowerShellを使って、自作してみることにしました。
Windows標準のクリップボード履歴ツールにイメージは近いですが、自分がほしい機能を追加して差別化を図ります。

なぜ、PowerShellなのか?

PowerShellでGUIツールを作る主なメリットは次のとおりです。

  • 手軽さ:
    Windows PCがあれば追加のインストールがほとんど不要で、すぐに開発を始められる。
    一方、PythonやC#などの言語は利用できる環境が限られる

  • 配布のしやすさ:
    スクリプトファイルを渡すだけで利用できるため、他のPCへの共有が簡単。
    コンパイル不要なので、環境構築の手間も最小限。

  • 機能性:
    Windows標準搭載の .NET Frameworkという基盤を直接利用できるため、本格的なGUIアプリケーションも作成可能。

作業の自動化だけでなく、ちょっとした便利ツールも作れるPowerShell。
その可能性が少しでも伝われば幸いです。

完成したスクリプト

クリックして展開
# 必要なアセンブリをロード
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# --- グローバル設定用の変数 ---
$FontName = "Meiryo UI" # 使用するフォント名を定義

# --- レイアウト定数 ---
$formWidth = 270 
$formHeight = 360 
$edgeMargin = 15 # フォームの端からのマージンを設定
$copyButtonWidth = 80 # コピーボタンの幅を定義
$copyButtonHeight = 30 # コピーボタンの高さを定義
$controlGroupTopMargin = 15 # コントロールグループ(ボタンなど)の上マージンを定義
$checkBoxWidth = 125 # チェックボックスの幅を定義
$checkBoxHeight = 20 # チェックボックスの高さを定義
$horizontalGapButtonToCheckBoxGroup = 20 # ボタンとチェックボックスグループ間の水平方向の間隔
$verticalGapCheckBox = 5 # チェックボックス間の垂直方向の間隔

# --- メインフォームの作成とプロパティ設定 ---
$mainForm = [System.Windows.Forms.Form]@{
    Text = "クリップボード履歴ツール" # ウィンドウのタイトルを設定
    Width = $formWidth              # ウィンドウの幅を設定
    Height = $formHeight            # ウィンドウの高さを設定
    StartPosition = "CenterScreen"  # フォームを画面中央に表示
    FormBorderStyle = "FixedSingle" # フォームの枠を固定し、サイズ変更を不可にする
    MaximizeBox = $false            # 最大化ボタンを無効にする
    TopMost = $true                 # 最前面表示に固定
}

# フォームのインスタンスが作成された直後にClientSizeを取得
# これがコントロール配置の基準となるクライアント領域のサイズ
$actualClientWidth = $mainForm.ClientSize.Width

# --- データグリッドの作成とプロパティ設定 ---
$dataGridView = [System.Windows.Forms.DataGridView]@{
    ColumnCount = 1                # 列数を1に設定
    Font = [System.Drawing.Font]::new($FontName, 8) # データグリッドのフォントを設定
    RowHeadersVisible = $false     # 行ヘッダー(左側の行番号)を非表示にする
    Width = $actualClientWidth - $edgeMargin * 2 # クライアント領域の幅から左右のマージンを引いた幅
    Left = $edgeMargin             # フォームの左端から指定マージン離して配置
    Top = $edgeMargin              # フォームの上端から指定マージン離して配置
    Height = 230                   # データグリッドの高さを固定
}
# データグリッドの列ヘッダーを設定
$dataGridView.Columns[0].Name = "クリップボード履歴" # 1列目の列名を「クリップボード履歴」に設定
$dataGridView.Columns[0].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill # 列幅をデータグリッドの幅に合わせて自動調整

# --- 「Copy」ボタンの作成 ---
$copyButton = [System.Windows.Forms.Button]@{
    Text = "Copy"                 # ボタンに表示するテキスト
    Font = [System.Drawing.Font]::new($FontName, 9) # ボタンのフォントを設定
    Width = $copyButtonWidth      # ボタンの幅を設定
    Height = $copyButtonHeight    # ボタンの高さを設定
    Left = $edgeMargin            # フォームの左端からマージン分配置
    Top = $dataGridView.Bottom + $controlGroupTopMargin # データグリッドの最下部から指定マージン下に配置
}

# --- 最前面固定チェックボックスの作成 ---
$alwaysOnTopCheckBox = [System.Windows.Forms.CheckBox]@{
    Text = "最前面に固定"          # チェックボックスのテキスト
    Font = [System.Drawing.Font]::new($FontName, 8) # フォント設定
    Width = $checkBoxWidth         # 幅
    Height = $checkBoxHeight       # 高さ
    Checked = $true                # 初期状態でチェックを入れておく
    Left = $copyButton.Right + $horizontalGapButtonToCheckBoxGroup # Copyボタンの右に水平方向のマージンを開けて配置
    Top = $copyButton.Top          # Copyボタンと垂直方向の位置を揃える
}

# --- 非アクティブ時半透明チェックボックスの作成 ---
$transparencyCheckBox = [System.Windows.Forms.CheckBox]@{
    Text = "非アクティブ時に半透明" # チェックボックスのテキスト
    Font = [System.Drawing.Font]::new($FontName, 8) # フォント設定
    Width = $checkBoxWidth         # 幅
    Height = $checkBoxHeight       # 高さ
    Checked = $true                # 初期状態でチェックを入れておく
    Left = $alwaysOnTopCheckBox.Left # 最前面固定チェックボックスと同じ水平位置に配置
    Top = $alwaysOnTopCheckBox.Bottom + $verticalGapCheckBox # 最前面固定チェックボックスの直下に配置
}

# --- コントロールをメインフォームに追加 ---
$mainForm.Controls.Add($dataGridView) # 作成したデータグリッドをフォームに追加
$mainForm.Controls.Add($copyButton) # 作成したCopyボタンをフォームに追加
$mainForm.Controls.Add($alwaysOnTopCheckBox) # 作成した最前面固定チェックボックスをフォームに追加
$mainForm.Controls.Add($transparencyCheckBox) # 作成した非アクティブ時半透明チェックボックスをフォームに追加

# --- クリップボード監視機能の準備(タイマーポーリング方式) ---
$script:LastClipboardContent = "" # 前回のクリップボード内容を保持するグローバル変数
$clipboardMonitorTimer = [System.Windows.Forms.Timer]::new() # タイマーのインスタンスを作成
$clipboardMonitorTimer.Interval = 2000 # 2000ミリ秒 (2秒) ごとにチェックを実行
$clipboardMonitorTimer.Enabled = $false # 初期状態ではタイマーを無効にしておく

$script:Timer_Tick = {
    # 現在のクリップボード内容を取得
    $currentClipboardContent = [System.Windows.Forms.Clipboard]::GetText()

    # 前回の内容と比較し、異なっていれば履歴としてデータグリッドに追加
    if ($currentClipboardContent -ne $script:LastClipboardContent) {
        $script:LastClipboardContent = $currentClipboardContent # 新しい内容をLastClipboardContentに記録
        $dataGridView.Rows.Add($currentClipboardContent) # データグリッドに行を追加
        
        # データグリッドのスクロール位置を調整し、最新の履歴が見えるようにする
        if ($dataGridView.Rows.Count -gt 0) {
            $dataGridView.FirstDisplayedScrollingRowIndex = $dataGridView.Rows.Count - 1
        }
    }
}
$clipboardMonitorTimer.add_Tick($script:Timer_Tick) # タイマーのTickイベントにスクリプトブロックを登録

# --- 「Copy」ボタンのクリックイベントハンドラの定義 ---
$script:CopyButton_Click = {
    if ($dataGridView.SelectedCells.Count -gt 0) {
       # 選択されたセルを行番号でソート
       $sortedCells = $dataGridView.SelectedCells | Sort-Object { $_.RowIndex }
       # 選択されたテキストを格納する配列
       $selectedTexts = @()
       # ソート後のセルをループ処理し、そのテキスト値を取得
       foreach ($cell in $sortedCells) {
            if ($cell.Value -ne $null) {
                $selectedTexts += $cell.Value.ToString()  # セルの値があれば文字列として配列に追加
            } else {
                $selectedTexts += "" # セルが空の場合は空文字列を追加
            }
        }
        # 有効な文字列が1つでもあればクリップボード出力処理
        if ($selectedTexts.Count -gt 0) {
            # クリップボードへのコピー前に、比較用変数に代入
            # (コピーボタン押下時にデータグリッドへの出力を防ぐため)
            $script:LastClipboardContent = $concatedText
            # 配列を結合し、クリップボードにコピー
            $concatedText = $selectedTexts -join "`r`n"  # 結合処理
            Set-Clipboard -Value $concatedText  # クリップボードへコピー
        }
    } 
}
$copyButton.add_Click($script:CopyButton_Click)

# --- 最前面固定チェックボックスのイベントハンドラ ---
$script:AlwaysOnTopCheckBox_CheckedChanged = {
    # チェックボックスの状態に応じてフォームのTopMostプロパティ(最前面表示)を設定
    $mainForm.TopMost = $alwaysOnTopCheckBox.Checked
}
$alwaysOnTopCheckBox.add_CheckedChanged($script:AlwaysOnTopCheckBox_CheckedChanged) # CheckedChangedイベントにスクリプトブロックを登録

# --- フォームのアクティブ/非アクティブイベントハンドラ (半透明機能用) ---
$script:MainForm_Activated = {
    # フォームがアクティブになったら常に不透明(Opacity 1.0)に戻す
    $mainForm.Opacity = 1.0
}
$mainForm.add_Activated($script:MainForm_Activated) # Activatedイベントにスクリプトブロックを登録

$script:MainForm_Deactivate = {
    # 半透明チェックボックスがオンの場合のみ、フォーム非アクティブ時に半透明(Opacity 0.7)にする
    if ($transparencyCheckBox.Checked) {
        $mainForm.Opacity = 0.7
    }
}
+$mainForm.add_Deactivate($script:MainForm_Deactivate) # Deactivateイベントにスクリプトブロックを登録

# メインフォームがロードされたときにタイマーを開始
$mainForm.add_Load({
    Set-Clipboard  # クリップボードを初期化
    $clipboardMonitorTimer.Start()

})

# メインフォームが閉じられるときにタイマーを停止・解放
$mainForm.add_FormClosed({
    $clipboardMonitorTimer.Stop()
    $clipboardMonitorTimer.Dispose()
})

# --- フォーム表示 ---
$null = $mainForm.ShowDialog()

# --- フォームが閉じられた後の後処理 ---
$mainForm.Dispose()

実際の作成の流れ

このセクションでは、ツール作成の具体的な手順を踏みながら解説していきます。

1. ウィンドウの作成と表示
2. データグリッドの追加
3. クリップボード履歴の取得と表示
4. 機能追加① Copyボタンの追加
5. 機能追加②最前面固定機能とウィンドウ半透明化機能の追加
6. 起動用バッチの作成


0. 環境

  • バージョン
    PowerShell 5.1 (Windows 10/11標準)

  • エディタ:
    Windows標準のPowerShell ISEでも問題なく動作します。起動方法などは割愛。
    より本格的に開発したい方には、高機能なコードエディタであるVS Code (Visual Studio Code)の導入をおすすめします。
    (ちなみに自分はプライベートでCursorを利用しています)

PowerShellの基本的な書き方等について、本稿では殆ど触れません。


1. ウィンドウの作成と表示

PowerShellでGUIアプリケーションを作る第一歩は、土台となるウィンドウ(フォーム)の作成です。ここが、ツールが表示される「キャンバス」になります。

必要な「部品」の読み込み

GUI部品(コントロール)を使用するには、PowerShellにそのためのライブラリを読み込ませる必要があります。(Pythonのimportに近い印象です)
これらのライブラリは「アセンブリ」と呼ばれ、Windowsアプリケーション開発の標準的な基盤である.NET Frameworkに含まれています。

GUIを扱うために最低限必要なSystem.Windows.Formsと、色やフォントを扱うためのSystem.Drawingを読み込みます。

# 必要なアセンブリをロード
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

フォームのインスタンス作成

次に、表示するウィンドウそのものを作成します。これは[System.Windows.Forms.Form]という型でインスタンスを作成し、同時にウィンドウのタイトルや初期サイズ、画面上の表示位置、ユーザーによるサイズ変更の可否といった基本的な特性を設定します。

# --- レイアウト定数 ---
$formWidth = 270 
$formHeight = 360 

# --- メインフォームの作成とプロパティ設定 ---
$mainForm = [System.Windows.Forms.Form]@{
    Text = "クリップボード履歴ツール" # ウィンドウのタイトル
    Width = $formWidth              # ウィンドウの幅
    Height = $formHeight            # ウィンドウの高さ
    StartPosition = "CenterScreen"  # 画面中央に表示
    FormBorderStyle = "FixedSingle" # サイズ変更不可にする
    MaximizeBox = $false            # 最大化ボタンを無効にする
}
スプラッティングを使用したプロパティ指定

本稿のオブジェクトのプロパティ設定には型アクセラレータの直後にハッシュテーブルを渡すスプラッティング技法を用いています。

$mainForm = [System.Windows.Forms.Form]@{
    Text = "クリップボード履歴ツール"
    Width = $formWidth
      :
}

これは、

$mainForm = New-Object System.Windows.Forms.Form
$mainForm.Text = "クリップボード履歴ツール"
$mainForm.Width = $formWidth
   :

のように「インスタンス作成 → 個別にプロパティ設定」する書き方と中身はだいたい同じです。ハッシュテーブルを渡す形式のほうがコードの見通しがよいため、前者を採用しています。
(ハッシュテーブルを渡す形式は自分自身を同じタイミングで参照できないため、その場合は特定のプロパティだけ後から設定し直すなど工夫が必要です)

フォームの表示

作成したフォームは、最後に$mainForm.ShowDialog()メソッドを呼び出すことで、初めて画面に表示されます。このメソッドは、表示されたウィンドウが閉じられるまでスクリプトの実行を一時停止するという特徴があります。

表示後、フォームが使用していたメモリなどのリソースを解放するための処理も追加します。

# --- フォーム表示 ---
$null = $mainForm.ShowDialog()

# --- フォームが閉じられた後の後処理 ---
$mainForm.Dispose()

$null = を先頭につけているのは、ShowDialog()が返す戻り値(ここでは通常DialogResult.OKなど)をコンソールに出力しないようにするためです。

ここまでの結果

スクリーンショット 2025-07-23 062807.png

現時点のソースコード(クリックして展開)
# 必要なアセンブリをロード
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# --- レイアウト定数 ---
$formWidth = 270 
$formHeight = 360 

# --- メインフォームの作成とプロパティ設定 ---
$mainForm = [System.Windows.Forms.Form]@{
    Text = "クリップボード履歴ツール" # ウィンドウのタイトルを設定
    Width = $formWidth              # ウィンドウの幅を設定
    Height = $formHeight            # ウィンドウの高さを設定
    StartPosition = "CenterScreen"  # フォームを画面中央に表示
    FormBorderStyle = "FixedSingle" # フォームの枠を固定し、サイズ変更を不可にする
    MaximizeBox = $false            # 最大化ボタンを無効にする
}

# --- フォーム表示 ---
$null = $mainForm.ShowDialog() # フォームを表示し、ユーザーが閉じるまでスクリプトを一時停止

# --- フォームが閉じられた後の後処理 ---
$mainForm.Dispose() # フォームが使用していたリソースを解放

2. データグリッドの追加

次に、クリップボードの履歴を表示するためのリスト部分を作成します。ここでは、データを表形式で表示するのに便利なデータグリッド(DataGridView)を使います。

コントロールレイアウトの基本

GUIコントロールをフォーム内に配置するには、主にLeft(フォームの左端からの距離)、Top(フォームの上端からの距離)、Width(幅)、Height(高さ)といったプロパティを設定します。

$dataGridView = [System.Windows.Forms.DataGridView]@{
     :
    Width = $actualClientWidth - $edgeMargin * 2 # フォーム幅から左右マージンを引く
    Left = $edgeMargin             # 左マージンから開始
    Top = $edgeMargin              # 上マージンから開始
    Height = 230                   # 高さは固定

DataGridView の作成と設定

[System.Windows.Forms.DataGridView] のインスタンスを作成し、表示形式や列の設定を行います。今回は履歴を表示するシンプルなリストなので、行ヘッダー(左端の行番号が表示される部分)は非表示にし、「クリップボード履歴」という単一の列を追加します。列の幅は、データグリッドのサイズに合わせて自動的に調整されるよう設定します。


# --- グローバル設定用の変数 ---
$FontName = "Meiryo UI" # フォント名を定義(ここでは例として追加)

# フォームのインスタンスが作成された直後にClientSizeを取得
# これがコントロール配置の基準となるクライアント領域のサイズ
$actualClientWidth = $mainForm.ClientSize.Width # mainFormのコードの後に記述されている必要があります

# --- データグリッドの作成とプロパティ設定 ---
$dataGridView = [System.Windows.Forms.DataGridView]@{
    ColumnCount = 1                # 列数を1に設定
    Font = [System.Drawing.Font]::new($FontName, 8) # フォントを設定
    RowHeadersVisible = $false     # 行ヘッダー(左側の行番号)を非表示
    Width = $actualClientWidth - $edgeMargin * 2 # フォーム幅から左右マージンを引く
    Left = $edgeMargin             # 左マージンから開始
    Top = $edgeMargin              # 上マージンから開始
    Height = 230                   # 高さは固定
}

# データグリッドの列ヘッダーを設定
$dataGridView.Columns[0].Name = "クリップボード履歴" # 列のタイトルを設定
$dataGridView.Columns[0].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill # 列幅を自動調整

ClientSizeについて
コントロールを配置する際にLeftやTopで指定する座標は、フォームのクライアント領域の左上(0,0)が起点となります。フォームのWidthやHeightプロパティはウィンドウ全体のサイズ(タイトルバーや枠線を含む)を示すのに対し、ClientSize.WidthやClientSize.Heightは、実際にコントロールを配置できる内側の領域のサイズを示します。

フォームのサイズを参照する際は、必要に応じClientSize.WidthやClientSize.Heightを使うことで、OSのテーマなどの影響を受けず、意図した位置に配置できます。

フォームへの配置

作成したデータグリッドは、$mainForm.Controls.Add()メソッドを使ってフォームに追加します。これにより、データグリッドがフォームの「子」コントロールとして認識され、画面に表示されるようになります。

# --- コントロールをメインフォームに追加 ---
$mainForm.Controls.Add($dataGridView)

ここまでの結果
スクリーンショット 2025-07-23 065809.png

現時点のソースコード(クリックして展開)
# 必要なアセンブリをロード
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

+# --- グローバル設定用の変数 ---
+$FontName = "Meiryo UI" # 使用するフォント名を定義

# --- レイアウト定数 ---
$formWidth = 270 
$formHeight = 360 
+$edgeMargin = 15 # フォームの端からのマージンを設定

# --- メインフォームの作成とプロパティ設定 ---
$mainForm = [System.Windows.Forms.Form]@{
    Text = "クリップボード履歴ツール" # ウィンドウのタイトルを設定
    Width = $formWidth              # ウィンドウの幅を設定
    Height = $formHeight            # ウィンドウの高さを設定
    StartPosition = "CenterScreen"  # フォームを画面中央に表示
    FormBorderStyle = "FixedSingle" # フォームの枠を固定し、サイズ変更を不可にする
    MaximizeBox = $false            # 最大化ボタンを無効にする
}

+# フォームのインスタンスが作成された直後にClientSizeを取得
+# これがコントロール配置の基準となるクライアント領域のサイズ
+$actualClientWidth = $mainForm.ClientSize.Width

+# --- データグリッドの作成とプロパティ設定 ---
+$dataGridView = [System.Windows.Forms.DataGridView]@{
+    ColumnCount = 1                # 列数を1に設定
+    Font = [System.Drawing.Font]::new($FontName, 8) # データグリッドのフォントを設定
+    RowHeadersVisible = $false     # 行ヘッダー(左側の行番号)を非表示にする
+    Width = $actualClientWidth - $edgeMargin * 2 # クライアント領域の幅から左右のマージンを引いた幅
+    Left = $edgeMargin             # フォームの左端から指定マージン離して配置
+    Top = $edgeMargin              # フォームの上端から指定マージン離して配置
+    Height = 230                   # データグリッドの高さを固定
+}
+# データグリッドの列ヘッダーを設定
+$dataGridView.Columns[0].Name = "クリップボード履歴" # 1列目の列名を「クリップボード履歴」に設定
+$dataGridView.Columns[0].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill # 列幅をデータグリッドの幅に合わせて自動調整

+# --- コントロールをメインフォームに追加 ---
+$mainForm.Controls.Add($dataGridView) # 作成したデータグリッドをフォームに追加

# --- フォーム表示 ---
$null = $mainForm.ShowDialog() # フォームを表示し、ユーザーが閉じるまでスクリプトを一時停止

# --- フォームが閉じられた後の後処理 ---
$mainForm.Dispose() # フォームが使用していたリソースを解放

3. クリップボード履歴の取得と表示

ウィンドウとデータグリッドの準備ができたので、いよいよクリップボードの履歴を取得して表示する機能を実装します。

クリップボード内容の取得と監視(ポーリング)

クリップボードの内容が変更されたことを常に検知するため、本ツールではポーリングという方法を採用します。ポーリングとは、対象の状態を一定の間隔でチェックし、条件を満たせば処理などを行なう仕組みです。
今回は”クリップボードの内容を定期的(2秒毎)にチェックし、変化があればそれを履歴としてデータグリッドに追加する”という仕様にします。

処理の定期的な実行にはSystem.Windows.Forms.Timerを使用します。これはForm上で使用可能な定期実行コンポーネントです。

# --- グローバル設定用の変数 (追加) ---
$FontName = "Meiryo UI" # フォント名を定義

# --- クリップボード監視機能の準備(タイマーポーリング方式) ---
$script:LastClipboardContent = "" # 前回のクリップボード内容を保持する変数
$clipboardMonitorTimer = [System.Windows.Forms.Timer]::new() # タイマーのインスタンスを作成
$clipboardMonitorTimer.Interval = 2000 # 2000ミリ秒 (2秒) ごとにチェック
$clipboardMonitorTimer.Enabled = $false # 初期状態ではタイマーを無効にする

$script:Timer_Tick = {
        #  :
        # ここに定期実行する処理を記述
        #  :
        }
    }
}

# タイマーのTickイベントにスクリプトブロックを登録
$clipboardMonitorTimer.add_Tick($script:Timer_Tick)
ポーリング以外のクリップボード検知方式

クリップボード内容の変更を検知する方法には、ポーリング以外にWindowsのメッセージループを用いたイベント通知を直接受け取る方法もあります。しかし、PowerShellでメッセージループ方式を実装するにはより高度な知識とAPIの呼び出しが必要になります。そのため、今回はシンプルなタイマーによるポーリング方式を採用しています。

スコープと$Script:変数について

$Script:はPowerShellにおけるスコープ修飾子の一つです。
PowerShellに限らずプログラミング言語にはスコープという面倒くさい概念があります。スコープとは変数の有効範囲のようなものです。スコープの中で定義された変数は、そのままではスコープの外からアクセスすることができません。

Add_Tick({ ... })やAdd_Click({ ... })などのイベント内ではそれぞれ異なるスコープが形成されるため、$Script:修飾子を使用してスコープ外からアクセスしています。

クリップボード内容の取得とデータグリッドへの追加

現在のクリップボードの内容を取得するには、[System.Windows.Forms.Clipboard]::GetText()を使います。

Timer_Tickイベントハンドラ内で、前回チェックした内容($script:LastClipboardContentに格納しておきます)と現在のクリップボード内容を比較し、差分があれば新たな履歴としてデータグリッドに追加します。

データグリッドへのデータの追加は、$dataGridView.Rows.Add()メソッドを使用します。
履歴が増えてデータグリッドの表示領域を超えた場合でも$dataGridView.FirstDisplayedScrollingRowIndexプロパティを操作することで、最新の履歴が自動的に表示されるようにスクロールしています。

$script:Timer_Tick = {
    # 現在のクリップボード内容を取得
    $currentClipboardContent = [System.Windows.Forms.Clipboard]::GetText()

    # 前回の内容と比較し、異なっていれば履歴に追加
    if ($currentClipboardContent -ne $script:LastClipboardContent) {
        $script:LastClipboardContent = $currentClipboardContent # 新しい内容を記録
        $dataGridView.Rows.Add($currentClipboardContent) # データグリッドに行を追加
        
        # データグリッドを自動スクロールして最新の履歴が見えるようにする
        if ($dataGridView.Rows.Count -gt 0) {
            $dataGridView.FirstDisplayedScrollingRowIndex = $dataGridView.Rows.Count - 1
        }
    }
}

タイマーの開始とクローズド処理

フォームを起動したときにタイマーを開始するようにします。フォーム起動時の処理はadd_Load()、タイマーの開始にStart()メソッドを使用します。
また、タイマーをスタートさせる前に、Set-Clipboardでクリップボードの初期化を行なっています。これは、アプリケーション起動時クリップボードにもともとデータが入っていると、履歴に追加してしまうのを防ぐためです。

アプリケーションが終了する際には、バックグラウンドで動作しているタイマーのリソースを停止・解放します。これにより、不要なプロセスが残り続けるのを防ぎ、システムの安定性を保ちます。フォーム終了時に行なう処理はadd_FormClosed()で指定します。

# メインフォームがロードされたときにタイマーを開始
$mainForm.add_Load({
    Set-Clipboard # 最初のクリップボード内容を取得する前に一度クリップボードをクリア(空文字列をセット)
    $clipboardMonitorTimer.Start() # タイマーを開始
})

# メインフォームが閉じられるときにタイマーを停止・解放
$mainForm.add_FormClosed({
    $clipboardMonitorTimer.Stop()   # タイマーを停止
    $clipboardMonitorTimer.Dispose() # タイマーが使用していたリソースを解放
})

これで最低限の機能を持ったクリップボード履歴ツールが完成しました。

ここまでの結果

クリップボード履歴ツール_履歴取得部分OK.gif

現時点のソースコード(クリックして展開)
 # 必要なアセンブリをロード
 Add-Type -AssemblyName System.Windows.Forms
 Add-Type -AssemblyName System.Drawing
 
 # --- グローバル設定用の変数 ---
 $FontName = "Meiryo UI" # 使用するフォント名を定義
 
 # --- レイアウト定数 ---
 $formWidth = 270 
 $formHeight = 360 
 $edgeMargin = 15 # フォームの端からのマージンを設定
 
 # --- メインフォームの作成とプロパティ設定 ---
 $mainForm = [System.Windows.Forms.Form]@{
     Text = "クリップボード履歴ツール" # ウィンドウのタイトルを設定
     Width = $formWidth              # ウィンドウの幅を設定
     Height = $formHeight            # ウィンドウの高さを設定
     StartPosition = "CenterScreen"  # フォームを画面中央に表示
     FormBorderStyle = "FixedSingle" # フォームの枠を固定し、サイズ変更を不可にする
     MaximizeBox = $false            # 最大化ボタンを無効にする
 }
 
 # フォームのインスタンスが作成された直後にClientSizeを取得
 # これがコントロール配置の基準となるクライアント領域のサイズ
 $actualClientWidth = $mainForm.ClientSize.Width
 
 # --- データグリッドの作成とプロパティ設定 ---
 $dataGridView = [System.Windows.Forms.DataGridView]@{
     ColumnCount = 1                # 列数を1に設定
     Font = [System.Drawing.Font]::new($FontName, 8) # データグリッドのフォントを設定
     RowHeadersVisible = $false     # 行ヘッダー(左側の行番号)を非表示にする
     Width = $actualClientWidth - $edgeMargin * 2 # クライアント領域の幅から左右のマージンを引いた幅
     Left = $edgeMargin             # フォームの左端から指定マージン離して配置
     Top = $edgeMargin              # フォームの上端から指定マージン離して配置
     Height = 230                   # データグリッドの高さを固定
 }
 # データグリッドの列ヘッダーを設定
 $dataGridView.Columns[0].Name = "クリップボード履歴" # 1列目の列名を「クリップボード履歴」に設定
 $dataGridView.Columns[0].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill # 列幅をデータグリッドの幅に合わせて自動調整
 
 # --- コントロールをメインフォームに追加 ---
 $mainForm.Controls.Add($dataGridView) # 作成したデータグリッドをフォームに追加
 
+# --- クリップボード監視機能の準備(タイマーポーリング方式) ---
+$script:LastClipboardContent = "" # 前回のクリップボード内容を保持するグローバル変数
+$clipboardMonitorTimer = [System.Windows.Forms.Timer]::new() # タイマーのインスタンスを作成
+$clipboardMonitorTimer.Interval = 2000 # 2000ミリ秒 (2秒) ごとにチェックを実行
+$clipboardMonitorTimer.Enabled = $false # 初期状態ではタイマーを無効にしておく

+$script:Timer_Tick = {
+    # 現在のクリップボード内容を取得
+    $currentClipboardContent = [System.Windows.Forms.Clipboard]::GetText()
+
+    # 前回の内容と比較し、異なっていれば履歴としてデータグリッドに追加
+    if ($currentClipboardContent -ne $script:LastClipboardContent) {
+        $script:LastClipboardContent = $currentClipboardContent # 新しい内容をLastClipboardContentに記録
+        $dataGridView.Rows.Add($currentClipboardContent) # データグリッドに行を追加
+        
+        # データグリッドのスクロール位置を調整し、最新の履歴が見えるようにする
+        if ($dataGridView.Rows.Count -gt 0) {
+            $dataGridView.FirstDisplayedScrollingRowIndex = $dataGridView.Rows.Count - 1
+        }
+    }
+}
+$clipboardMonitorTimer.add_Tick($script:Timer_Tick) # タイマーのTickイベントにスクリプトブロックを登録

+# メインフォームがロードされたときにタイマーを開始する
+$mainForm.add_Load({
+    Set-Clipboard # 最初のクリップボード内容取得前に一度クリア(空文字列をセット)
+
+    $clipboardMonitorTimer.Start() # クリップボード監視タイマーを開始
+})

+# メインフォームが閉じられるときにタイマーを停止・解放する
+$mainForm.add_FormClosed({
+    $clipboardMonitorTimer.Stop()   # タイマーの動作を停止
+    $clipboardMonitorTimer.Dispose() # タイマーが使用していたシステムリソースを解放
+})

 # --- フォーム表示 ---
 $null = $mainForm.ShowDialog() # フォームを表示し、ユーザーが閉じるまでスクリプトを一時停止
 
 # --- フォームが閉じられた後の後処理 ---
 $mainForm.Dispose() # フォームが使用していたリソースを解放

4. 機能追加① Copyボタンの追加

現在のデータグリッドでもCtrl+Cで選択行をコピーできますが、右クリックメニュー(コンテキストメニュー)を表示できないため、ユーザーが「どうやってコピーすればいいの?」と迷うかもしれません。そこで、より明示的にコピーを実行できる「Copy」ボタンを追加します。

ボタンの作成と配置

[System.Windows.Forms.Button]のインスタンスを作成し、表示テキストやフォント、サイズを設定します。配置では、これまでに使用したLeftTopに加えて、他のコントロールの端に連携して配置できるBottomRightプロパティを活用します。これにより、フォームや他のコントロールのサイズが変わっても、ボタンの位置が自動的に調整され、レイアウトが崩れにくくなります。

# --- レイアウト定数 (追加) ---
$copyButtonWidth = 80
$copyButtonHeight = 30
$controlGroupTopMargin = 15 
$edgeMargin = 15 

# --- 「Copy」ボタンの作成 ---
$copyButton = [System.Windows.Forms.Button]@{
    Text = "Copy"                 # ボタンのテキスト
    Font = [System.Drawing.Font]::new($FontName, 9) # フォント設定
    Width = $copyButtonWidth      # 幅
    Height = $copyButtonHeight    # 高さ
    # データグリッドのBottomから指定マージン下に配置
    Left = $edgeMargin            # 左マージンに固定
    Top = $dataGridView.Bottom + $controlGroupTopMargin # データグリッドのBottom位置を基準に配置
}

# --- コントロールをメインフォームに追加 (追加) ---
$mainForm.Controls.Add($copyButton)


image.png

クリックイベントの処理

ボタンがクリックされたときに特定の動作を実行させるには、ボタンのAdd_Clickイベントに処理を登録します。

# --- 「Copy」ボタンのクリックイベントハンドラの定義 ---
$script:CopyButton_Click = {
    # ここにボタンがクリックされた時の処理を記述
}

# ボタンのClickイベントにスクリプトブロックを登録
$copyButton.add_Click($script:CopyButton_Click)
変数を用いたイベントハンドラ定義

add_Click($script:CopyButton_Click)のように変数に処理を一度定義してからイベントハンドラに登録する方法は、$copyButton.add_Click({ # イベント内容を直接記述 })と同じ意味を持ちます。
少ない処理の場合は直接記述の方が簡潔に書けます。しかし、処理が複雑になる場合や、複数のイベントで同じ処理を使い回したい場合は、変数に一度定義した方がイベントを管理しやすくなります。

選択内容の取得とクリップボードへのコピー

「Copy」ボタンがクリックされたら、データグリッドで選択されているセルのテキストを取得し、クリップボードにコピーします。

複数選択したセルもコピーしたいので、$dataGridView.SelectedCellsで取得したセルをforeachで回してすべて取得しています。
取得後、改行文字`r`nで配列を連結しクリップボードへ格納します。

# --- 「Copy」ボタンのクリックイベントハンドラの定義 (修正・追加) ---
$script:CopyButton_Click = {
    # 選択されているセルがあるか確認
    if ($dataGridView.SelectedCells.Count -gt 0) {
        $selectedTexts = @() # 選択されたセルの内容を格納する配列

        # 選択されている各セルからテキストを取得
        foreach ($cell in $dataGridView.SelectedCells) {
            if ($cell.Value -ne $null) {
                $selectedTexts += $cell.Value.ToString()
            } else {
                $selectedTexts += "" # セルが空の場合は空文字列を追加
            }
        }

        # 取得したテキストをクリップボードにコピー
        if ($selectedTexts.Count -gt 0) {
            # 配列を結合し、クリップボードにコピー
            $concatedText = $selectedTexts -join "`r`n"  # 結合処理
            Set-Clipboard -Value $concatedText  # クリップボードへコピー
        } 
    } 
}
# ボタンのClickイベントにスクリプトブロックを登録 (既存)
$copyButton.add_Click($script:CopyButton_Click)

バグ対応①:Copy時にコピー内容がセルに出力されてしまう

動作確認をしてみると分かりますが、以下のようにCopyを実行すると、その内容がデータグリッドに出力されてしまうバグがありました。
クリップボード履歴ツール_バグ1_コピー結果が出力.gif

原因
コピーが実行された際に、クリップボードの差分を読み取って出力されてしまう。

対策
コピー時に比較用変数にもコピー内容を出力し、差分を発生させないようにする。

 # --- 「Copy」ボタンのクリックイベントハンドラの定義 ---
 $script:CopyButton_Click = {
     if ($dataGridView.SelectedCells.Count -gt 0) {
            
         if ($selectedTexts.Count -gt 0) {
            # 文字列を結合
            $concatedText = $selectedTexts -join "`r`n"
+            # クリップボードへのコピー前に、比較用変数に代入
+            # (コピーボタン押下時にデータグリッドへの出力を防ぐため)
+            $script:LastClipboardContent = $concatedText
            # クリップボードにコピー
             Set-Clipboard -Value $concatedText
             

→ OK

バグ対応②:セル選択の順番によってコピー結果が変わる

セル選択はCtrlを押しながら複数選択できるのですが、その選択の順番によってコピーされる内容の順番が変わることが分かりました。先に選択したセルが下になっている…?

クリップボード履歴ツール_バグ2_コピー順番が変わってしまう_.gif

原因
DataGridViewSelectedCellsが選択順に基づいてデータが格納されている(恐らく)。

対策
選択セルを行番号でソートし、次の処理に回す。

 # --- 「Copy」ボタンのクリックイベントハンドラの定義 ---
 $script:CopyButton_Click = {
     if ($dataGridView.SelectedCells.Count -gt 0) {
+        # 選択されたセルを行番号でソート
+        $sortedCells = $dataGridView.SelectedCells | Sort-Object { $_.RowIndex }
        # 選択セルの内容を格納する配列
        $selectedTexts = @()
+        foreach ($cell in $sortedCells) {
             if ($cell.Value -ne $null) {
                 $selectedTexts += $cell.Value.ToString()
                 

→ OK

ソート処理のところで使用している|パイプラインといって、PowerShellの特徴的な記法の一つです。簡単に言うと「データのバケツリレー」で、|の左のコマンド結果を右のコマンドに渡すことができます。パイプラインを用いることで変数やファイルへの出力を省け、データを効率的に処理できます。

ここまでの結果
クリップボード履歴ツール_コピーボタンOK.gif

Copyボタン押下時にデータグリッドに出力されることもなく、セル選択順番の影響も受けていないことが分かります。

現時点のソースコード(クリックして展開)
# 必要なアセンブリをロード
 Add-Type -AssemblyName System.Windows.Forms
 Add-Type -AssemblyName System.Drawing
 
 # --- グローバル設定用の変数 ---
 $FontName = "Meiryo UI" # 使用するフォント名を定義
 
 # --- レイアウト定数 ---
 $formWidth = 270 
 $formHeight = 360 
 $edgeMargin = 15 # フォームの端からのマージンを設定
+$copyButtonWidth = 80 # コピーボタンの幅を定義
+$copyButtonHeight = 30 # コピーボタンの高さを定義
+$controlGroupTopMargin = 15 # コントロールグループ(ボタンなど)の上マージンを定義
 
 # --- メインフォームの作成とプロパティ設定 ---
 $mainForm = [System.Windows.Forms.Form]@{
     Text = "クリップボード履歴ツール" # ウィンドウのタイトルを設定
     Width = $formWidth              # ウィンドウの幅を設定
     Height = $formHeight            # ウィンドウの高さを設定
     StartPosition = "CenterScreen"  # フォームを画面中央に表示
     FormBorderStyle = "FixedSingle" # フォームの枠を固定し、サイズ変更を不可にする
     MaximizeBox = $false            # 最大化ボタンを無効にする
 }
 
 # フォームのインスタンスが作成された直後にClientSizeを取得
 # これがコントロール配置の基準となるクライアント領域のサイズ
 $actualClientWidth = $mainForm.ClientSize.Width
 
 # --- データグリッドの作成とプロパティ設定 ---
 $dataGridView = [System.Windows.Forms.DataGridView]@{
     ColumnCount = 1                # 列数を1に設定
     Font = [System.Drawing.Font]::new($FontName, 8) # データグリッドのフォントを設定
     RowHeadersVisible = $false     # 行ヘッダー(左側の行番号)を非表示にする
     Width = $actualClientWidth - $edgeMargin * 2 # クライアント領域の幅から左右のマージンを引いた幅
     Left = $edgeMargin             # フォームの左端から指定マージン離して配置
     Top = $edgeMargin              # フォームの上端から指定マージン離して配置
     Height = 230                   # データグリッドの高さを固定
 }
 # データグリッドの列ヘッダーを設定
 $dataGridView.Columns[0].Name = "クリップボード履歴" # 1列目の列名を「クリップボード履歴」に設定
 $dataGridView.Columns[0].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill # 列幅をデータグリッドの幅に合わせて自動調整
 
+# --- 「Copy」ボタンの作成 ---
+$copyButton = [System.Windows.Forms.Button]@{
+    Text = "Copy"                 # ボタンに表示するテキスト
+    Font = [System.Drawing.Font]::new($FontName, 9) # ボタンのフォントを設定
+    Width = $copyButtonWidth      # ボタンの幅を設定
+    Height = $copyButtonHeight    # ボタンの高さを設定
+    Left = $edgeMargin            # フォームの左端からマージン分配置
+    Top = $dataGridView.Bottom + $controlGroupTopMargin # データグリッドの最下部から指定マージン下に配置
+}
+
 # --- コントロールをメインフォームに追加 ---
 $mainForm.Controls.Add($dataGridView) # 作成したデータグリッドをフォームに追加
+$mainForm.Controls.Add($copyButton) # 作成したCopyボタンをフォームに追加
 
 # --- クリップボード監視機能の準備(タイマーポーリング方式) ---
 $script:LastClipboardContent = "" # 前回のクリップボード内容を保持するグローバル変数
 $clipboardMonitorTimer = [System.Windows.Forms.Timer]::new() # タイマーのインスタンスを作成
 $clipboardMonitorTimer.Interval = 2000 # 2000ミリ秒 (2秒) ごとにチェックを実行
 $clipboardMonitorTimer.Enabled = $false # 初期状態ではタイマーを無効にしておく
 
 $script:Timer_Tick = {
     # 現在のクリップボード内容を取得
     $currentClipboardContent = [System.Windows.Forms.Clipboard]::GetText()
 
     # 前回の内容と比較し、異なっていれば履歴としてデータグリッドに追加
     if ($currentClipboardContent -ne $script:LastClipboardContent) {
         $script:LastClipboardContent = $currentClipboardContent # 新しい内容をLastClipboardContentに記録
         $dataGridView.Rows.Add($currentClipboardContent) # データグリッドに行を追加
         
         # データグリッドのスクロール位置を調整し、最新の履歴が見えるようにする
         if ($dataGridView.Rows.Count -gt 0) {
             $dataGridView.FirstDisplayedScrollingRowIndex = $dataGridView.Rows.Count - 1
         }
     }
 }
 $clipboardMonitorTimer.add_Tick($script:Timer_Tick) # タイマーのTickイベントにスクリプトブロックを登録
 
+# --- 「Copy」ボタンのクリックイベントハンドラの定義 ---
+$script:CopyButton_Click = {
+    # データグリッドで選択されているセルがあるか確認
+    if ($dataGridView.SelectedCells.Count -gt 0) {
+        # 選択されたセルを行番号でソート
+        $sortedCells = $dataGridView.SelectedCells | Sort-Object { $_.RowIndex }
+        # 選択セルの内容を格納する配列
+        $selectedTexts = @()
+        # ソート後のセルをループ処理し、そのテキスト値を取得
+        foreach ($cell in $sortedCells) {
+            if ($cell.Value -ne $null) {
+                $selectedTexts += $cell.Value.ToString() # セルの値があれば文字列として配列に追加
+            } else {
+                $selectedTexts += "" # セルが空の場合は空文字列を追加
+            }
+        }
+
+        # 取得したテキストの配列をクリップボードにコピー
+        if ($selectedTexts.Count -gt 0) {
+            # クリップボードへのコピー前に、比較用変数に代入
+            # (コピーボタン押下時にデータグリッドへの出力を防ぐため)
+            $script:LastClipboardContent = $concatedText
+            # 配列を結合し、クリップボードにコピー
+            $concatedText = $selectedTexts -join "`r`n"  # 結合処理
+            Set-Clipboard -Value $concatedText  # クリップボードへコピー
+        }
+    } 
+}
+$copyButton.add_Click($script:CopyButton_Click) # CopyボタンのClickイベントにスクリプトブロックを登録
+
 # メインフォームがロードされたときにタイマーを開始
 $mainForm.add_Load({
     Set-Clipboard
     $clipboardMonitorTimer.Start()
 })
 
 # メインフォームが閉じられるときにタイマーを停止・解放
 $mainForm.add_FormClosed({
     $clipboardMonitorTimer.Stop()
     $clipboardMonitorTimer.Dispose()
 })
 
 # --- フォーム表示 ---
 $null = $mainForm.ShowDialog()
 
 # --- フォームが閉じられた後の後処理 ---
 $mainForm.Dispose()

5. 機能追加②最前面固定機能とウィンドウ半透明化機能の追加

ここから自分がほしいと思った機能を追加していきます。今回は、

  • 作業をしながらクリップボードへの貼付け状況を見たい→「ウィンドウを最前面に固定
  • その際にウィンドウが邪魔になる→「非アクティブ時にウィンドウを半透明化

といった機能を追加していきます。

チェックボックスの配置

これらの機能はユーザーがON/OFFできたほうが良いので、チェックボックスを追加します。それぞれのチェックボックスをCopyボタンの右側に、縦に並ぶように配置します。

初期状態をONにしたいので、プロパティでChecked = $trueを指定しています。

# --- レイアウト定数 (追加) ---
$checkBoxWidth = 125 
$checkBoxHeight = 20
$horizontalGapButtonToCheckBoxGroup = 20 
$verticalGapCheckBox = 5 # チェックボックス間の垂直方向の間隔

# --- 最前面固定チェックボックスの作成 ---
$alwaysOnTopCheckBox = [System.Windows.Forms.CheckBox]@{
    Text = "最前面に固定"          # チェックボックスのテキスト
    Font = [System.Drawing.Font]::new($FontName, 8) # フォント設定
    Width = $checkBoxWidth         # 幅
    Height = $checkBoxHeight       # 高さ
    Checked = $true                # 初期状態でチェックを入れておく
    # Copyボタンの右に配置
    Left = $copyButton.Right + $horizontalGapButtonToCheckBoxGroup 
    Top = $copyButton.Top          # Copyボタンと垂直方向を揃える
}

# --- 非アクティブ時半透明チェックボックスの作成 ---
$transparencyCheckBox = [System.Windows.Forms.CheckBox]@{
    Text = "非アクティブ時に半透明" 
    Font = [System.Drawing.Font]::new($FontName, 8)
    Width = $checkBoxWidth
    Height = $checkBoxHeight
    Checked = $true # 初期状態でチェックを入れておく
    # 最前面固定チェックボックスの下に配置
    Left = $alwaysOnTopCheckBox.Left
    Top = $alwaysOnTopCheckBox.Bottom + $verticalGapCheckBox 
}

# --- コントロールをメインフォームに追加 (追加) ---
$mainForm.Controls.Add($alwaysOnTopCheckBox)
$mainForm.Controls.Add($transparencyCheckBox)


image.png

最前面固定化機能の設定

この機能は、フォームのTopMostプロパティを操作することで実現します。$trueに設定すると最前面に、$falseに設定すると通常のウィンドウとして扱われます。この設定は、「最前面に固定」チェックボックスのオン/オフと連動させます。チェックボックスのadd_CheckedChangedチェック状態が変わるたびに実行されるイベントを指定できるので、これを使用します。

# --- 最前面固定チェックボックスのイベントハンドラ ---
$script:AlwaysOnTopCheckBox_CheckedChanged = {
    # チェックボックスの状態に応じてフォームのTopMostプロパティを設定
    $mainForm.TopMost = $alwaysOnTopCheckBox.Checked
}
$alwaysOnTopCheckBox.add_CheckedChanged($script:AlwaysOnTopCheckBox_CheckedChanged)

フォームの初期状態も最前面固定にしておきます。

 # --- メインフォームの作成とプロパティ設定 ---
 $mainForm = [System.Windows.Forms.Form]@{
        
     TopMost = $true                 # 最前面表示に固定
 }

非アクティブ時半透明機能の追加

この機能は、フォームのOpacityプロパティ(透明度)を操作することで実現します。1.0が完全に不透明、0.0が完全に透明です。今回は0.7に指定してやや半透明にします。
フォームのadd_ActivatedDeactivateはそれぞれウィンドウがアクティブ非アクティブ化したときに実行されるイベントを追加できます。チェックボックスの状態とこれらのイベントを組み合わせて、非アクティブ時にウィンドウが半透明化される機能を追加します。

# --- フォームのアクティブ/非アクティブイベントハンドラ ---
$script:MainForm_Activated = {
    # フォームがアクティブになったら常に不透明に戻す
    $mainForm.Opacity = 1.0
}
$mainForm.add_Activated($script:MainForm_Activated)

$script:MainForm_Deactivate = {
    # 半透明チェックがオンの場合のみOpacityを0.7にする
    if ($transparencyCheckBox.Checked) {
        $mainForm.Opacity = 0.7
    }
}
$mainForm.add_Deactivate($script:MainForm_Deactivate)

ここまでの結果
クリップボード履歴ツール_最前面_半透明OK.gif

現時点のソースコード(クリックして展開)
# 必要なアセンブリをロード
 Add-Type -AssemblyName System.Windows.Forms
 Add-Type -AssemblyName System.Drawing
 
 # --- グローバル設定用の変数 ---
 $FontName = "Meiryo UI" # 使用するフォント名を定義
 
 # --- レイアウト定数 ---
 $formWidth = 270 
 $formHeight = 360 
 $edgeMargin = 15 # フォームの端からのマージンを設定
 $copyButtonWidth = 80 # コピーボタンの幅を定義
 $copyButtonHeight = 30 # コピーボタンの高さを定義
 $controlGroupTopMargin = 15 # コントロールグループ(ボタンなど)の上マージンを定義
+$checkBoxWidth = 125 # チェックボックスの幅を定義
+$checkBoxHeight = 20 # チェックボックスの高さを定義
+$horizontalGapButtonToCheckBoxGroup = 20 # ボタンとチェックボックスグループ間の水平方向の間隔
+$verticalGapCheckBox = 5 # チェックボックス間の垂直方向の間隔
 
 # --- メインフォームの作成とプロパティ設定 ---
 $mainForm = [System.Windows.Forms.Form]@{
     Text = "クリップボード履歴ツール" # ウィンドウのタイトルを設定
     Width = $formWidth              # ウィンドウの幅を設定
     Height = $formHeight            # ウィンドウの高さを設定
     StartPosition = "CenterScreen"  # フォームを画面中央に表示
     FormBorderStyle = "FixedSingle" # フォームの枠を固定し、サイズ変更を不可にする
     MaximizeBox = $false            # 最大化ボタンを無効にする
+    TopMost = $true                 # 最前面表示に固定
 }
 
 # フォームのインスタンスが作成された直後にClientSizeを取得
 # これがコントロール配置の基準となるクライアント領域のサイズ
 $actualClientWidth = $mainForm.ClientSize.Width
 
 # --- データグリッドの作成とプロパティ設定 ---
 $dataGridView = [System.Windows.Forms.DataGridView]@{
     ColumnCount = 1                # 列数を1に設定
     Font = [System.Drawing.Font]::new($FontName, 8) # データグリッドのフォントを設定
     RowHeadersVisible = $false     # 行ヘッダー(左側の行番号)を非表示にする
     Width = $actualClientWidth - $edgeMargin * 2 # クライアント領域の幅から左右のマージンを引いた幅
     Left = $edgeMargin             # フォームの左端から指定マージン離して配置
     Top = $edgeMargin              # フォームの上端から指定マージン離して配置
     Height = 230                   # データグリッドの高さを固定
 }
 # データグリッドの列ヘッダーを設定
 $dataGridView.Columns[0].Name = "クリップボード履歴" # 1列目の列名を「クリップボード履歴」に設定
 $dataGridView.Columns[0].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill # 列幅をデータグリッドの幅に合わせて自動調整
 
 # --- 「Copy」ボタンの作成 ---
 $copyButton = [System.Windows.Forms.Button]@{
     Text = "Copy"                 # ボタンに表示するテキスト
     Font = [System.Drawing.Font]::new($FontName, 9) # ボタンのフォントを設定
     Width = $copyButtonWidth      # ボタンの幅を設定
     Height = $copyButtonHeight    # ボタンの高さを設定
     Left = $edgeMargin            # フォームの左端からマージン分配置
     Top = $dataGridView.Bottom + $controlGroupTopMargin # データグリッドの最下部から指定マージン下に配置
 }
 
+# --- 最前面固定チェックボックスの作成 ---
+$alwaysOnTopCheckBox = [System.Windows.Forms.CheckBox]@{
+    Text = "最前面に固定"          # チェックボックスのテキスト
+    Font = [System.Drawing.Font]::new($FontName, 8) # フォント設定
+    Width = $checkBoxWidth         # 幅
+    Height = $checkBoxHeight       # 高さ
+    Checked = $true                # 初期状態でチェックを入れておく
+    Left = $copyButton.Right + $horizontalGapButtonToCheckBoxGroup # Copyボタンの右に水平方向のマージンを開けて配置
+    Top = $copyButton.Top          # Copyボタンと垂直方向の位置を揃える
+}

+# --- 非アクティブ時半透明チェックボックスの作成 ---
+$transparencyCheckBox = [System.Windows.Forms.CheckBox]@{
+    Text = "非アクティブ時に半透明" # チェックボックスのテキスト
+    Font = [System.Drawing.Font]::new($FontName, 8) # フォント設定
+    Width = $checkBoxWidth         # 幅
+    Height = $checkBoxHeight       # 高さ
+    Checked = $true                # 初期状態でチェックを入れておく
+    Left = $alwaysOnTopCheckBox.Left # 最前面固定チェックボックスと同じ水平位置に配置
+    Top = $alwaysOnTopCheckBox.Bottom + $verticalGapCheckBox # 最前面固定チェックボックスの直下に配置
+}

 # --- コントロールをメインフォームに追加 ---
 $mainForm.Controls.Add($dataGridView) # 作成したデータグリッドをフォームに追加
 $mainForm.Controls.Add($copyButton) # 作成したCopyボタンをフォームに追加
+$mainForm.Controls.Add($alwaysOnTopCheckBox) # 作成した最前面固定チェックボックスをフォームに追加
+$mainForm.Controls.Add($transparencyCheckBox) # 作成した非アクティブ時半透明チェックボックスをフォームに追加
 
 # --- クリップボード監視機能の準備(タイマーポーリング方式) ---
 $script:LastClipboardContent = "" # 前回のクリップボード内容を保持するグローバル変数
 $clipboardMonitorTimer = [System.Windows.Forms.Timer]::new() # タイマーのインスタンスを作成
 $clipboardMonitorTimer.Interval = 2000 # 2000ミリ秒 (2秒) ごとにチェックを実行
 $clipboardMonitorTimer.Enabled = $false # 初期状態ではタイマーを無効にしておく
 
 $script:Timer_Tick = {
     # 現在のクリップボード内容を取得
     $currentClipboardContent = [System.Windows.Forms.Clipboard]::GetText()
 
     # 前回の内容と比較し、異なっていれば履歴としてデータグリッドに追加
     if ($currentClipboardContent -ne $script:LastClipboardContent) {
         $script:LastClipboardContent = $currentClipboardContent # 新しい内容をLastClipboardContentに記録
         $dataGridView.Rows.Add($currentClipboardContent) # データグリッドに行を追加
         
         # データグリッドのスクロール位置を調整し、最新の履歴が見えるようにする
         if ($dataGridView.Rows.Count -gt 0) {
             $dataGridView.FirstDisplayedScrollingRowIndex = $dataGridView.Rows.Count - 1
         }
     }
 }
 $clipboardMonitorTimer.add_Tick($script:Timer_Tick) # タイマーのTickイベントにスクリプトブロックを登録
 
 # --- 「Copy」ボタンのクリックイベントハンドラの定義 ---
 $script:CopyButton_Click = {
     if ($dataGridView.SelectedCells.Count -gt 0) {
        # 選択されたセルを行番号でソート
        $sortedCells = $dataGridView.SelectedCells | Sort-Object { $_.RowIndex }
        # 選択されたテキストを格納する配列
        $selectedTexts = @()
        # ソート後のセルをループ処理し、そのテキスト値を取得
        foreach ($cell in $sortedCells) {
             if ($cell.Value -ne $null) {
                 $selectedTexts += $cell.Value.ToString()  # セルの値があれば文字列として配列に追加
             } else {
                 $selectedTexts += "" # セルが空の場合は空文字列を追加
             }
         }
         # 有効な文字列が1つでもあればクリップボード出力処理
         if ($selectedTexts.Count -gt 0) {
             # クリップボードへのコピー前に、比較用変数に代入
             # (コピーボタン押下時にデータグリッドへの出力を防ぐため)
             $script:LastClipboardContent = $concatedText
             # 配列を結合し、クリップボードにコピー
             $concatedText = $selectedTexts -join "`r`n"  # 結合処理
             Set-Clipboard -Value $concatedText  # クリップボードへコピー
         }
     } 
 }
 $copyButton.add_Click($script:CopyButton_Click)
 
+# --- 最前面固定チェックボックスのイベントハンドラ ---
+$script:AlwaysOnTopCheckBox_CheckedChanged = {
+    # チェックボックスの状態に応じてフォームのTopMostプロパティ(最前面表示)を設定
+    $mainForm.TopMost = $alwaysOnTopCheckBox.Checked
+}
+$alwaysOnTopCheckBox.add_CheckedChanged($script:AlwaysOnTopCheckBox_CheckedChanged) # CheckedChangedイベントにスクリプトブロックを登録

+# --- フォームのアクティブ/非アクティブイベントハンドラ (半透明機能用) ---
+$script:MainForm_Activated = {
+    # フォームがアクティブになったら常に不透明(Opacity 1.0)に戻す
+    $mainForm.Opacity = 1.0
+}
+$mainForm.add_Activated($script:MainForm_Activated) # Activatedイベントにスクリプトブロックを登録

+$script:MainForm_Deactivate = {
+    # 半透明チェックボックスがオンの場合のみ、フォーム非アクティブ時に半透明(Opacity 0.7)にする
+    if ($transparencyCheckBox.Checked) {
+        $mainForm.Opacity = 0.7
+    }
+}
+$mainForm.add_Deactivate($script:MainForm_Deactivate) # Deactivateイベントにスクリプトブロックを登録

 # メインフォームがロードされたときにタイマーを開始
 $mainForm.add_Load({
     Set-Clipboard  # クリップボードを初期化
     $clipboardMonitorTimer.Start()

 })
 
 # メインフォームが閉じられるときにタイマーを停止・解放
 $mainForm.add_FormClosed({
     $clipboardMonitorTimer.Stop()
     $clipboardMonitorTimer.Dispose()
 })
 
 # --- フォーム表示 ---
 $null = $mainForm.ShowDialog()
 
 # --- フォームが閉じられた後の後処理 ---
 $mainForm.Dispose()


6. 起動用バッチの作成

ここまででひとまずスクリプトが完成しました。

最後に起動用バッチを作成し、そのバッチファイルをクリックするだけで起動できるようにしておきます。

1.完成したスクリプトを.ps1ファイルとして任意の名前をつけて保存
2.保存したps1ファイルと同じフォルダに新規テキストファイルを作成
3.メモ帳などでテキストファイルに以下のコードを記入・保存

@echo off
powershell -executionpolicy RemoteSigned -File "%~dp0%~n0.ps1" -NoProfile
起動用バッチのコマンド内容メモ

powershell:
 PowerShellを実行
-executionpolicy RemoteSigned:
 セキュリティ設定を「RemoteSigned」で実行
 (リモートからダウンロードしたスクリプトの実行を許可し、ローカルスクリプトは制限なく実行)
-File "%~dp0%~n0.ps1":
 実行するファイルを指定
 %~dp0は自身のフォルダパスを指す
 %~n0は自身の拡張子を除いたファイル名を指す
-NoProfile:
 PowerShell起動時にユーザープロファイル(設定ファイル)を読み込ませない。
 起動を少しだけ高速化し、ユーザープロファイル由来の影響を排除できる。

4.テキストファイルのファイル名を<ps1ファイルと同じファイル名>.batにする

これで、バッチをダブルクリックするだけでスクリプトが実行できるようになりました!
クリップボード履歴ツール_起動.gif
(環境によっては、上記のようにISEとフォームのデザインが変わってしまいます。。)

とはいえ、完成!

今後・所感

追加予定の機能

  • データの削除、クリア、並び替え
  • CSVへの出力
  • テキストボックスへの出力

(すでに実装できてるけど、解説が大変なので解説無しでコードだけそのうちUPするかも)

WPF化

現在WPFを勉強しているので、実践も兼ねてWPFで作り直したい。
もうちょっと動きのあるツールにできるといいなぁ。。

記事作成にあたって

久々の投稿です。

今回、記事作成にあたって初めてAIを使用してみました。使用したのはGems(Gemini)。
まずAIと壁打ちでコードを作った後、それを記事にするような感じでお願いしてみました。
ベースを作ってもらう部分は良かったのですが、結局細かな間違いや変な日本語、自分のこだわりもあって大部分の文章を修正することになりました…。
一応トータルでは時間短縮できた気がしますが。

今後、プロンプトの改善や高機能モデルの使用でどうなるか試せたらなぁ、と考えています。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?