Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,54 @@
# PowerShell WPF XAML GUI Sample Codes / PowerShell XAML Designer

PowerShell だけで WPF/XAML の画面を作成・編集するためのデザイナーと、すぐに動かせる GUI サンプル集です。Visual Studio や追加モジュールを実行時に必要としません。

## はじめに(日本語)

### 動作環境

- Windows 10 / 11
- Windows PowerShell 5.1 または PowerShell 7
- WPF を表示できるデスクトップ環境

PowerShell 7 では、WPF のために `-STA` を付けて起動してください。

### XAML デザイナーを起動する

```powershell
git clone https://github.com/papanda925/PowerShell_WPF_XAML_GUI_SampleCodes.git
Set-Location .\PowerShell_WPF_XAML_GUI_SampleCodes
pwsh.exe -NoProfile -STA -File .\XamlDesigner\Start-XamlDesigner.ps1
```

Windows PowerShell 5.1 の場合は、最後の行を次に置き換えます。

```powershell
powershell.exe -NoProfile -STA -File .\XamlDesigner\Start-XamlDesigner.ps1
```

基本操作は [日本語スタートガイド](./XamlDesigner/GETTING_STARTED.ja.md)、設計と制限事項は [デザイナーのREADME](./XamlDesigner/README.md) を参照してください。

### 収録サンプル

| サンプル | 内容 | 実行例 |
|---|---|---|
| `WPF_CustomGraphicalInputBoxSample.ps1` | テキスト入力ダイアログ。OK時に入力文字列を返します | `.\WPF_CustomGraphicalInputBoxSample.ps1` |
| `WPF_GraphicalDatePickerSample.ps1` | 日付選択ダイアログ。OK時に `DateTime` を返します | `.\WPF_GraphicalDatePickerSample.ps1` |
| `WPF_SimpleWeatherFormSample` | Grid / StackPanel と画像のレイアウト例 | `.\WPF_SimpleWeatherFormSample\WPF_SimpleWeatherFormSample.ps1` |
| `WPF_InkCanvas.ps1` | InkCanvas の手書き入力例 | `.\WPF_InkCanvas.ps1` |
| `WPF_OCR_Sample.ps1` | Windows OCR API の利用例 | `.\WPF_OCR_Sample.ps1` |

入力・日付サンプルは、キャンセル時には値を返しません。スクリプトと同じフォルダーの XAML を `$PSScriptRoot` から解決するため、どのカレントディレクトリからでも起動できます。

### トラブルシューティング

- `The term ... is not recognized`:リポジトリのルートへ移動してから、上記の相対パスで実行してください。
- STA に関するエラー:`pwsh.exe -STA` または `powershell.exe -STA` で起動してください。
- 実行ポリシーで停止する:管理者の許可なくポリシーを変更せず、組織のルールに従って署名・許可された方法で実行してください。
- XAML の読み込みエラー:`x:Name` の重複、閉じタグ、名前空間、単体 `XamlReader` で利用できないイベント属性を確認してください。

---

This repository is being refocused around a **PowerShell-only WPF/XAML GUI designer** for Windows.

The goal is to make it possible to create and maintain PowerShell WPF screens even in environments where Visual Studio, Blend, paid IDE add-ons, or third-party GUI designers cannot be installed or used.
Expand Down
26 changes: 25 additions & 1 deletion Tests/Test-Repository.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,30 @@ foreach ($relativePath in $requiredFiles) {
}
}

$standaloneSamples = @(
'WPF_CustomGraphicalInputBoxSample.ps1',
'WPF_GraphicalDatePickerSample.ps1',
'WPF_SimpleWeatherFormSample\WPF_SimpleWeatherFormSample.ps1'
)
foreach ($relativePath in $standaloneSamples) {
$samplePath = Join-Path $repositoryRoot $relativePath
if (-not (Test-Path -LiteralPath $samplePath)) {
$errorsFound.Add("Required standalone sample is missing: $relativePath")
continue
}

$sampleCode = [System.IO.File]::ReadAllText($samplePath)
if (-not $sampleCode.Contains('$PSScriptRoot')) {
$errorsFound.Add("Standalone sample must resolve assets from PSScriptRoot: $relativePath")
}
if ($sampleCode -match 'Get-Content\s+\.\\') {
$errorsFound.Add("Standalone sample still reads XAML from the current directory: $relativePath")
}
if ($sampleCode -match '(?i)\.add_[a-z]+\.Invoke\(') {
$errorsFound.Add("Standalone sample uses indirect event registration: $relativePath")
}
}

$templateCodePath = Join-Path $repositoryRoot 'XamlDesigner\Templates\BlankWindow.ps1'
if (Test-Path -LiteralPath $templateCodePath) {
$templateCode = [System.IO.File]::ReadAllText($templateCodePath)
Expand Down Expand Up @@ -114,4 +138,4 @@ if ($errorsFound.Count -gt 0) {
throw "$($errorsFound.Count) repository validation error(s) found."
}

Write-Host 'PowerShell/XAML syntax, required files, generated markers, beginner UI, and CI policy checks passed.'
Write-Host 'PowerShell/XAML syntax, standalone samples, required files, generated markers, beginner UI, and CI policy checks passed.'
92 changes: 40 additions & 52 deletions WPF_CustomGraphicalInputBoxSample.ps1
Original file line number Diff line number Diff line change
@@ -1,52 +1,40 @@
#presentationframeworkを読み込み
Add-Type -AssemblyName presentationframework

#XAMLファイルを読み込んでxmlに変換
[System.Xml.XmlDocument]$xaml = Get-Content .\WPF_CustomGraphicalInputBoxSample.xaml
$xaml.GetType().FullName
#visual studio でデザインした場合に付与され、Powershellで読み込む時に不要な要素を削除
$xaml.window.RemoveAttribute("x:Class")
$xaml.window.RemoveAttribute("mc:Ignorable")

#読み込んだxamlをXmlNodeReaderにキャスト
[System.Xml.XmlNodeReader]$xamlReader = $xaml -as "System.Xml.XmlNodeReader"
$xamlReader.GetType().FullName
#Windows.Markup.XamlReaderで読み込み
[System.Windows.Window]$mainWindow = [Windows.Markup.XamlReader]::Load($xamlReader)
$mainWindow.gettype().FullName

#各種コントロールを取得
[System.Windows.Controls.Button]$okButton = $mainWindow.FindName("OkButton")
$okButton.gettype().FullName
[System.Windows.Controls.Button]$CancelButton = $mainWindow.FindName("CancelButton")
$CancelButton.gettype().FullName
[System.Windows.Controls.TextBox]$TextBox = $mainWindow.FindName("TextBox")
$TextBox.gettype().FullName
[System.Windows.Controls.Label]$Label = $mainWindow.FindName("Label")
$Label.gettype().FullName

#イベントを追加
$okButton.add_Click.Invoke({
Write-Output "Using $textBox.Text" | Out-Host
$mainWindow.DialogResult = $true
$mainWindow.Close()
return
})
$CancelButton.add_Click.Invoke({
Write-Output "Cancel Click Bye" | Out-Host
$mainWindow.DialogResult = $false
$mainWindow.Close()
return
})

#表示
#$mainWindow.showDialog() | out-null
[System.Boolean]$result = $mainWindow.showDialog()
$result.GetType().FullName
Write-Output "Result is $result" | Out-Host

if ($result -eq $true )
{
$x = $textBox.Text
$x
}
[CmdletBinding()]
param()

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Add-Type -AssemblyName PresentationFramework

# 呼び出し元のカレントディレクトリに依存せず、スクリプトと同じ場所の XAML を読む。
$xamlPath = Join-Path $PSScriptRoot 'WPF_CustomGraphicalInputBoxSample.xaml'
[xml]$xaml = Get-Content -LiteralPath $xamlPath -Raw

# Visual Studio が追加する、単体の XamlReader では不要な属性を取り除く。
$xamlRoot = $xaml.DocumentElement
[void]$xamlRoot.RemoveAttribute('Class', 'http://schemas.microsoft.com/winfx/2006/xaml')
[void]$xamlRoot.RemoveAttribute('Ignorable', 'http://schemas.openxmlformats.org/markup-compatibility/2006')

$xamlReader = [System.Xml.XmlNodeReader]::new($xaml)
try {
[System.Windows.Window]$mainWindow = [System.Windows.Markup.XamlReader]::Load($xamlReader)
}
finally {
$xamlReader.Close()
}

[System.Windows.Controls.Button]$okButton = $mainWindow.FindName('OkButton')
[System.Windows.Controls.Button]$cancelButton = $mainWindow.FindName('CancelButton')
[System.Windows.Controls.TextBox]$textBox = $mainWindow.FindName('TextBox')

$okButton.Add_Click({
$mainWindow.DialogResult = $true
})
$cancelButton.Add_Click({
$mainWindow.DialogResult = $false
})

$result = $mainWindow.ShowDialog()
if ($result -eq $true) {
$textBox.Text
}
92 changes: 41 additions & 51 deletions WPF_GraphicalDatePickerSample.ps1
Original file line number Diff line number Diff line change
@@ -1,57 +1,47 @@
#presentationframeworkを読み込み
Add-Type -AssemblyName presentationframework

#XAMLファイルを読み込んでxmlに変換
[System.Xml.XmlDocument]$xaml = Get-Content .\WPF_GraphicalDatePickerSample.xaml
$xaml.GetType().FullName
#visual studio でデザインした場合に付与され、Powershellで読み込む時に不要な要素を削除
$xaml.window.RemoveAttribute("x:Class")
$xaml.window.RemoveAttribute("mc:Ignorable")

#読み込んだxamlをXmlNodeReaderにキャスト
[System.Xml.XmlNodeReader]$xamlReader = $xaml -as "System.Xml.XmlNodeReader"
$xamlReader.GetType().FullName
#Windows.Markup.XamlReaderで読み込み
[System.Windows.Window]$mainWindow = [Windows.Markup.XamlReader]::Load($xamlReader)
$mainWindow.gettype().FullName

#各種コントロールを取得
[System.Windows.Controls.Button]$okButton = $mainWindow.FindName("OKButton")
$okButton.gettype().FullName
[System.Windows.Controls.Button]$CancelButton = $mainWindow.FindName("CancelButton")
$CancelButton.gettype().FullName
#[System.Windows.Controls.TextBox]$TextBox = $mainWindow.FindName("TextBox")
#$TextBox.gettype().FullName
[System.Windows.Controls.Label]$Label = $mainWindow.FindName("Label")
$Label.gettype().FullName
[System.Windows.Controls.Calendar]$Calendar = $mainWindow.FindName("Calendar")
$Calendar.gettype().FullName

$Calendar.SelectedDate = [datetime]::Now
$Label.Content = $Calendar.SelectedDate.ToString("'Today:' yyyy/MM/dd")

#イベントを追加
$okButton.add_Click.Invoke({
# Write-Output "Using $textBox.Text" | Out-Host
[CmdletBinding()]
param()

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Add-Type -AssemblyName PresentationFramework

$xamlPath = Join-Path $PSScriptRoot 'WPF_GraphicalDatePickerSample.xaml'
[xml]$xaml = Get-Content -LiteralPath $xamlPath -Raw

$xamlRoot = $xaml.DocumentElement
[void]$xamlRoot.RemoveAttribute('Class', 'http://schemas.microsoft.com/winfx/2006/xaml')
[void]$xamlRoot.RemoveAttribute('Ignorable', 'http://schemas.openxmlformats.org/markup-compatibility/2006')

$xamlReader = [System.Xml.XmlNodeReader]::new($xaml)
try {
[System.Windows.Window]$mainWindow = [System.Windows.Markup.XamlReader]::Load($xamlReader)
}
finally {
$xamlReader.Close()
}

[System.Windows.Controls.Button]$okButton = $mainWindow.FindName('OKButton')
[System.Windows.Controls.Button]$cancelButton = $mainWindow.FindName('CancelButton')
[System.Windows.Controls.Label]$label = $mainWindow.FindName('Label')
[System.Windows.Controls.Calendar]$calendar = $mainWindow.FindName('Calendar')

$calendar.SelectedDate = [datetime]::Today
$label.Content = ([datetime]$calendar.SelectedDate).ToString("'Selected:' yyyy/MM/dd")
$calendar.Add_SelectedDatesChanged({
if ($null -ne $calendar.SelectedDate) {
$label.Content = ([datetime]$calendar.SelectedDate).ToString("'Selected:' yyyy/MM/dd")
}
})

$okButton.Add_Click({
$mainWindow.DialogResult = $true
$mainWindow.Close()
return
})
$CancelButton.add_Click.Invoke({
Write-Output "Cancel Click Bye" | Out-Host
$cancelButton.Add_Click({
$mainWindow.DialogResult = $false
$mainWindow.Close()
return
})

#表示
#$mainWindow.showDialog() | out-null
[System.Boolean]$result = $mainWindow.showDialog()
$result.GetType().FullName
Write-Output "Result is $result" | Out-Host

if ($result -eq $true )
{
$x = $Calendar.SelectedDate.ToString()
$x
$result = $mainWindow.ShowDialog()
if (($result -eq $true) -and ($null -ne $calendar.SelectedDate)) {
[datetime]$calendar.SelectedDate
}
71 changes: 29 additions & 42 deletions WPF_SimpleWeatherFormSample/WPF_SimpleWeatherFormSample.ps1
Original file line number Diff line number Diff line change
@@ -1,43 +1,30 @@
#参考URL
#https://docs.microsoft.com/en-us/windows/apps/design/layout/grid-tutorial
#Tutorial: Use Grid and StackPanel to create a simple weather app より

#presentationframeworkを読み込み
Add-Type -AssemblyName presentationframework

#動的絶対パスを作成
$scriptPath = $MyInvocation.MyCommand.Path
Write-Output "---------scriptPath ------------"
Write-Output $scriptPath
$scriptPath2 = Split-Path -Parent $scriptPath
Write-Output "---------scriptPath2------------"
Write-Output $scriptPath2
$ImagePath = Join-Path $scriptPath2 "partially-cloudy.png"
Write-Output "----------ImagePath-------------"
Write-Output $ImagePath
Write-Output "--------------------------------"

#XAMLファイルを読み込んでxmlに変換
[System.Xml.XmlDocument]$xaml = Get-Content .\WPF_SimpleWeatherFormSample.xaml

#visual studio でデザインした場合に付与され、Powershellで読み込む時に不要な要素を削除
$xaml.window.RemoveAttribute("x:Class")
$xaml.window.RemoveAttribute("mc:Ignorable")

#読み込んだxamlをXmlNodeReaderにキャスト
[System.Xml.XmlNodeReader]$xamlReader = $xaml -as "System.Xml.XmlNodeReader"
$xamlReader.GetType().FullName
#Windows.Markup.XamlReaderで読み込み
[System.Windows.Window]$mainWindow = [Windows.Markup.XamlReader]::Load($xamlReader)
$mainWindow.gettype().FullName

#各種コントロールを取得
[System.Windows.Controls.Image]$Image = $mainWindow.FindName("imageData")
$Image.gettype().FullName
$Image.Source = $ImagePath #絶対パスでないとエラーとなるため、動的取得の絶対パスを再設定

#表示
[System.Boolean]$result = $mainWindow.showDialog()
$result.GetType().FullName
Write-Output "Result is $result" | Out-Host
[CmdletBinding()]
param()

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

# 参考: https://learn.microsoft.com/windows/apps/design/layout/grid-tutorial
Add-Type -AssemblyName PresentationFramework

$xamlPath = Join-Path $PSScriptRoot 'WPF_SimpleWeatherFormSample.xaml'
$imagePath = Join-Path $PSScriptRoot 'partially-cloudy.png'
[xml]$xaml = Get-Content -LiteralPath $xamlPath -Raw

$xamlRoot = $xaml.DocumentElement
[void]$xamlRoot.RemoveAttribute('Class', 'http://schemas.microsoft.com/winfx/2006/xaml')
[void]$xamlRoot.RemoveAttribute('Ignorable', 'http://schemas.openxmlformats.org/markup-compatibility/2006')

$xamlReader = [System.Xml.XmlNodeReader]::new($xaml)
try {
[System.Windows.Window]$mainWindow = [System.Windows.Markup.XamlReader]::Load($xamlReader)
}
finally {
$xamlReader.Close()
}

[System.Windows.Controls.Image]$image = $mainWindow.FindName('imageData')
$image.Source = $imagePath

[void]$mainWindow.ShowDialog()