MVVMで、まともなプロジェクトを作るなら……
C#/MVVM において、私が見てきた『いわゆる入門記事』ですと、フォルダが Models/ViewModels/Views 『だけ』になっています。
ですが『本格的なプロジェクト』を作成するためには、この分類では到底足りません。
迷ったことはありませんか? 「behaviorクラスはどこに置けばいいの?」「ModelとViewModelにまたがるクラスはどこに置けばいいの?」 その迷いは真っ当です!
その点について、私なりにサヤ(ChatGPT-4o)と協力して、本稿を書いています。
必要なフォルダとは?
まず、そこそこ大きなプロジェクトで必要な、 Models/ViewModels/Views を省いたフォルダ構成案を一気に羅列します。
Behaviors
言わずと知れた、behaviorを置くフォルダです。 これはViewの延長線上でありながら、Viewに大量に置くと混乱しますよね?
小規模なプロジェクトであれば、Viewsに置けばいいでしょう。
Constants
定数宣言を置くフォルダです。
定数宣言の数が少ない場合、Modelsに置いてしまっても、いいかも知れません。
Converters
型変換の処理を置くフォルダです。
BooleanToVisibilityConverter や EnumToStringConverter 等を置くといいでしょう。
Viewsと繋がりを持つ BooleanToVisibilityConverter がある場合、このフォルダは作った方がいいでしょう。
ただし、Behaviors と Converters のファイルが少ない場合、Utils フォルダにまとめてしまってもいいかも知れません。
Enums
列挙型を置くフォルダです……と、言わなくても分かりますよね(笑)
型列挙が少ないうちは、その関連のクラスに書いてもいいでしょう。
Helpers (or Utils)
これは、個人的に強くお勧めします
例えば、4K対応アプリを作成する場合には、このように DpiHelper.cs を作成して、
App.xaml.cs から EnablePerMonitorDpiAwareness() を呼び出すという使い方になります。
同様に Win32API を利用する場合、ここに置くといいでしょう。
※このクラスを利用する場合、Unsafeをプロジェクトで許可する必要があります。
using System.Runtime.InteropServices;
namespace FileHashCraft.Helpers;
public static partial class DpiHelper
{
/// <summary>
/// DpiAwarenessを設定する
/// </summary>
public static void EnablePerMonitorDpiAwareness()
{
// アプリケーションをDPI Awareに設定
if (Environment.OSVersion.Version.Major >= 6 && Environment.OSVersion.Version.Minor >= 3)
{
// 新しい SetProcessDpiAwareness
SetProcessDpiAwareness(ProcessDpiAwareness.ProcessPerMonitorDpiAware);
}
else
{
// Windows 8.1以前の場合は、SetProcessDpiAwareを使用する(非推奨)
SetProcessDPIAware();
}
}
/// <summary>
/// 新しい DPI Aware で使う引数
/// </summary>
private enum ProcessDpiAwareness
{
ProcessDpiUnaware = 0,
ProcessSystemDpiAware = 1,
ProcessPerMonitorDpiAware = 2
}
/// <summary>
/// 古い DPI Aware
/// </summary>
/// <returns></returns>
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SetProcessDPIAware();
/// <summary>
/// 新しいDPI Aware
/// </summary>
/// <param name="awareness"></param>
/// <returns></returns>
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SetProcessDpiAwareness(ProcessDpiAwareness awareness);
}
Messages
CommunityToolkit.Mvvmでの利用を想定していますが、メッセージの定義を行います。
WeakReferenceMessenger 用の定義ですね。
数が少ない場合は Service フォルダに置いても構わないでしょう。
public record CurrentDirectoryChanged(string CurrentDirectoryPath);
Services
これは、個人的に強くお勧めします
ViewModels と Models の両方に関わる処理は、ここに置きましょう。
これは WPFを中心としたMVVM論 で述べた通り、Model が ViewModel を知っていることが MVVM 的によろしくないからです。 Model→ViewModel を許容することは、それ自体が循環参照を招く可能性があり、バグの温床となります。
Resources
リソースファイルを置く場所です。
国際化対応の resource や、テーマを利用する場合はここに置きましょう。
Models/Dto
DTO(Data Transfer Object)の置き場です。
数が少ない場合は直接 Models に置いてもいいかも知れませんが、フォルダを作った方がいいでしょう。
Model層に「データ構造」と「振る舞い(ロジック)」を混ぜたくない時に、特に有効です。
これは、絶対の提案ではありません
随所に補足説明を入れたとおり、この構成が『絶対』とか『完璧』とか言うつもりは毛頭ありません。
たった一つのファイルのためにフォルダを作るくらいなら、例えば Utils フォルダにまとめても、あるいはいいかも知れません。
また、ここでは原則としてルートフォルダを前提としていますが、 規模によっては「ルートフォルダが多すぎる」という事態も起こるかもしれません。
Enums を Constants に入れてしまうなどの亜種は、考えれば幾らでも出てきそうです。
短いですが、以上です (え?まだカップ麺ができあがってない?知らんがな)。
MyApp/
├── Behaviors/
├── Constants/
├── Converters/
├── Enums/
├── Helpers/
├── Messages/
├── Models/
│ └── Dto/
├── Resources/
├── Services/
├── ViewModels/
└── Views/