◆ 自作のコンソール風デバッグ用出力画面

この記事の続きです

コンソールに出力するには

  • コンソールから起動すればそのコンソールに出力される AttachConsole する方法
  • エクスプローラからでも絶対にコンソールウィンドウを表示するコンソールアプリケーション化する方法

の 2 つがあります

コンソールから起動した時だけの AttachConsole でよさそうですが 考えてみたらログを見たいって時はコンソールから起動してないことのほうが多いんじゃ? と思いました

再度コンソールから起動し直すのは手間ですし 問題が再現してくれないかもしれません

かといって起動時に強制的にコンソール表示するのもちょっと違います


なら自分で出力用コンソールウィンドウ作ろう!
というのが今回やること




ではいきなりですが こちらが完成品です


[DebugOutputConsole.xaml]
<Window x:Class="liblib.wpf.DebugOutputConsole"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:liblib.wpf"
        mc:Ignorable="d"
        Title="OutputConsole" Height="400" Width="600"
        Foreground="White" FontFamily="monaco" FontSize="10pt"
        SizeChanged="Window_SizeChanged">
    <ScrollViewer Opacity=".7" Background="Black" Padding="2" HorizontalScrollBarVisibility="Auto">
        <StackPanel x:Name="lines">
            <TextBlock/>
        </StackPanel>
    </ScrollViewer>
</Window>

[DebugOutputConsole.xaml.cs]
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace liblib.wpf
{
    public partial class DebugOutputConsole : Window
    {
        private int line_length { get; set; }

        private int _lines_length = 999;
        public int lines_length
        {
            get { return this._lines_length; }
            set
            {
                if (value < 0)
                {
                    throw new Exception("lines length must be larger than or equal 0");
                }

                this._lines_length = value == 0 ? int.MaxValue : value;
                this.updateLinesLength();
            }
        }

        public DebugOutputConsole()
        {
            InitializeComponent();
            this.updateLineLength();
        }

        public void write(string str)
        {
            var tb_lines = this.lines.Children;
            var text = tb_lines.Cast<TextBlock>().Last().Text;
            str = str.Replace("\r\n", "\n");
            str = str.Replace("\r", "\n");

            var text_lines = (text + str).Split('\n').SelectMany(e =>
            {
                var lines = new List<string>();
                var ll = this.line_length;

                var s = e;
                while(s.Length > ll)
                {
                    lines.Add(s.Substring(0, ll));
                    s = s.Substring(ll);
                }

                lines.Add(s);

                return lines;
            });

            tb_lines.RemoveAt(tb_lines.Count - 1);
            foreach(var line in text_lines)
            {
                tb_lines.Add(new TextBlock() { Text = line });
            }

            this.updateLinesLength();
        }

        public void writeLine(string str)
        {
            this.write(str + "\n");
        }

        public void clear()
        {
            this.lines.Children.Clear();
            this.lines.Children.Add(new TextBlock());
        }

        public void updateLineLength()
        {
            var formatted_text = new FormattedText(
                "x", CultureInfo.CurrentUICulture, System.Windows.FlowDirection.LeftToRight,
                new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch),
                this.FontSize, Brushes.White);

            this.line_length = (int)(this.lines.ActualWidth / formatted_text.Width);
        }

        public void updateLinesLength()
        {
            while (this.lines.Children.Count > this.lines_length)
            {
                this.lines.Children.RemoveAt(0);
            }
        }

        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.updateLineLength();
        }
    }
}

[DebugConsole.cs]
namespace liblib.wpf
{
    public static class DebugConsole
    {
        public static DebugOutputConsole window { get; private set; }
        private static bool window_closed { get; set; }

        private static int? _lines_length = null;
        public static int? lines_length
        {
            get { return _lines_length; }
            set
            {
                _lines_length = value;
                if (window_is_alive)
                {
                    window.lines_length = value.Value;
                }
            }
        }

        private static bool window_is_alive
            => window != null && !window_closed;

        private static void reshow()
        {
            close();
            window = new DebugOutputConsole();
            if(lines_length != null)
            {
                window.lines_length = lines_length.Value;
            }
            window.Show();
            window_closed = false;
            window.Closed += (sender, e) =>
            {
                window_closed = true;
            };
        }

        public static void show()
        {
            if (window_is_alive)
            {
                window.Show();
            }
            else
            {
                reshow();
            }
        }

        public static void hide()
        {
            if (window_is_alive)
            {
                window.Hide();
            }
        }

        public static void close()
        {
            if (window_is_alive)
            {
                window.Close();
            }
        }

        public static void write(string str)
        {
            show();
            window.write(str);
        }

        public static void writeIfExistConsole(string str)
        {
            if (window_is_alive)
            {
                show();
                window.write(str);
            }
        }

        public static void writeLine(string str)
        {
            show();
            window.writeLine(str);
        }

        public static void writeLineIfExistConsole(string str)
        {
            if (window_is_alive)
            {
                show();
                window.writeLine(str);
            }
        }

        public static void clear()
        {
            if (window_is_alive)
            {
                window.clear();
            }
        }
    }
}

DebugConsole.writeLine と DebugConsole.writeLineIfExistConsole の static 関数で書き込みます
writeLine の方はコンソールウィンドウがなければ作ります
IfExistConsole つきの方は名前の通りコンソールウィンドウがないときは何もしません

ほかにも clear/close/show/hide/write などが使えます

デバッグ用途でウィンドウはひとつあればいいですが 種類ごとに別々のウィンドウに出力したいというときは DebugConsole クラスの static メソッドではなく直接 DebugOutputConsole ウィンドウのインスタンスを作ってメソッドを呼べば可能です

横の文字数は幅に応じて計算されます
フォントが等幅じゃないとおかしなことになります
また日本語が半角サイズになるように設定されていないとずれます

保存する行数は lines_length プロパティで設定可能です
無制限(0)でもいいのですがスクロールが重くなりそうなので適度な値にしておくのが無難です


サンプル画面
DebugConsole-sample

続く

だいたい思い通りな見た目なのですが ちょっとだけ思い通りじゃないです

まずはフォント
なんか太いです

期待するのはこういうの

mintty-sample

mintty ターミナルです
フォントとサイズは一緒ですが自作の方は太くて見てみにくいです


そしてもうひとつが透過度
mintty はの画面のように背景が半透明などなんかカッコイイですよね
あと綺麗な感じがします

デバッグ用で見た目にこだわる必要はないのですが もうちょっと頑張ります


ということで   続く