2009-05-11 14 views
6

Piszę aplikację serwera i chcę, aby była oparta na konsoli. Potrzebuję, aby użytkownik mógł wprowadzać różne polecenia, ale jednocześnie istnieje możliwość, że coś zostanie wyprowadzone na konsolę podczas pisania przez użytkownika. To zakłóca bufor. Czy jest jakiś czysty sposób na zrobienie tego?C# jednoczesne wejście i wyjście konsoli?

Dzięki.

+0

Nie znam żadnej łatwej drogi, nie.Zauważyłem, że niektóre strasznie dojrzałe produkty (gdb, windbg) nie przejmują się tym, chociaż generalnie nie zrzucają asynchronicznych informacji na ekran. Jakiego rodzaju interakcji szukasz? Czy zdajesz sobie sprawę, jak myślisz, że konsola powinna się zachowywać? –

+0

To nie jest skomplikowana koncepcja, wszystko, czego chcę, to użytkownik, który może wprowadzać tekst, a podpowiedź jest po napisaniu ostatniej linii. Jeśli podczas wpisywania tekstu zostanie zapisana jakakolwiek inna linia, monit zostanie przerysowany po tym wierszu. –

Odpowiedz

4

Rozpocząłem pracę nad programem testowym, aby pokazać, w jaki sposób można podzielić konsolę na obszar wyjściowy i obszar wejściowy, w którym obszar wejściowy zostanie przesunięty w dół w miarę powiększania obszaru wyjściowego i zwiększania mocy wyjściowej. To nie jest jeszcze doskonały, ale może być w stanie przekształcić go w odpowiedzi szukasz:

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; 
    } 
+0

Wielkie dzięki, będę z tym pracował! –

0

Czy próbowałeś zadzwonić pod numer OpenStandardInput, odczytać dowolne wejście i zresetować jego pozycję, a następnie zapisać do strumienia wyjściowego. Następnie możesz ponownie zadzwonić do OpenStandardInput i ponownie wprowadzić dane do strumienia.

+0

Po prostu próbowałem, ale obawiam się, że nie jestem do końca pewny, jak to zrobić ... Po prostu próbowałem i wyrzucił wyjątek "Stream nie wspiera poszukiwania". –

1

Jeśli chcesz zezwolić na wyjście, gdy użytkownik pisze, polecam wysłanie danych wyjściowych do nowego okna. Więc możesz mieć jedno okno, które jest używane do uruchamiania aplikacji, a następnie uruchamia wątek, aby otworzyć nową konsolę do wprowadzania danych, a następnie kontynuuje wysyłanie wszelkich komunikatów wyjściowych do oryginalnego okna. Wydaje mi się, że jeśli będziesz starał się zachować wszystko w tym samym oknie, możesz przejść do zbyt wielu problemów z blokowaniem zasobów.

+0

+1. W każdym razie, jak biedny użytkownik będzie w stanie odróżnić wynik spowodowany przez ich pisanie i wynik spowodowany przez jakiś zwariowany proces w tle, który działał zaraz po naciśnięciu Enter? – MarkJ

0

Nie ma idealnego sposobu na osiągnięcie tego, jak sądzę. To, co robi telnet (przynajmniej ostatnia wersja, której używałem) nie było drukowane żadnych danych wejściowych (wystarczy przeczytać naciśnięcia klawiszy) i po prostu wydrukuj dane wyjściowe po ich otrzymaniu. Alternatywą jest przechowywanie wszelkich danych, które muszą być wyprowadzane na konsolę w buforze, i drukowanie ich tylko po zakończeniu wprowadzania komendy przez użytkownika. (Możesz nawet oznaczyć czas wyjścia, aby było bardziej oczywiste.) Naprawdę nie widzę tutaj żadnej lepszej alternatywy - nieuchronnie będziesz miał problemy z używaniem synchronicznego interfejsu We/Wy (np. Wiersza poleceń) wraz z operacje asynchroniczne w zapleczu.

1

Tego typu rzeczy stają się nieco prostszym problemem, jeśli traktujesz serwer jako aplikację klient/serwer. Niech serwer ma połączenia "n" z aplikacjami administratora klienta, które wysyłają polecenia i odbierają dane wyjściowe. Aplikacja kliencka może całkowicie oddzielić dane wejściowe i wyjściowe, ma jeden wątek do obsługi danych wejściowych i jeden do obsługi danych wyjściowych.

Wątek wyjściowy może blokować, jeśli wątek wejściowy znajduje się w środku wprowadzania linii i odblokowuje się, gdy linia zostanie anulowana lub zatwierdzona.

2

Osobiście używam obsługi zdarzeń, aby udało konsoli, która obsługuje zarówno wejście i outup w tym samym czasie, utwórz klasy ScreenManager lub cokolwiek innego, wewnątrz tej klasy dodaj pustkę RunProgram(), stwórz zdarzenie z handler'em i wymagane zmienne do odczytu klucza wejściowego "Console.ReadKey (bool) .key".

static Consolekey newKey; 

na swoim głównym programie, creat wystąpienie klasy "whatev pan nazwał go", a następnie utworzyć wątek, że przypadki metody wewnętrznej, gwint coreThread = new Thread (delegat() {myinstance.myProgramMrthod() });

zapętlaj się w gnieździe głównym aż do uruchomienia wątków. while (! Thread.IsAlive);

następnie utwórz główną pętlę programową.

następnie dla bezpieczeństwa, dołącz do niestandardowego wątku, aby program główny nie był kontynuowany, dopóki niestandardowy wątek nie zostanie zamknięty/usunięty.

customThread.Join(); 

Masz teraz dwa wątki działające osobno.

wróć do swojej klasy, stwórz przełącznik wewnątrz metody obsługi zdarzeń.

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; 
} 

trzymać całą logikę gdzieś z tym, jak chcesz obsługiwać klucze. How to use multiple modifier keys in C# może być pomocne.

Wewnątrz metody twojej instancji RunProgram() lub whatev, którą wybierzesz, aby wywołać, po wykonaniu jakiegokolwiek kodu, którego potrzebujesz, utwórz nieskończoną pętlę, aby sprawdzić zmianę klucza.

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

ta pętla przechowuje dowolny klawisz naciśnięty, a następnie sprawdza, czy jest nowy klucz, jeśli true uruchamia zdarzenie.

masz teraz rdzeń tego, czego szukasz, jeden ciąg, który zapętla askng na nowy klucz, podczas gdy główna pętla może wyświetlać dowolny tekst, który chcesz wyświetlić.

dwa naprawiane błędy z tym, co mogę pomyśleć, jeden jest "domyślny" wewnątrz przełącznika będzie drukowany na konsoli w czapkach lub łańcuchach. a drugi to dowolny tekst dodany do konsoli dodawany w punkcie kursora, więc dodaje on tekst, który użytkownik właśnie wprowadził.

Po, jak już to zrobiłem, w jaki sposób musisz zarządzać tekstem dodano do konsoli. ponownie użyję zdarzenia. Mógłbym używać metod i funkcji przez cały czas, ale wydarzenia dodają elastyczność ruchów do programu, tak myślę.

dobrze, więc chcemy mieć możliwość dodawania tekstu do konsoli, bez naruszania danych wejściowych, które wprowadzamy. utrzymywanie danych na dole;

utwórz nowego delegata, który ma signiture z argumentem typu string, void delegate myDelegate (string Arg). następnie utwórz wydarzenie z tym delegatem, nazwij go newline, newinput, whatev you like.

funkcja obsługi zdarzeń pobierze argument (odświeżenie tekstu aktualizacji konsoli: co chcesz wstawić do konsoli powyżej danych wejściowych użytkownika), pobierze tekst, który użytkownik wprowadził do konsoli, zapisze go, a następnie wydrukuj ciąg paramiterów na konsoli, a następnie wydrukuj dane wejściowe użytkowników poniżej.

osobiście wybrałem tworzenie statycznego ciągu na górze poza metodą, inicjalizuję go do pustego, ponieważ będzie on często używany i nie chcesz tworzyć nowego identyfikatora, a następnie inicjalizujesz zmienną za każdym razem, gdy metoda jest zadzwoniłem, a następnie pozbyłem się go na końcu metody, tylko po to, aby ponownie utworzyć nowy i znowu.

wywołać ciąg "input" lub cokolwiek innego.

w domyślnym obszarze uchwytu zdarzenia keychange dodaj wejście + = klawisz new.

w konsoli Consolekey.enter wpisanie tekstu na konsolę, a następnie input = string.empty Lub string = "".

w module obsługi zdarzeń dodaj trochę logiki.

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. 
+1

oldKey jest const = 0; – Gorlykio

Powiązane problemy