TomaszRewak / C-sharp-console-gui-framework

A GUI framework for C# console applications
MIT License
1.08k stars 45 forks source link

Updating Content within InputListener #26

Closed DanBiscotti closed 3 years ago

DanBiscotti commented 3 years ago

Hi, thanks for a great library. I was testing something out for a project in which I want to update the ConsoleManager.Content from within the IInputListener so I created a few test files and it seems that the window isn't updating properly. I wondered whether perhaps it was because you shouldn't be able to do this, or if it is a genuine bug (or maybe something I'm missing)?

Code

Program.cs

ConsoleManager.Setup();
ConsoleManager.Content = new TestView(1);
var flag = new TestFlag();
var listener = new TestListener(2, flag);
while(!flag.Exit)
{
    Thread.Sleep(10);
    ConsoleManager.ReadInput(new[] { listener });
}

TestFlag.cs

public class TestFlag
{
    public bool Exit { get; set; }
}

TestListener.cs

public class TestListener : IInputListener
{
    private readonly int i;
    private readonly TestFlag flag;

    public TestListener(int i, TestFlag flag)
    {
        this.i = i;
        this.flag = flag;
    }

    public void OnInput(InputEvent inputEvent)
    {
        if (inputEvent.Key.Key == System.ConsoleKey.Enter)
        {
            ConsoleManager.Content = new TestView(i);
            var listener = new TestListener(i + 1, flag);
            while (!flag.Exit)
            {
                ConsoleManager.ReadInput(new[] { listener });
            }
            inputEvent.Handled = true;
        }
        else if(inputEvent.Key.Key == System.ConsoleKey.Q)
        {
            flag.Exit = true;
            inputEvent.Handled = true;
        }
    }
}

TestView.cs

public class TestView : SimpleControl
{
    public TestView(int i)
    {
        Content = new Border { Content = new TextBlock { Text = $"test {i}" } };
    }
}

Result

The first iteration appears as you would expect: image

However subsequent iterations look like only the changed cell is displayed (no border or "test"): image

TomaszRewak commented 3 years ago

Hey! Thanks a lot for a detailed description of the problem with a complete code example! That helped quite a lot with the investigation.

It was indeed a genuine bug. Whenever one swaps the ConsoleManager.Content, the framework clears the window completely and draws the full content of that new control on the screen. The problem was that the ConsoleManager was not clearing its internal buffer during that operation. When redrawing the content, it wasn't actually drawing the border, as according to the internal buffer that border didn't change (even though the actual terminal has been full cleared).

The bug was fixed in the newly released version 1.4.1. The fix is simple - the ConsoleManager now clears the buffer when the ConsoleManager.Content is swapped. Later on I will also try to optimize it so that if the console was already initialized, the terminal will not be fully cleared on those swaps (as there is no need for that).

So now (after upgrading the package), your code should work.

I also took a liberty of analyzing your code (even though I know it's just a quick snippet to test things out). Two comments:

Suggested fix:

    public class TestFlag
    {
        public bool Exit { get; set; }
    }

    public class TestListener : IInputListener
    {
        private readonly TestFlag flag;
        private readonly TestView testView;

        private int i = 1;

        public TestListener(TestFlag flag, TestView testView)
        {
            this.flag = flag;
            this.testView = testView;
        }

        public void OnInput(InputEvent inputEvent)
        {
            if (inputEvent.Key.Key == System.ConsoleKey.Enter)
            {
                testView.Value = ++i;
                inputEvent.Handled = true;
            }
            else if (inputEvent.Key.Key == System.ConsoleKey.Q)
            {
                flag.Exit = true;
                inputEvent.Handled = true;
            }
        }
    }

    public class TestView : SimpleControl
    {
        private readonly TextBlock textBlock = new TextBlock();

        public TestView()
        {
            Content = new Border { Content = textBlock };
            Value = 1;
        }

        public int Value
        {
            set => textBlock.Text = $"test {value}";
        }
    }

    class Program
    {
        static void Main()
        {
            var testView = new TestView();

            ConsoleManager.Setup();
            ConsoleManager.Content = testView;

            var flag = new TestFlag();
            var listener = new TestListener(flag, testView);

            while (!flag.Exit)
            {
                Thread.Sleep(10);
                ConsoleManager.ReadInput(new[] { listener });
            }
        }
    }

Please let me know if your problem is indeed fixed.

DanBiscotti commented 3 years ago

Hey, yes thanks that fixed it 👍 and thanks for the pointers, I will remember to try and avoid redrawing the whole screen