🙆

革新的なコンソールファイルexplorerを作成

に公開

概要

フォルダの移動をすべて矢印のみで完結させる。

上下で選択。
右で開く。(フォルダの場合、展開してまた表示。ファイルの場合、ファイルが実行、表示される。)
左が戻る。

操作はこれだけ。

マウスを使わないソフト。

GUIのほうが楽ではありますが、
こういった選択肢があってもいいのかなと思い作成。

フォルダを開いて、シームレスにファイルを開くことができるソフトということで、
自分の中では上出来だと感じています。

懸念点

ちらつきがひどい。

この点を改善してくださる方いらっしゃいませんか?
いい案がありましたら、コメントで教えてください。

コード

Visual stadio C# コンソールアプリで制作。

using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

public class FileBrowser
{
   
    private static string _currentDirectory;
    private static string _rootDirectory;
    private static int _selectedIndex = 0;
    private static List<FileSystemInfo> _currentItems;

    private const int MaxDisplayItems = 7; // 1ページに表示する最大項目数
    private static int _scrollOffset = 0; // 現在の表示開始位置

    private const string LastDirectoryConfigFileName = "last_directory.config";
    
    public static void Main(string[] args)
    {
        Console.OutputEncoding = System.Text.Encoding.UTF8; // 文字化け対策
        Console.CursorVisible = false; // カーソルを非表示にする

        string initialDirectory = LoadLastDirectory();

        // ★修正: 初期ディレクトリの設定ロジックをより堅牢に
        if (Directory.Exists(initialDirectory))
        {
            _rootDirectory = initialDirectory;
            _currentDirectory = initialDirectory;
            Console.WriteLine($"前回のディレクトリ {_rootDirectory} から開始します。");
            Console.WriteLine("変更する場合は、set、そうでない場合はエンターを押してください。");
            
            string c = Console.ReadLine();
            if (c.Trim() == "set") {
                set_root_folder();
            }
        }
        else // 前回パスがないか、無効な場合
        {
            set_root_folder();
          
        }

       RefreshDisplay();

        while (true)
        {
            ConsoleKeyInfo keyInfo = Console.ReadKey(true); // キー入力を読み取る (表示しない)
            HandleInput(keyInfo);
            RefreshDisplay();
            
        }
    }
    private static void set_root_folder() {
        // まずユーザーに設定を促す
        SetRootDirectory();

        // SetRootDirectory() が成功しても、_rootDirectory が設定されているか確認
        if (!string.IsNullOrEmpty(_rootDirectory) && Directory.Exists(_rootDirectory))
        {
            _currentDirectory = _rootDirectory;
        }
        else // ユーザーが有効なパスを設定しなかった場合
        {
            Console.WriteLine("有効なルートディレクトリが設定されませんでした。アプリケーションを終了します。");
            Console.ReadKey();
            return;
        }
    }
    // ルートディレクトリの設定
    private static void SetRootDirectory()
    {
        Console.Clear();
        Console.WriteLine("ルートディレクトリを設定してください (例: C:\\Users\\YourName\\Documents)");
        Console.Write("パス: ");
        string inputPath = Console.ReadLine();

        // 両端のダブルクォーテーションを除去
        inputPath = inputPath.Trim('"');

        if (Directory.Exists(inputPath))
        {
            _rootDirectory = inputPath;
            Console.WriteLine($"ルートディレクトリを {_rootDirectory} に設定しました。");
            SaveLastDirectory(inputPath); // 設定したルートディレクトリを保存
        }
        else
        {
            Console.WriteLine("指定されたディレクトリは存在しません。");
            Console.WriteLine("既定のドキュメントフォルダをルートディレクトリとして設定します。");
            _rootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            Console.WriteLine($"既定のルートディレクトリ: {_rootDirectory}");
            SaveLastDirectory(_rootDirectory); // 既定のルートディレクトリを保存
        }
        Console.WriteLine("続行するには何かキーを押してください...");
        Console.ReadKey();
    }

    // 画面表示を更新する
    private static void RefreshDisplay()
    {

        
            

        try
        {
            // ★追加: _currentDirectoryが有効なパスであることをここで再度確認
            if (!Directory.Exists(_currentDirectory))
            {
                Console.WriteLine($"エラー: 無効なディレクトリパスです。{_currentDirectory}");
                Console.WriteLine("ルートディレクトリに戻ります...");
                _currentDirectory = _rootDirectory; // 強制的にルートに戻す
                _selectedIndex = 0;
                _scrollOffset = 0;
                // ここで再度RefreshDisplayを呼び出すか、次のループで自動的に更新されるのを待つ
                // 今回は次のループに任せる
                return ;
            }

            var directories = Directory.GetDirectories(_currentDirectory)
                                    .Select(d => new DirectoryInfo(d) as FileSystemInfo)
                                    .ToList();
            var files = Directory.GetFiles(_currentDirectory)
                                .Select(f => new FileInfo(f) as FileSystemInfo)
                                .ToList();

            _currentItems = new List<FileSystemInfo>();
            _currentItems.AddRange(directories.OrderBy(d => d.Name)); // フォルダをソートして追加
            _currentItems.AddRange(files.OrderBy(f => f.Name));       // ファイルをソートして追加

            bool inst_is_update = true;
            // 選択インデックスを範囲内に調整
            if (_selectedIndex >= _currentItems.Count)
            {
                _selectedIndex = _currentItems.Count - 1;
            

            }
            if (_selectedIndex < 0)
            {
                _selectedIndex = 0;
                

            }
           
            Console.Clear();



            Console.WriteLine($"現在のディレクトリ: {_currentDirectory}");
            Console.WriteLine("---");
            if (_currentItems.Count == 0)
            {
                Console.WriteLine("このディレクトリにはファイルやフォルダがありません。");
                Console.WriteLine("---");
                Console.WriteLine("項目数: 0 | ページ: 0/0");
                return ;
            }

            

            // スクロールオフセットを調整して、選択項目が画面内に収まるようにする
            if (_selectedIndex < _scrollOffset)
            {
                _scrollOffset = _selectedIndex;
                
              
            }
            if (_selectedIndex >= _scrollOffset + MaxDisplayItems)
            {
                _scrollOffset = _selectedIndex - MaxDisplayItems + 1;
                
            }

            for (int i = 0; i < MaxDisplayItems; i++)
            {
                int itemIndex = _scrollOffset + i;
                if (itemIndex >= _currentItems.Count)
                {
                    break; // 表示する項目がもうない場合
                }

                if (itemIndex == _selectedIndex)
                {
                    Console.BackgroundColor = ConsoleColor.White;
                    Console.ForegroundColor = ConsoleColor.Black;
                }

                if (_currentItems[itemIndex] is DirectoryInfo)
                {
                    Console.WriteLine($"[ ] {_currentItems[itemIndex].Name}");
                }
                else
                {
                    Console.WriteLine(_currentItems[itemIndex].Name.Substring(0, Math.Min(_currentItems[itemIndex].Name.Length,20)));
                }

                Console.ResetColor();
            }

            // ページング情報の表示
            int totalPages = (int)Math.Ceiling((double)_currentItems.Count / MaxDisplayItems);
            int currentPage = (_scrollOffset / MaxDisplayItems) + 1;
            Console.WriteLine("---");
            Console.WriteLine($"項目数: {_currentItems.Count} | ページ: {currentPage}/{totalPages}");
        }
        catch (UnauthorizedAccessException)
        {
            Console.WriteLine("アクセスが拒否されました。このディレクトリにはアクセスできません。");
            Console.WriteLine("何かキーを押して戻ります...");
            Console.ReadKey(true);
            MoveBack();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"エラーが発生しました: {ex.Message}");
            Console.WriteLine("何かキーを押して続行します...");
            Console.ReadKey(true);
            MoveBack();
        }
        return ;
    }

    // キー入力の処理
    private static void HandleInput(ConsoleKeyInfo keyInfo)
    {
        switch (keyInfo.Key)
        {
            case ConsoleKey.W:
            case ConsoleKey.UpArrow:
                _selectedIndex = Math.Max(0, _selectedIndex - 1);
                break;
            case ConsoleKey.S:
            case ConsoleKey.DownArrow:
                _selectedIndex = Math.Min(_currentItems.Count - 1, _selectedIndex + 1);
                break;
            case ConsoleKey.D:
            case ConsoleKey.RightArrow:
                OpenSelectedItem(enterFolder: true); // trueでもファイルは開くようにOpenSelectedItemを修正
                break;
            case ConsoleKey.A:
            case ConsoleKey.LeftArrow:
                MoveBack();
                break;
            case ConsoleKey.O: // 'o'キーで開く/実行
                OpenSelectedItem(enterFolder: false);
                break;
            case ConsoleKey.Decimal: // '.'キーでルートに戻る
            case ConsoleKey.OemPeriod:
                _currentDirectory = _rootDirectory;
                _selectedIndex = 0;
                _scrollOffset = 0;
                
                break;
        }
    }

    // 選択中のアイテムを開く/実行する
    private static void OpenSelectedItem(bool enterFolder)
    {
        if (_currentItems == null || _currentItems.Count == 0) return;

        FileSystemInfo selectedItem = _currentItems[_selectedIndex];

        if (selectedItem is DirectoryInfo directory)
        {
            // フォルダに入る場合は、現在のディレクトリを更新し、選択とスクロールをリセット
            _currentDirectory = directory.FullName;
            _selectedIndex = 0;
            _scrollOffset = 0;
           
        }
        // ファイルの場合、enterFolderがtrueでもfalseでも開く
        else if (selectedItem is FileInfo file)
        {
            try
            {
                Process.Start(new ProcessStartInfo(file.FullName) { UseShellExecute = true });
            }
            catch (Exception ex)
            {
                Console.Clear();
                Console.WriteLine($"ファイルの起動に失敗しました: {ex.Message}");
                Console.WriteLine("何かキーを押して戻ります...");
                Console.ReadKey(true);
            }
        }
    }

    // 親ディレクトリに戻る
    private static void MoveBack()
    {
        // ★修正: _currentDirectoryがnullでないことを確認
        if (string.IsNullOrEmpty(_currentDirectory)) return;

        // ★修正: _rootDirectoryがnullでないことを確認
        if (string.IsNullOrEmpty(_rootDirectory) || _currentDirectory == _rootDirectory)
        {
            // ルートディレクトリの場合はこれ以上戻れない
            return;
        }

        DirectoryInfo parentDirectory = Directory.GetParent(_currentDirectory);
        if (parentDirectory != null) // nullチェックは既にありましたが、念のため
        {
            _currentDirectory = parentDirectory.FullName;
            _selectedIndex = 0; // 戻ったら選択をリセット
            _scrollOffset = 0;
        
        }
        else // _currentDirectory がルートドライブ (例: C:\) で親がない場合
        {
            // ここに到達した場合は、すでにルートディレクトリなので、何もしない
            Console.WriteLine("これ以上上の階層はありません。");
            Console.ReadKey(true); // メッセージを確認するための一時停止
            _currentDirectory = _rootDirectory; // 念のためルートに戻す
            _selectedIndex = 0;
            _scrollOffset = 0;
        }
    }

    // 最終ディレクトリパスをファイルに保存する
    private static void SaveLastDirectory(string path)
    {
        try
        {
            File.WriteAllText(LastDirectoryConfigFileName, path);
        }
        catch (Exception ex)
        {
            // 保存に失敗してもアプリケーションの動作には影響しないため、エラーは表示しない
            // Console.WriteLine($"設定の保存に失敗しました: {ex.Message}");
        }
    }

    // 最終ディレクトリパスをファイルから読み込む
    private static string LoadLastDirectory()
    {
        if (File.Exists(LastDirectoryConfigFileName))
        {
            try
            {
                string path = File.ReadAllText(LastDirectoryConfigFileName).Trim();
                if (Directory.Exists(path))
                {
                    return path;
                }
            }
            catch (Exception ex)
            {
                // 読み込みに失敗してもアプリケーションの動作には影響しないため、エラーは表示しない
                // Console.WriteLine($"設定の読み込みに失敗しました: {ex.Message}");
            }
        }
        return string.Empty; // ファイルがない、または無効なパスの場合は空文字列を返す
    }
}

Discussion