C# одновременный ввод и вывод консоли?

Я пишу серверное приложение, и я хочу, чтобы оно было консольным. Мне нужно, чтобы пользователь мог вводить разные команды, но в то же время есть возможность, что что-то будет выводиться на консоль во время записи пользователя. Это портит буфер. Есть ли чистый способ сделать это?

спасибо.

6 ответов


Я начал работу над тестовой программой, чтобы показать, как вы можете разделить консоль на область вывода и область ввода, где область ввода перемещается вниз по мере расширения области вывода с большим выходом. Это еще не идеально, но вы можете развить его в ответ, который ищете:

  static int outCol, outRow, outHeight = 10;

  static void Main(string[] args)
  {
     bool quit = false;
     System.DateTime dt = DateTime.Now;
     do
     {
        if (Console.KeyAvailable)
        {
           if (Console.ReadKey(false).Key == ConsoleKey.Escape)
              quit = true;
        }
        System.Threading.Thread.Sleep(0);
        if (DateTime.Now.Subtract(dt).TotalSeconds > .1)
        {
           dt = DateTime.Now;
           WriteOut(dt.ToString(" ss.ff"), false);
        }            
     } while (!quit);
  }

  static void WriteOut(string msg, bool appendNewLine)
  {
     int inCol, inRow;
     inCol = Console.CursorLeft;
     inRow = Console.CursorTop;

     int outLines = getMsgRowCount(outCol, msg) + (appendNewLine?1:0);
     int outBottom = outRow + outLines;
     if (outBottom > outHeight)
        outBottom = outHeight;
     if (inRow <= outBottom)
     {
        int scrollCount = outBottom - inRow + 1;
        Console.MoveBufferArea(0, inRow, Console.BufferWidth, 1, 0, inRow + scrollCount);
        inRow += scrollCount;
     }
     if (outRow + outLines > outHeight)
     {
        int scrollCount = outRow + outLines - outHeight;
        Console.MoveBufferArea(0, scrollCount, Console.BufferWidth, outHeight - scrollCount, 0, 0);
        outRow -= scrollCount;
        Console.SetCursorPosition(outCol, outRow);
     }
     Console.SetCursorPosition(outCol, outRow);
     if (appendNewLine)
        Console.WriteLine(msg);
     else
        Console.Write(msg);
     outCol = Console.CursorLeft;
     outRow = Console.CursorTop;
     Console.SetCursorPosition(inCol, inRow);
  }

  static int getMsgRowCount(int startCol, string msg)
  {
     string[] lines = msg.Split('\n');
     int result = 0;
     foreach (string line in lines)
     {
        result += (startCol + line.Length) / Console.BufferWidth;
        startCol = 0;
     }
     return result + lines.Length - 1;
  }

лично я бы использовал обработчики событий для управления консолью, которая обрабатывает как ввод, так и вывод одновременно, создает класс ScreenManager или что-то еще, внутри этого класса добавьте void RunProgram() mthod, создайте событие с обработчиком и необходимыми переменными для чтения ключа ввода "консоль.ReadKey (bool).ключевой."

static Consolekey newKey;

в вашей основной программе создайте экземпляр своего класса "whatev вы его назвали", затем создайте поток этого внутреннего метода экземпляров, поток coreThread = Новый Поток (delegate () {myinstance.myProgramMrthod()});

цикл в вашем основном, пока потоки и работает. пока (!Нитка.IsAlive);

затем создайте основной цикл программы.

while (true)
{
}

затем для safty присоединитесь к пользовательскому потоку, чтобы основная программа не продолжалась, пока пользовательский поток не будет закрыт / удален.

customThread.Join();

теперь у вас есть два потока выполняется отдельно.

назад к вашему классу, создайте переключатель внутри вашего события метод обработчика.

switch (newkey)
{
case Consolekey.enter
Console.WriteLine("enter pressed");
break;

ect, ect.

default:
Console.write(newkey); // writes character key that dont match above conditions to the screen. 
break;
}

stick allyour логика здесь с тем, как вы хотите обрабатывать ключи. как использовать несколько клавиш в C# может быть, это поможет.

внутри метода вашего экземпляра RunProgram () или whatev, который вы решили назвать, после того, как вы сделали любой код, который вам нужен, создайте бесконечный цикл для проверки изменения ключа.

while (true)
{
newKey = Console.ReadKey(true).Key;
if (newKey != oldKey)
{
KeyChange.Invoke();
}
}

этот цикл хранит любую нажатую клавишу, а затем проверяет, есть ли новый ключ, если true запускает событие.

теперь у вас есть ядро того, что вы ищете, одна строка, которая петляет askng для нового ключа, в то время как основной цикл может отображать любой текст, который вы хотите отобразить.

две исправимые ошибки с этим, что я могу думать, один" по умолчанию " внутри коммутатора будет печатать на консоли в шапках или строках. а другой-любой текст, добавленный в консоль, добавляется в точку курсора, поэтому он добавляет к тексту, который пользователь только что ввел.

hwoever я буду, так как я просто сделал это, как вы должны менеджер текст был добавлен в консоль. снова im через событие. я мог бы использовать методы и функции повсюду, но события добавляют гибкость перемещения в программу, я думаю.

хорошо, поэтому мы хотим иметь возможность добавлять текст в консоль, не нарушая ввод, который мы вводим. сохранение ввода в нижней части;

создайте новый делегат, который имеет signiture со строковым аргументом, void delegate myDelegate (string Arg). затем создать событие с этот делегат, назовите его newline, newinput, whatev вам нравится.

обработчик событий примет строковый аргумент (реперсентация текста обновления консоли: то, что вы хотите вставить в консоль над вводом пользователей), он захватит текст, который пользователь вводил в консоль, сохранит его, затем распечатает строку paramiter на консоли, а затем распечатает ввод пользователей underneith.

лично я решил создать статическую строку вверху вне метода, инициализируйте его пустым, потому что он будет часто использоваться, и вы не хотите создавать новый идентификатор, а затем инициализировать переменную каждый раз, когда вызывается метод, а затем утилизировать его в конце метода, только чтобы воссоздать новый снова и снова.

вызовите строку "input" или что-то еще.

в области по умолчанию дескриптора события keychange добавьте input +=newkey.

в Consolekey.зайдите в раздел консоли ввода writline после ввода = строка.пусто или строка ="".

в обработчике событий добавьте немного логики.

public void OnInsert(string Argument)
        {
            Console.CursorTop -= 1;

    // moves the cursor to far left so new input overwrites the old.



    // if arg string is longer, then print arg string then print input  // string.

            if (Argument.Length > input.Length)
            {
                Console.WriteLine(Argument);
                Console.WriteLine(input);
            }
            else
            {

    // if the users input if longer than the argument text then print
    // out the argument text, then print white spaces to overwrite the
    // remaining input characters still displayed on screen.


                for (int i = 0; i < input.Length;i++ )
                {
                    if (i < Argument.Length)
                    {
                        Console.Write(Argument[i]);
                    }
                    else
                    {
                        Console.Write(' ');
                    }
                }
                Console.Write(Environment.NewLine);
                Console.WriteLine(input);
            }
        }

hope this helps some of you, its not perfect, a quick put together test that works enough to be built on.

Если вам нужно разрешить вывод поступать во время ввода пользователем, я рекомендую отправить вывод в новое окно. Таким образом, у вас может быть одно окно, которое используется для запуска приложения, а затем оно порождает поток для открытия новой консоли для ввода, а затем продолжает отправлять любые выходные сообщения в исходное окно. Я думаю, вы столкнетесь со слишком многими проблемами блокировки ресурсов, если попытаетесь сохранить все в одном окне.


такого рода вещи становятся несколько более простой проблемой, если вы рассматриваете сервер как клиент / серверное приложение. Пусть сервер имеет" n " подключений к приложениям администратора клиента, которые отправляют команды и получают выходные данные. Клиентское приложение может полностью отделить вход и выход, имея один поток для обработки ввода и один для обработки вывода.

выходной поток может блокировать, если входной поток находится в середине ввода строки, и разблокировать, когда строка либо отменено или совершено.


вы пробовали звонить OpenStandardInput, чтение любого ввода и сброс его положения, а затем запись в выходной поток. После этого вы можете снова вызвать OpenStandardInput и заполнить данные обратно в поток.


Я думаю, что нет идеального способа сделать это. То, что делает telnet (по крайней мере, последняя версия, которую я использовал), не печатает вход (просто читает нажатия клавиш) и просто печатает выход по мере его поступления. Альтернативой является сохранение любых данных, которые необходимо вывести на консоль в буфере, и печать их только после того, как пользователь закончит ввод своей команды. (Вы можете даже отметить время вывода, чтобы сделать его более очевидным.) Я действительно не вижу лучшей альтернативы здесь - вы неизбежно возникнут проблемы с использованием синхронного интерфейса ввода-вывода (т. е. командной строки) вместе с асинхронными операциями в бэкэнде.