■ PowerShellでメモ帳開発 ～スクリプトでも頑張るGUI開発～

![PowerShell_banner.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4049822/2fd4db5e-14e9-42b3-9e38-a3728ed83c5b.png)


<br>

こんにちは :star2:
好奇心で身を滅ぼすタイプ **零壱(ゼロイチ)テクト**です。

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4049822/ec08f703-4a5c-46d9-8c78-a7527b229e57.png">

VBSから乗り換えのため、PowerShellを弄っていますが
MsgboxすらGUI系の命令はなく、WindowsシステムやVBオブジェクト等
間接的に呼び出してGUIに触れる必要があると知りました。

であれば、MsgboxだけでなくGUI全体に触れてみようと考えた次第です。
そうなると、「**PowerShell製のメモ帳**」開発は誰もが思いつくと思います。

ですが、WEBやQiitaで探しても、見つからないか
リンクが消えていたので、勉強がてら作ったので公開します。

# １．概要/仕様
・メモ帳の基本機能を踏襲
・コードを減らすため自分が使わない機能は削除
・少なくとも自分は業務に使うイメージで作成


# ２．コード
```powershell:notepad.ps1
############################################
# PowerShell メモ帳
############################################

# GUI用アセンブリ読込
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName Microsoft.VisualBasic

# ファイルパス変数
$currentFilePath = ""

# アプリ名定義
$appName = "PowerShell メモ帳"

# 検索用：グローバル変数初期化
$global:searchText = ""
$global:searchIndex = 0

# タイトル更新関数
function Update-Title {
    if ([string]::IsNullOrWhiteSpace($currentFilePath)) {
        $form.Text = $appName
    } else {
        $fileName = [System.IO.Path]::GetFileName($currentFilePath)
        $form.Text = "$fileName - " + $appName
    }
}

# フォーム作成
$form = New-Object System.Windows.Forms.Form
$form.Text = $appName
$form.Size = New-Object System.Drawing.Size(800,600)
$form.StartPosition = "CenterScreen"

# テキストボックス（メイン）
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Multiline = $true
$textBox.Dock = "Fill"
$textBox.ScrollBars = "Both"
$textBox.Font = New-Object System.Drawing.Font("Consolas", 11)
$textBox.AcceptsTab = $true
$form.Controls.Add($textBox) | Out-Null

# メニューバー作成
$menu = New-Object System.Windows.Forms.MenuStrip
$form.MainMenuStrip = $menu
$form.Controls.Add($menu) | Out-Null

# メニュー：ファイル
$fileMenu = New-Object System.Windows.Forms.ToolStripMenuItem "ファイル"
$menu.Items.Add($fileMenu) | Out-Null

# 新規作成：Ctrl+N
$newItem = New-Object System.Windows.Forms.ToolStripMenuItem "新規作成"
$newItem.ShortcutKeys = "Control, N"
$newItem.Add_Click({ 
    $textBox.Clear()
    $currentFilePath = $null
    Update-Title 
})
$fileMenu.DropDownItems.Add($newItem) | Out-Null

# 開く：Ctrl+O
$openItem = New-Object System.Windows.Forms.ToolStripMenuItem "開く"
$openItem.ShortcutKeys = "Control, O"
$openItem.Add_Click({
    $openDialog = New-Object System.Windows.Forms.OpenFileDialog
    $openDialog.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
    if ($openDialog.ShowDialog() -eq "OK") {
        $currentFilePath = $openDialog.FileName
        $textBox.Text = Get-Content $currentFilePath -Raw -Encoding UTF8
        Update-Title
    }
}) | Out-Null
$fileMenu.DropDownItems.Add($openItem) | Out-Null

# 保存（上書き）：Ctrl+S
$saveItem = New-Object System.Windows.Forms.ToolStripMenuItem "保存"
$saveItem.ShortcutKeys = "Control, S"
$saveItem.Add_Click({
    $saveDialog = New-Object System.Windows.Forms.SaveFileDialog
    $saveDialog.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
    if ($saveDialog.ShowDialog() -eq "OK") {
        $currentFilePath = $saveDialog.FileName
        [System.IO.File]::WriteAllText($currentFilePath, $textBox.Text)
        Update-Title
    }
})
$fileMenu.DropDownItems.Add($saveItem) | Out-Null

# 名前を付けて保存：Ctrl+Shift+S
$saveAsItem = New-Object System.Windows.Forms.ToolStripMenuItem "名前を付けて保存"
$saveAsItem.ShortcutKeys = "Control, Shift, S"
$saveAsItem.Add_Click({
    $saveDialog = New-Object System.Windows.Forms.SaveFileDialog
    $saveDialog.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
    if ($saveDialog.ShowDialog() -eq "OK") {
        $currentFilePath = $saveDialog.FileName
        [System.IO.File]::WriteAllText($currentFilePath, $textBox.Text)
        Update-Title
    }
})
$fileMenu.DropDownItems.Add($saveAsItem) | Out-Null

# 区切り
$fileMenu.DropDownItems.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null

# メニュー：終了
$exitItem = New-Object System.Windows.Forms.ToolStripMenuItem "終了"
$exitItem.Add_Click({ $form.Close() })
$fileMenu.DropDownItems.Add($exitItem) | Out-Null

# メニュー：編集
$editMenu = New-Object System.Windows.Forms.ToolStripMenuItem "編集"
$menu.Items.Add($editMenu) | Out-Null

# 元に戻す：Ctrl+Z
$undoItem = New-Object System.Windows.Forms.ToolStripMenuItem "元に戻す"
$undoItem.ShortcutKeys = "Control, Z"
$undoItem.Add_Click({ $textBox.Undo() })
$editMenu.DropDownItems.Add($undoItem) | Out-Null

# 区切り
$editMenu.DropDownItems.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null

# 切り取り：Ctrl+X
$cutItem = New-Object System.Windows.Forms.ToolStripMenuItem "切り取り"
$cutItem.ShortcutKeys = "Control, X"
$cutItem.Add_Click({ $textBox.Cut() })
$editMenu.DropDownItems.Add($cutItem) | Out-Null

# コピー：Ctrl+C
$copyItem = New-Object System.Windows.Forms.ToolStripMenuItem "コピー"
$copyItem.ShortcutKeys = "Control, C"
$copyItem.Add_Click({ $textBox.Copy() })
$editMenu.DropDownItems.Add($copyItem) | Out-Null

# 貼り付け：Ctrl+V
$pasteItem = New-Object System.Windows.Forms.ToolStripMenuItem "貼り付け"
$pasteItem.ShortcutKeys = "Control, V"
$pasteItem.Add_Click({ $textBox.Paste() })
$editMenu.DropDownItems.Add($pasteItem) | Out-Null

# 削除：Delete
$deleteItem = New-Object System.Windows.Forms.ToolStripMenuItem "削除"
$deleteItem.ShortcutKeys = [System.Windows.Forms.Keys]::Delete
$deleteItem.Add_Click({
    $start = $textBox.SelectionStart
    $length = $textBox.SelectionLength
    $textBox.Text = $textBox.Text.Remove($start, $length)
})
$editMenu.DropDownItems.Add($deleteItem) | Out-Null

# 区切り
$editMenu.DropDownItems.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null

# 全て選択：Ctrl+A
$selectAllItem = New-Object System.Windows.Forms.ToolStripMenuItem "すべて選択"
$selectAllItem.ShortcutKeys = "Control, A"
$selectAllItem.Add_Click({ $textBox.SelectAll() })
$editMenu.DropDownItems.Add($selectAllItem) | Out-Null

# 日付と時刻：F5
$timeDateItem = New-Object System.Windows.Forms.ToolStripMenuItem "日付と時刻"
$timeDateItem.ShortcutKeys = "F5"
$timeDateItem.Add_Click({
    $dateTime = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
    $textBox.SelectedText = $dateTime
})
$editMenu.DropDownItems.Add($timeDateItem) | Out-Null

# 区切り
$editMenu.DropDownItems.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null

# 検索：Ctrl+F
$findItem = New-Object System.Windows.Forms.ToolStripMenuItem "検索"
$findItem.ShortcutKeys = "Control, F"
$findItem.Add_Click({
    $input = [Microsoft.VisualBasic.Interaction]::InputBox("検索する文字列を入力してください", "検索")
    if ($input) {
        $global:searchText = $input
        $global:searchIndex = 0  # 先頭から検索開始

        $index = $textBox.Text.IndexOf($global:searchText, $global:searchIndex)
        if ($index -ge 0) {
            $textBox.SelectionStart = $index
            $textBox.SelectionLength = $global:searchText.Length
            $textBox.ScrollToCaret()
            $global:searchIndex = $index + $global:searchText.Length
        } else {
            [System.Windows.Forms.MessageBox]::Show("文字列が見つかりません。", "検索")
            $global:searchText = ""
            $global:searchIndex = 0
        }
    }
})
$editMenu.DropDownItems.Add($findItem) | Out-Null

# 次を検索：F3
$findNextItem = New-Object System.Windows.Forms.ToolStripMenuItem "次を検索"
$findNextItem.ShortcutKeys = [System.Windows.Forms.Keys]::F3
$findNextItem.Add_Click({
    if ([string]::IsNullOrEmpty($global:searchText)) {
        [System.Windows.Forms.MessageBox]::Show("先に検索してください。", "次を検索")
        return
    }
    $index = $textBox.Text.IndexOf($global:searchText, $global:searchIndex)
    if ($index -ge 0) {
        $textBox.SelectionStart = $index
        $textBox.SelectionLength = $global:searchText.Length
        $textBox.ScrollToCaret()
        $global:searchIndex = $index + $global:searchText.Length
    } else {
        [System.Windows.Forms.MessageBox]::Show("これ以上見つかりません。", "次を検索")
    }
})
$editMenu.DropDownItems.Add($findNextItem) | Out-Null

# 置換：Ctrl+H
$replaceItem = New-Object System.Windows.Forms.ToolStripMenuItem "置換"
$replaceItem.ShortcutKeys = "Control, H"
$replaceItem.Add_Click({
    $find = [Microsoft.VisualBasic.Interaction]::InputBox("置換したい文字列を入力してください", "置換（検索）")
    if ($find) {
        $replace = [Microsoft.VisualBasic.Interaction]::InputBox("新しい文字列を入力してください", "置換（置換後）")
        if ($replace -ne $null) {
            $textBox.Text = $textBox.Text.Replace($find, $replace)
        }
    }
})
$editMenu.DropDownItems.Add($replaceItem) | Out-Null


# アプリ実行
$form.Add_Shown({ $form.Activate() })
$form.ShowDialog() | Out-Null

```
お好きにコードをお使いください。


# ３．備考

✅ 改良案１：時間経過で自動保存する機能
　　　　　　→これは便利機能ですがコードが長くなりそうだったので割愛
      
✅ 改良案２：フォントサイズや文字色は固定なので変更できるように
　　　　　　→僕の現在の用途には不要な機能だったので割愛しました
　　　　　　→文字コードも同じです。
      
✅ タブ化したり、文字数や行数表示したり、弄り方は色々あると思います。

・「**`Out-Null`**」は「`.Add`」を実行する時にISEのログに返り値が表示されるため
　消す為に入れてあります。邪魔であれば削除しても動作に問題ないです。
 
・置換機能として、1つのダイアログに入力を2つ持たせる画面は
　コードが長くなりそうだったのでやめました。

・友人知人からプログラムを勉強したいだけど何をしたら良いか
　と聞かれる事があるのですが、その時まず提案するのが「**メモ帳**」アプリ作りです。

・WEBアプリやサイトを作るのでなければ、メモ帳開発はとても良い教材だと思います。

*****
　　それでは
　　　　　　　どこかの誰かの何かの足しになれば幸いです。
　　　　　　　　　　　　　　　　　　　　　　　　01000010 01011001 01000101
                          
# ４．参考
#### ■ @Tadataka_Takahashi様：[備忘録] PowerShellで作る「リアルタイム システム監視ダッシュボード」- PropertyGridの活用
https://qiita.com/Tadataka_Takahashi/items/a7602a66e6432716af75

#### ■ @K014C様：PowerShellでフォームの作成とファイルパスの取得
https://qiita.com/K014C/items/a1c73754c6ac3de50375

#### ■ @tatsuki1112様：PowerShellでGUIアプリを作って、勤怠を少しだけ効率化した話
https://qiita.com/tatsuki1112/items/110c7ad2a4c4a2a6d3ae