🐶

PowerShellでドラッグアンドドロップしたい

2025/01/03に公開

おっさんは引数指定メンドクセーんですよ
ってわけでとりあえずドラッグアンドドロップ用GUIの実装を簡単にしましょー
うん、つまりよくあるコレっすわ

本題

20250103 実は今日誤爆して前の投稿にコレぶっ込んだんだが、きっとバレてねえハズだっ
20250113 親ウィンドウを取ろうとする無駄な努力を止めて普通にZオーダー先頭に移動に

引数 説明
Title タイトルです
Message メッセージです
改行できます
FileFilter D&D可能なファイル名です
拡張子だけって意外に使いづらいんで正規表現でファイル名全体をマッチします
FileList 初期リストです
args突っ込んでやればバッチ起動時のファイルリストをD&Dしたことにできます
function ShowFileListDialog {
    param (
        [Parameter(Mandatory = $true)]  [string]   $Title,
        [Parameter(Mandatory = $true)]  [string]   $Message,
        [Parameter(Mandatory = $false)] [string]   $FileFilter  = ".*",
        [Parameter(Mandatory = $false)] [string[]] $FileList
    )
    begin {}
    process {
        # フォーム生成
        $frmMain = New-Object System.Windows.Forms.Form -Property @{
            Text          = $Title                                                      # タイトル
            StartPosition = 'CenterScreen'                                              # 表示位置
            Size          = New-Object System.Drawing.Size(480,320)
            Padding       = New-Object System.Windows.Forms.Padding(5)
        }

        $tlpMain = New-Object System.Windows.Forms.TableLayoutPanel -Property @{
            Dock     = [System.Windows.Forms.DockStyle]::Fill
            RowCount = 2
        }

        $pnlBody = New-Object System.Windows.Forms.Panel -Property @{
            Dock = [System.Windows.Forms.DockStyle]::Fill
        }
        $lblDD = New-Object System.Windows.Forms.Label -Property @{
            Dock      = [System.Windows.Forms.DockStyle]::Top
            Text      = $Message
            AutoSize  = $true
            TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
        }
        $lbxDD = New-Object System.Windows.Forms.ListBox -Property @{
            Dock        = [System.Windows.Forms.DockStyle]::Fill
            AllowDrop   = $true
        }

        $pnlTail = New-Object System.Windows.Forms.Panel -Property @{
            Dock = [System.Windows.Forms.DockStyle]::Fill
        }
        $btnOK = New-Object System.Windows.Forms.Button -Property @{
            Dock                    = [System.Windows.Forms.DockStyle]::Right
            Size                    = New-Object System.Drawing.Size(128, 0) # ボタン巾のみ指定可能
            Text                    = "OK"
            UseVisualStyleBackColor = $true
            DialogResult            = [Windows.Forms.DialogResult]::OK
        }
        $btnCancel = New-Object System.Windows.Forms.Button -Property @{
            Dock                    = [System.Windows.Forms.DockStyle]::Right
            Size                    = New-Object System.Drawing.Size(128, 0) # ボタン巾のみ指定可能
            Text                    = "Cancel"
            UseVisualStyleBackColor = $true
            DialogResult            = [Windows.Forms.DialogResult]::Cancel
        }

        $null = $pnlBody.Controls.Add($lbxDD)
        $null = $pnlBody.Controls.Add($lblDD)
        $null = $pnlTail.Controls.Add($btnOK)
        $null = $pnlTail.Controls.Add($btnCancel)
        $null = $tlpMain.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100)))
        $null = $tlpMain.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 50))) # ボタン高さはコレ
        $null = $tlpMain.Controls.Add($pnlBody, 0, 0)
        $null = $tlpMain.Controls.Add($pnlTail, 0, 1)
        $null = $frmMain.Controls.Add($tlpMain)

        $null = $frmMain.Add_Load({
            $frmMain.BringToFront()
        })
        $null = $lbxDD.Add_DragEnter({
            $_.Effect = "All"
        })
        $null = $lbxDD.Add_DragDrop({
            AddFileList $lbxDD $_.Data.GetData("FileDrop") $FileFilter
        })
        $null = $lbxDD.Add_KeyDown({
            if ($_.KeyCode -eq "Delete") {
                [array]::Reverse($lbxDD.SelectedIndices) | ForEach-Object {
                    [void]$lbxDD.Items.RemoveAt($_)
                }
            }
        })

        # フォーム表示
        if ($null -ne $FileList) {
            AddFileList $lbxDD $FileList $FileFilter
        }
        $frmMain.AcceptButton = $btnOK
        $frmMain.CancelButton = $btnCancel
        $null = $frmMain.ShowDialog()
        return @($frmMain.DialogResult, $lbxDD.Items)
    }
    end {}
}

# FileListDialog用ファイルリスト生成
function local:AddFileList([System.Windows.Forms.ListBox] $ListBox, [string[]] $FilePaths, [string] $FileFilter) {
    foreach ($FilePath in $FilePaths) {
        if (Test-Path -LiteralPath $FilePath) {
            if ([System.IO.Directory]::Exists($FilePath)) {
                $ChildFilePaths = @()
                @(Get-ChildItem -LiteralPath $FilePath -File -Recurse) | ForEach-Object {
                    $ChildFilePaths += $_.FullName
                }
                AddFileList $ListBox $ChildFilePaths $FileFilter
            } else {
                if ([System.IO.Path]::GetFileName($FilePath) -match $FileFilter) {
                    if ($ListBox.Items -notcontains $FilePath) {
                        [void]$ListBox.Items.Add($FilePath)
                    }
                }
            }
        }
    }
}

使い方

引数全部が入力ファイルとかゆー状況ならこんな感じにすればいいんじゃなかろーか
※バッチ経由でPowerShell呼ぶとかなら結構ある状況のハズ

機能としてリストしたものの対象から削除したい場合はDelキーで消せるとか
フォルダぶっ込んだら再帰的にファイル名を登録とかも用意してあります
一応だけど-FileListは初期リストで-FileFilterはファイル名への正規表現ね?

$ret = ShowFileListDialog `
        -Title "タイトル" `
        -Message "対象ファイルをドラッグ&ドロップしてください" `
        -FileList $args `
        -FileFilter "\.(txt)$" 
if ($ret[0] -eq "OK") {
    foreach($elm in $ret[1]) {
        # なんか処理
    }
}

蛇足

コレだけだとアレなのでちょっとした実行オプションをつけたやつも用意
現実的にはコッチのほうが使うだろーなぁきっと

引数 説明
Title タイトルです
Message メッセージです
改行できます
FileFilter D&D可能なファイル名です
拡張子だけって意外に使いづらいんで正規表現でファイル名全体をマッチします
FileList 初期リストです
args突っ込んでやればバッチ起動時のファイルリストをD&Dしたことにできます
Options オプションです
ほんの少しだけ違う挙動を組み込みたい時用のアレ
function ShowFileListDialogWithOption {
    param (
        [Parameter(Mandatory = $true)]  [string]   $Title,
        [Parameter(Mandatory = $true)]  [string]   $Message,
        [Parameter(Mandatory = $false)] [string]   $FileFilter  = ".*",
        [Parameter(Mandatory = $false)] [string[]] $FileList,
        [Parameter(Mandatory = $false)] [string[]] $Options
    )
    begin {}
    process {
        # フォーム生成
        $frmMain = New-Object System.Windows.Forms.Form -Property @{
            Text          = $Title                                                      # タイトル
            StartPosition = 'CenterScreen'                                              # 表示位置
            Size          = New-Object System.Drawing.Size(480,320)
            Padding       = New-Object System.Windows.Forms.Padding(5)
        }

        $tlpMain = New-Object System.Windows.Forms.TableLayoutPanel -Property @{
            Dock     = [System.Windows.Forms.DockStyle]::Fill
            RowCount = 2
        }

        $pnlBody = New-Object System.Windows.Forms.Panel -Property @{
            Dock = [System.Windows.Forms.DockStyle]::Fill
        }
        $lblDD = New-Object System.Windows.Forms.Label -Property @{
            Dock      = [System.Windows.Forms.DockStyle]::Top
            Text      = $Message
            AutoSize  = $true
            TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
        }
        $lbxDD = New-Object System.Windows.Forms.ListBox -Property @{
            Dock        = [System.Windows.Forms.DockStyle]::Fill
            AllowDrop   = $true
        }
        $grpOpt = New-Object System.Windows.Forms.GroupBox -Property @{
            Dock     = [System.Windows.Forms.DockStyle]::Right
            Text     = "Options"
            AutoSize = $true
            Padding  = New-Object System.Windows.Forms.Padding(5)
        }
        $flpOpt = New-Object System.Windows.Forms.FlowLayoutPanel -Property @{
            Dock          = [System.Windows.Forms.DockStyle]::Fill
            AutoSize      = $true
            FlowDirection = [System.Windows.Forms.FlowDirection]::TopDown
        }
        $Checked = $true
        $Options | ForEach-Object {
            $rdoOpt = New-Object System.Windows.Forms.RadioButton -Property @{
                Text     = $_
                Checked  = $Checked
                AutoSize = $true
            }
            $flpOpt.Controls.Add($rdoOpt)
            $Checked = $false
        }

        $pnlTail = New-Object System.Windows.Forms.Panel -Property @{
            Dock = [System.Windows.Forms.DockStyle]::Fill
        }
        $btnOK = New-Object System.Windows.Forms.Button -Property @{
            Dock                    = [System.Windows.Forms.DockStyle]::Right
            Size                    = New-Object System.Drawing.Size(128, 0) # ボタン巾のみ指定可能
            Text                    = "OK"
            UseVisualStyleBackColor = $true
            DialogResult            = [Windows.Forms.DialogResult]::OK
        }
        $btnCancel = New-Object System.Windows.Forms.Button -Property @{
            Dock                    = [System.Windows.Forms.DockStyle]::Right
            Size                    = New-Object System.Drawing.Size(128, 0) # ボタン巾のみ指定可能
            Text                    = "Cancel"
            UseVisualStyleBackColor = $true
            DialogResult            = [Windows.Forms.DialogResult]::Cancel
        }

        $null = $pnlBody.Controls.Add($lbxDD)
        $null = $pnlBody.Controls.Add($lblDD)
        $null = $pnlBody.Controls.Add($grpOpt)
        $null = $grpOpt.Controls.Add($flpOpt)
        $null = $pnlTail.Controls.Add($btnOK)
        $null = $pnlTail.Controls.Add($btnCancel)
        $null = $tlpMain.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100)))
        $null = $tlpMain.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 50))) # ボタン高さはコレ
        $null = $tlpMain.Controls.Add($pnlBody, 0, 0)
        $null = $tlpMain.Controls.Add($pnlTail, 0, 1)
        $null = $frmMain.Controls.Add($tlpMain)

        $null = $frmMain.Add_Load({
            $frmMain.BringToFront()
        })
        $null = $lbxDD.Add_DragEnter({
            $_.Effect = "All"
        })
        $null = $lbxDD.Add_DragDrop({
            AddFileList $lbxDD $_.Data.GetData("FileDrop") $FileFilter
        })
        $null = $lbxDD.Add_KeyDown({
            if ($_.KeyCode -eq "Delete") {
                [array]::Reverse($lbxDD.SelectedIndices) | ForEach-Object {
                    [void]$lbxDD.Items.RemoveAt($_)
                }
            }
        })

        # フォーム表示
        if ($null -ne $FileList) {
            $null = AddFileList $lbxDD $FileList $FileFilter
        }
        $frmMain.AcceptButton = $btnOK
        $frmMain.CancelButton = $btnCancel
        $null = $frmMain.ShowDialog()
        return @($frmMain.DialogResult, $lbxDD.Items, ($flpOpt.Controls | Where-Object {$_.Checked -eq $true} | Select-Object -ExpandProperty Text))
    }
    end {}
}

こいつの呼び出しは一応こんな感じ

$ret = ShowFileListDialog `
        -Title "タイトル" `
        -Message "対象ファイルをドラッグ&ドロップしてください" `
        -FileList $args `
        -FileFilter "\.(txt)$" `
        -Options @("aaa","bbb") 
if ($ret[0] -eq "OK") {
    foreach($elm in $ret[1]) {
        $ret[2] # 選択したオプションの文字列
    }
}

Discussion