XAMLを使わないWPFプロジェクトを作成するPowerShellスクリプトを作成しました。
ファイル名:Create-NoXAMLProject.ps1
<#
.SYNOPSIS
WPF NoXAMLアプリのスケルトン(骨組み)プロジェクトを自動生成します。
.DESCRIPTION
指定したプロジェクト名のディレクトリを作成し、NoXAML形式(App.xamlやMainWindow.xaml未使用)の
WPFプロジェクト(.NET 8対応)を自動構成します。
プロジェクト名省略時はカレントディレクトリで生成します。
.PARAMETER ProjectName
作成するプロジェクト名。省略時はカレントディレクトリでスケルトンを生成します。
.EXAMPLE
# 新規MyAppディレクトリにNoXAMLプロジェクトを生成
.\Create-NoXAMLProject.ps1 -ProjectName MyApp
# 既存ディレクトリでNoXAMLプロジェクト生成
cd MyApp
..\Create-NoXAMLProject.ps1
#>
param(
[string]$ProjectName
)
if ($ProjectName) {
$Directory = "./$ProjectName"
# 1. プロジェクトフォルダの作成
if (!(Test-Path $Directory)) {
New-Item -ItemType Directory -Path $Directory | Out-Null
}
# 2. プロジェクトフォルダへ移動
Set-Location $Directory
} else {
$Directory = Get-Location
}
# 3. dotnet new wpf -f net8.0 で初期プロジェクト生成
dotnet new wpf -f net8.0 --force
# 4. 不要ファイル削除(XAMLとProgram.csを削除)
Remove-Item ".\App.xaml*" -Force -ErrorAction SilentlyContinue
Remove-Item ".\MainWindow.xaml*" -Force -ErrorAction SilentlyContinue
Remove-Item ".\Program.cs" -Force -ErrorAction SilentlyContinue
# プロジェクト名の決定
if (-not $ProjectName) {
# カレントディレクトリ名をプロジェクト名に
$ProjectName = Split-Path -Leaf $Directory
}
# AppEntry.cs生成(エントリポイント含む)
$app = @"
using System.Windows;
namespace $ProjectName;
using System.Windows;
public class AppEntry : Application
{
[STAThread] public static void Main() => new AppEntry().Run(new MainWindow());
}
"@
Set-Content -Encoding UTF8 ".\AppEntry.cs" $app
# Kit.cs生成
$kit = @"
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
// 汎用アタッチ拡張
static class Attach
{
// 親要素のChildrenに追加
public static T
AddTo<T>(this T child, Panel parent) where T : UIElement
{ parent.Children.Add(child); return child; }
// Grid(親要素)にrow,colを指定して登録
public static T
PlaceIn<T>(this T child, Grid parent, int row = 0, int col = 0, int rowSpan = 1, int colSpan = 1) where T : UIElement
{
Grid.SetRow(child, row); Grid.SetColumn(child, col);
if (rowSpan != 1) Grid.SetRowSpan(child, rowSpan);
if (colSpan != 1) Grid.SetColumnSpan(child, colSpan);
parent.Children.Add(child); return child;
}
// 親要素のContentにセット
public static T
SetTo<T>(this T child, ContentControl parent) where T : UIElement
{ parent.Content = child; return child; }
// 親要素のItemsに追加
public static T AddTo<T>(this T child, ItemsControl parent) where T : UIElement
{ parent.Items.Add(child); return child; }
}
// ビットマップソース
public static class BmpSrc
{
// ファイルパスから読み込み(ロックしない/Freeze 済み)
public static BitmapSource FromFile(string path, int? decodeW = null, int? decodeH = null)
{
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
return FromStream(stream, decodeW, decodeH);
}
// ストリームから読み込み(必要なら)
public static BitmapSource FromStream(Stream stream, int? decodeW = null, int? decodeH = null)
{
var bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad; // EndInit後にstreamを閉じられる
bi.StreamSource = stream;
if (decodeW.HasValue) bi.DecodePixelWidth = decodeW.Value;
if (decodeH.HasValue) bi.DecodePixelHeight = decodeH.Value;
bi.EndInit();
bi.Freeze();
return bi;
}
// 例外を投げたくない場合用
public static bool TryFromFile(string path, out BitmapSource? bmp, int? decodeW = null, int? decodeH = null)
{
try { bmp = FromFile(path, decodeW, decodeH); return true; }
catch { bmp = null; return false; }
}
}
// クリップボード
public static class Clipb
{
/// クリップボードへ画像を設定します。失敗時は例外をそのままスローします。
/// STA スレッドで呼び出してください(WPFのUIスレッドならOK)。
public static void SetImageOrThrow(BitmapSource bmp)
{
if (bmp is null) throw new ArgumentNullException(nameof(bmp));
Clipboard.SetImage(bmp); // 失敗時は例外が呼び出し元へ
}
/// ImageSource が BitmapSource の場合のみコピーします。そうでなければ例外。
public static void SetImageFromSourceOrThrow(ImageSource src)
{
if (src is not BitmapSource bmp)
throw new ArgumentException("ImageSource は BitmapSource である必要があります。", nameof(src));
Clipboard.SetImage(bmp);
}
}
// ダイアログ
static class Dialogs
{
public static string? Open(string filter = "Text|*.txt;*.log;*.md|All|*.*")
{ var d = new OpenFileDialog { Filter = filter }; return d.ShowDialog() == true ? d.FileName : null; }
public static string? SaveAs(string suggest = "untitled.txt", string filter = "Text|*.txt|All|*.*")
{ var d = new SaveFileDialog { FileName = suggest, Filter = filter }; return d.ShowDialog() == true ? d.FileName : null; }
public static string? Prompt(string title, string message, string defaultText = "")
{
var owner = Application.Current?.Windows.OfType<Window>().FirstOrDefault(w => w.IsActive);
var w = new Window {
Title = title, Width = 420, Height = 160,
ResizeMode = ResizeMode.NoResize,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = owner
};
var label = new TextBlock { Text = message, Margin = UiDefaults.Margin };
var tb = new TextBox { Text = defaultText, Margin = UiDefaults.Margin };
var ok = new Button { Content="OK", IsDefault = true, MinWidth = 80, Margin = UiDefaults.Margin };
var cancel= new Button { Content="Cancel",IsCancel = true, MinWidth = 80, Margin = UiDefaults.Margin };
ok.Click += (_, __) => w.DialogResult = true; // これで閉じる
w.Content = UI.Col(label, tb, UI.Row(ok, cancel));
var result = w.ShowDialog();
return result == true ? tb.Text : null;
}
}
// デフォルト値
static class UiDefaults
{
public static double Scale = 1.0;
public static string FontFamily = "Yu Gothic UI";
public static double FontSize = 14;
public static Thickness Margin = new(6);
public static double ButtonW = 96, ButtonH = 32;
public static double TextBoxW = 480, TextBoxH = 260;
public static void UseCompact() { Scale = 0.9; Margin = new(4); ButtonH = 28; }
public static void UseTouch() { Scale = 1.3; Margin = new(10); ButtonH = 44; FontSize = 16; }
}
// ボーダー
public static partial class UI // コントロールの初期化
{
public static Border Bar(double height=16)
=> new Border{ Height=height, Margin=new Thickness(6),
Background=SystemColors.HighlightBrush };
}
// ボタン
public static partial class UI // よく使うビルダー:Row/Col/Btn/Txt
{
// ボタン
public static Button
Btn(
string text, // 表示文字列
Action? onClick = null // クリック時実行するコード
)
{
var b = new Button
{
Content = text,
Margin = new Thickness(6),
MinWidth = 96
};
if (onClick != null)
b.Click += (_, __) => onClick();
return b;
}
}
// グリッド
public static partial class UI // コントロールの初期化
{
// グリッド
public static Grid
Grd(string rows = "*", string cols = "*")
{
var g = new Grid();
foreach (var r in rows.Split(',')) g.RowDefinitions.Add(new RowDefinition { Height = GrdParse(r) });
foreach (var c in cols.Split(',')) g.ColumnDefinitions.Add(new ColumnDefinition { Width = GrdParse(c) });
return g;
}
// グリッド引数文字列=>オプション値
static GridLength GrdParse(string s)
{
s = s.Trim();
if (s.Equals("Auto", System.StringComparison.OrdinalIgnoreCase)) return GridLength.Auto;
if (s.EndsWith("*")) return new GridLength(s.Length == 1 ? 1 : double.Parse(s[..^1]), GridUnitType.Star);
return new GridLength(double.Parse(s), GridUnitType.Pixel);
}
}
// イメージ
public static partial class UI
{
// Imageオブジェクトの生成
public static Image Img(
Stretch stretch = Stretch.Uniform,
double? width = null,
double? height = null,
Thickness? margin = null,
BitmapScalingMode scaling = BitmapScalingMode.HighQuality)
{
var img = new Image { Stretch = stretch };
if (width.HasValue) img.Width = width.Value;
if (height.HasValue) img.Height = height.Value;
if (margin.HasValue) img.Margin = margin.Value;
RenderOptions.SetBitmapScalingMode(img, scaling);
return img;
}
}
// メニュー
public static partial class UI // よく使うビルダー
{
// メニューバーを作成
public static Menu MBar(params MenuItem[] roots)
{
var m = new Menu();
foreach (var r in roots) m.Items.Add(r);
return m;
}
// DockPanel の上部に配置して返す
public static Menu MAddToTop(DockPanel root, params MenuItem[] roots)
{
var m = MBar(roots);
DockPanel.SetDock(m, Dock.Top);
root.Children.Add(m);
return m;
}
// ルート(サブメニューを持つ)MenuItem
public static MenuItem MiRoot(string header, params object[] items)
{
var mi = new MenuItem { Header = header };
foreach (var it in items)
{
switch (it)
{
case MenuItem m: mi.Items.Add(m); break;
case Separator s: mi.Items.Add(s); break;
case null: mi.Items.Add(new Separator()); break;
default: throw new ArgumentException("Root() の items は MenuItem / Separator / null のみ可");
}
}
return mi;
}
// クリックだけの項目
public static MenuItem MItem(string header, Action onClick)
{
var mi = new MenuItem { Header = header };
mi.Click += (_, __) => onClick();
return mi;
}
// ショートカット付き項目(Window にコマンド/キーをバインドしつつ表示も揃える)
public static MenuItem MItem(Window w, string header, Key key, ModifierKeys mods, Action onInvoke)
{
var cmd = new RoutedUICommand();
w.CommandBindings.Add(new CommandBinding(cmd, (_, __) => onInvoke(), (_, e) => e.CanExecute = true));
w.InputBindings.Add(new KeyBinding(cmd, key, mods));
return new MenuItem
{
Header = header,
Command = cmd,
InputGestureText = MFormatGesture(key, mods) // 表示用
};
}
public static Separator MSep() => new Separator();
static string MFormatGesture(Key key, ModifierKeys mods)
{
var parts = new List<string>();
if (mods.HasFlag(ModifierKeys.Control)) parts.Add("Ctrl");
if (mods.HasFlag(ModifierKeys.Shift)) parts.Add("Shift");
if (mods.HasFlag(ModifierKeys.Alt)) parts.Add("Alt");
if (mods.HasFlag(ModifierKeys.Windows)) parts.Add("Win");
parts.Add(key.ToString());
return string.Join("+", parts);
}
}
// スライダー
public static partial class UI
{
// onChanged を渡すと初期値でも一回呼び出します
public static Slider Sld(double min = 0, double max = 100, double value = 50,
Action<double>? onChanged = null, double? tick = null, bool snap = false)
{
var s = new Slider
{
Minimum = min,
Maximum = max,
Value = value,
Margin = new Thickness(6),
IsMoveToPointEnabled = true,
IsSnapToTickEnabled = snap
};
if (tick is double t) s.TickFrequency = t;
if (onChanged != null)
{
s.ValueChanged += (_, __) => onChanged(s.Value);
onChanged(value); // 初期反映
}
return s;
}
}
// スタックパネル
public static partial class UI
{
// スタックパネル(横積み)
public static StackPanel
Row(params UIElement[] kids)
{
var p=new StackPanel
{
Orientation=Orientation.Horizontal, // 水平
Margin=new Thickness(6)
};
foreach(var k in kids)
p.Children.Add(k);
return p;
}
// スタックパネル(縦積み)
public static StackPanel
Col(params UIElement[] kids)
{
var p=new StackPanel
{
Orientation=Orientation.Vertical, // 垂直
Margin=new Thickness(6)
};
foreach(var k in kids)
p.Children.Add(k);
return p;
}
}
// ステータスバー
public static partial class UI // よく使うビルダー
{
/// ステータスバーを作成し、DockPanelの下部に追加します。
/// 表示用の TextBlock も同時に作成して返します。
public static (StatusBar bar, TextBlock text) SBAddToBottom(DockPanel root, string initial = "Ready")
{
var bar = new StatusBar();
DockPanel.SetDock(bar, Dock.Bottom);
var text = new TextBlock { Text = initial, Margin = new Thickness(6, 0, 6, 0) };
bar.Items.Add(text);
root.Children.Add(bar);
return (bar, text);
}
}
// タブコントロール
public static partial class UI
{
public static TabItem Tab(string header, UIElement content)
=> new() { Header = header, Content = content };
}
// テキストブロック
public static partial class UI
{
public static TextBlock Lbl(string text="", double font=14)
=> new TextBlock{ Text=text, Margin=new Thickness(6),
VerticalAlignment=VerticalAlignment.Center, FontSize=font };
}
// テキストボックス
public static partial class UI // よく使うビルダー:Row/Col/Btn/Txt
{
// テキストボックス
public static TextBox
Txt(
bool multi=true, // 複数行
bool wrap=false // テキストの折り返し
)
{
return new TextBox
{
AcceptsReturn=multi,
AcceptsTab=multi,
TextWrapping=wrap ? TextWrapping.Wrap : TextWrapping.NoWrap,
Margin=new Thickness(6)
};
}
}
// Window関連
public static class Win
{
// 初期化
public static T
Init<T>(
this T w,
string title="Demo", // タイトル文字列
double width=640, // ウィンドウ幅
double height=420, // ウィンドウ高さ
WindowStartupLocation loc = WindowStartupLocation.CenterScreen
) where T : Window
{
w.Title=title;
w.Width=width;
w.Height=height;
w.WindowStartupLocation=loc;
return w;
}
// コンテンツをセット
public static T
Content<T>(
this T w,
UIElement content
) where T : Window
{
w.Content = content;
return w;
}
}
// イベント連携
public static class Wiring
{
// D&D: 指定拡張子だけ受け付ける。Unloadedで自動解除。
public static void AcceptFiles(FrameworkElement el, Action<string[]> onFiles, params string[] exts)
{
el.AllowDrop = true;
DragEventHandler? drop = null;
drop = (_, e) =>
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
var files = (string[])e.Data.GetData(DataFormats.FileDrop)!;
if (exts?.Length > 0)
files = files.Where(f => exts.Any(x => f.EndsWith(x, StringComparison.OrdinalIgnoreCase))).ToArray();
if (files.Length > 0) onFiles(files);
};
el.Drop += drop;
el.Unloaded += (_, __) => el.Drop -= drop;
}
// Hotkey: Actionベースで最短。Unloadedで自動解除。
public static void Hotkey(Window w, Key key, ModifierKeys mods, Action action, Func<bool>? canExecute = null)
{
var cmd = new RoutedUICommand();
ExecutedRoutedEventHandler exec = (_, __) => action();
CanExecuteRoutedEventHandler can = (_, e) => e.CanExecute = canExecute?.Invoke() ?? true;
var cb = new CommandBinding(cmd, exec, can);
var kb = new KeyBinding(cmd, key, mods);
w.CommandBindings.Add(cb);
w.InputBindings.Add(kb);
RoutedEventHandler? unload = null;
unload = (_, __) => { w.Unloaded -= unload!; w.CommandBindings.Remove(cb); w.InputBindings.Remove(kb); };
w.Unloaded += unload;
}
// 左クリックで発火
public static T OnLeftClick<T>(this T el, Action<Point> onClick, bool consume = true)
where T : FrameworkElement
{
el.MouseLeftButtonUp += (_, e) =>
{
onClick(e.GetPosition(el));
if (consume) e.Handled = true;
};
return el;
}
}
"@
Set-Content -Encoding UTF8 ".\Kit.cs" $kit
# MainWindow
$window = @"
// メインウィンドウ
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using static UI;
public sealed class MainWindow : Window
{
public MainWindow()
{
var panel = new DockPanel();
this.Init("Simple NoXAML Samples").Content(panel);
// ボタンのサンプル
Col(
Btn("Hello", () => MessageBox.Show("Clicked!"))
).AddTo(panel);
}
}
"@
Set-Content -Encoding UTF8 ".\MainWindow.cs" $window
Write-Host "NoXAMLスケルトン(エントリポイントApp.cs)生成完了: $Directory"
生成されたプロジェクトのスケルトンコード実行すると、ボタンが表示されるアプリが起動します。
コメント