XAMLを使わないWPF入門40「シンプルなメモ帳アプリ」

コンピュータ

メモ帳を作ります。

ファイル名:NoXAML40Notepad.csproj


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
    <ApplicationIcon>J:\csharp\wpf\NoXAML40Notepad\app.ico</ApplicationIcon>
  </PropertyGroup>

</Project>

ファイル名:App.cs


using System.IO;
using System.Windows;
using System.Windows.Media;

namespace NoXAML40Notepad;
public class App : Application
{
    [STAThread]
    public static void Main(string[] args)
    {
        string path = "";
        if (args.Length > 0) path = args[0];
        AppHost.Run(() => Notepad.Build(path));
    }
}

ファイル名:AppHost.cs



using System.Windows;

static class AppHost
{
    public static void Run(Func<Window> factory,
        string title = "App", double width = 900, double height = 600,
        WindowStartupLocation startup = WindowStartupLocation.CenterScreen)
    {
        var app = new Application();
        var w = factory();
        Win.Init(w, title, width, height, startup);
        app.Run(w);
    }
}

ファイル名:AssemblyInfo.cs


using System.Windows;

[assembly:ThemeInfo(
    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
                                                //(used if a resource is not found in the page,
                                                // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
                                                //(used if a resource is not found in the page,
                                                // app, or any theme specific resource dictionaries)
)]

ファイル名:Dialog.cs



using Microsoft.Win32;

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;
    }
}

ファイル名:DnD.cs



using System.Windows;

static class DnD
{
    public static void AcceptFiles(UIElement target, Action<string[]> onFiles, string[]? exts = null)
    {
        if (target is FrameworkElement fe) fe.AllowDrop = true;
        target.Drop += (_, e) =>
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
            var files = (string[])e.Data.GetData(DataFormats.FileDrop)!;
            if (exts is null) { onFiles(files); return; }
            onFiles(Array.FindAll(files, f
                => Array.Exists(exts, x => f.EndsWith(x, StringComparison.OrdinalIgnoreCase))));
        };
    }
}

ファイル名:Hotkeys.cs



using System.Windows;
using System.Windows.Input;
static class Hotkeys
{
    public static void Add(Window w, ICommand cmd, Key key, ModifierKeys mods,
        ExecutedRoutedEventHandler exec, CanExecuteRoutedEventHandler? can = null)
    {
        w.CommandBindings.Add(
            new CommandBinding(cmd, exec, can ?? ((_, e) => e.CanExecute = true)));
        w.InputBindings.Add(
            new KeyBinding(cmd, key, mods));
    }
}

ファイル名:Notepad.cs



using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace NoXAML40Notepad;
static class Notepad
{
    private static string? _path;

    public static Window Build(string path="")
    {
        var tb = UI.Txt();
        var w = new Window();
        Win.Content(w, tb);

        // D&Dで開く
        DnD.AcceptFiles(w, files => { if (files.Length > 0) Load(tb, w, files[0]); },
            new[] { ".txt", ".log", ".md", ".cs", ".json", ".xml" });

        // ホットキー
        Hotkeys.Add(w,
            ApplicationCommands.Open,
            Key.O,
            ModifierKeys.Control, (_, __) => Open(tb, w));
        Hotkeys.Add(w,
            ApplicationCommands.Save,
            Key.S, ModifierKeys.Control,
            (_, __) => Save(tb, w, asNew: false));
        Hotkeys.Add(w,
            ApplicationCommands.SaveAs,
            Key.S,
            ModifierKeys.Control | ModifierKeys.Shift, (_, __) => Save(tb, w, asNew: true));
        
        if (!string.IsNullOrWhiteSpace(path)) Load(tb, w, path);

        return w; // タイトル等は AppHost.Run 側で既定設定
    }

    static void Load(TextBox tb, Window w, string path)
    {
        tb.Text = File.ReadAllText(path);
        _path = path;
        w.Title = $"MiniPad - {System.IO.Path.GetFileName(path)}";
    }

    static void Open(TextBox tb, Window w)
    {
        var p = Dialogs.Open();
        if (p is not null) Load(tb, w, p);
    }

    static void Save(TextBox tb, Window w, bool asNew)
    {
        var p = _path;
        if (asNew || string.IsNullOrEmpty(p))
            p = Dialogs.SaveAs(string.IsNullOrEmpty(_path) ? "untitled.txt" : Path.GetFileName(_path!));
        if (p is null) return;

        File.WriteAllText(p, tb.Text);
        _path = p;
        w.Title = $"MiniPad - {System.IO.Path.GetFileName(p)}";
    }
}

ファイル名:UI.cs



using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

static class UI
{
    public static TextBox Txt(bool multiline = true, bool wrap = false, string font = "Consolas", double size = 14)
    {
        var t = new TextBox
        {
            AcceptsReturn = multiline,
            AcceptsTab = multiline,
            HorizontalScrollBarVisibility = wrap ? ScrollBarVisibility.Disabled : ScrollBarVisibility.Auto,
            VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
            TextWrapping = wrap ? TextWrapping.Wrap : TextWrapping.NoWrap,
            FontFamily = new FontFamily(font),
            FontSize = size
        };
        return t;
    }
}

ファイル名:Win.cs



using System.Windows;

static class Win
{
    public static T Init<T>(T w, string title = "App", double width = 900, double height = 600,
        WindowStartupLocation startup = WindowStartupLocation.CenterScreen) where T : Window
    { w.Title = title; w.Width = width; w.Height = height; w.WindowStartupLocation = startup; return w; }

    public static T Content<T>(T w, UIElement content) where T : Window
    { w.Content = content; return w; }
}

更新履歴
20250819 初出
20250819 コマンドライン引数(args)対応

コメント