0

I have a relatively large console app with multiple menu's and user inputs. I need to create a way for a user to "Quit" or "Back" at any time essentially break; the current method in progress. I considered using a whole bunch of conditionals but that would take some time and wouldn't be very clean. Is there any way to continually check for "Q" to quit and run a method based on that input across an entire project?

PseudoCode


What I have now:

UserInput #1;
UserInput #2;
UserInput #3; //ETC....
PromptFor Quit; //Option to quit after inputs are completed.

What I thought about trying:

UserInput #1;
PromptFor Quit#1; //Add prompt to quit after every input as a conditional.
UserInput #2; 
PromptFor Quit#2;
UserInput #3;
PromptFor Quit#3;

What I'd like to have:

PromptForQuit    //Some method of constantly checking if userInput hit's "Q" or "ESC" key is entered.
    {
      UserInput#1;
      UserInput#2;
      UserInput#3; // etc..
    }

I could solve it by hard-coding it into every single method but there has to be a better way to do it. Also, I need to output to the console that there is an option to "Q" to quit for every input.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • A custom class named `InputProcessor` with `processCommand()` can be written and its property `quitCommand` can be set as "Q". – Tiefan Ju Sep 20 '18 at 19:04
  • You possibly are looking for this https://stackoverflow.com/questions/177856/how-do-i-trap-ctrl-c-in-a-c-sharp-console-app – adityap Sep 20 '18 at 19:05
  • @TiefanJu would you happen to have a good link for something like that. I'm still learning C# so I definitely am not familiar with this. –  Sep 20 '18 at 19:06
  • something like in https://stackoverflow.com/questions/8898182/how-to-handle-key-press-event-in-console-application would help you think. – Ercan Peker Sep 20 '18 at 19:07
  • What does "quit" mean here? You want to exit the whole application, or go back to a main menu? – Rufus L Sep 20 '18 at 19:35

2 Answers2

1

Simple Answer: Just use ctrl + C to exit the Console anytime (no code needed)

If you want to do some clean up operations before existing: then you are probably looking for Console.CancelKeyPress Event

private static volatile bool cancelRequested = false;

public static void Main(string[] args)
{
    Console.CancelKeyPress += new ConsoleCancelEventHandler(ExitConsole);
    while (!cancelRequested)
    {
        // here your program will continue a WHOLE WHILE loop until user requests exit by 
        // pressing either the C console key (C) or the Break key 
        // (Ctrl+C or Ctrl+Break).

        UserInput #1;
        UserInput #2;
        UserInput #3;
    }
}

static void ExitConsole(object sender, ConsoleCancelEventArgs e)
{
    e.Cancel = true;
    cancelRequested = true;
}

Here you can find relative answers.

Bakri Bitar
  • 1,543
  • 18
  • 29
  • This should be a comment, not an answer. – Zohar Peled Sep 20 '18 at 19:08
  • Why?, what is the rule? – Bakri Bitar Sep 20 '18 at 19:09
  • @BakriBitar You aren't really answering the question. If you included an example from that link it would be better then just referring them where to look. – Jimenemex Sep 20 '18 at 19:12
  • Your answer is basically a link dump. You should either vote (or flag) for close as a duplicate, post the links as a comment, or write a proper answer, in such a way that if the links brake, your answer still stands. Quot the relevant parts of the links, consider adding a small code sample, that would make it a proper answer. – Zohar Peled Sep 20 '18 at 19:12
  • Wouldn't Console.CancelKeyPress require that I put a conditional after every input or does this detect a constant stream? –  Sep 20 '18 at 19:14
  • You can use a while loop and set the cancelRequested to true whenever you want to exit – Bakri Bitar Sep 20 '18 at 19:27
  • This answer will still wait until the `while` loop completes the current iteration before cancelling, whereas I believe the requirement was that if the user enters the cancel request at the first or second input, no more input requests should be made... – Rufus L Sep 20 '18 at 19:32
  • @RufusL yes you are right at least one loop will be executed – Bakri Bitar Sep 20 '18 at 19:59
1

I haven't done a complex console application since the early 90s. If I have an app with "with multiple menu's and user inputs", I usually use something (Windows Forms, WPF, a web app) that supports that natively. But...

If this is a big/complex enough project, and particularly if you have plans to write more than one of these, it might be worth writing a little framework based on the Model-View-Controller (MVC) pattern.

In this case, we'll actually have two models, one that's reasonably complex that describes the program flow, and a second one that is a simple Dictionary that contains the user's answers. The Controller is a simple processing loop that executes the directives in the first model. The View is very simple, but we'll see that by segregating it out, there are some advantages.

To do this, you will need to completely change the structure of your programming. I'm assuming it mostly looks like this:

 Console.WriteLine("UserInput #1");
 var response = Console.ReadLine();
 DoSomethingWith(response);
 Console.WriteLine("UserInput #2");
 response = Console.ReadLine();
 DoSomethingWith(response);
 // lather, rinse, repeat

Instead, the flow of the program will be determined by that first model. So...

The Models

The first model is the important part, but let's get the second model out of the way first. The second model (the AnswerModel) is just a List<Answer>, where an Answer looks something like:

public class Answer {
    public string StepName { get; set; }
    public string VerbatimResponse { get; set; }
    public object TypedResponse { get; set; }
    public Type ResponseType { get; set; }
}

It represents the answer to a particular question. The List of answers represents the answers to all of the user's questions so far. It may be possible to play some generics games (likely with inheritance) to make the TypedResponse property actually be properly typed, this should be enough to get us started.

The first model, the InputModel is the guts of the program. It will consist of a collection of ModelStep objects. The collection could just be a simple list (question 1, question 2, etc.) or it could be a complex graph. In particular, the EvalNextStep delegate property in the model shown below allows you to build a simple state machine (for example, if one of your questions is "What is your gender?", they you could have a separate path through the graph for males and for females).

The InputModel would look something like this (you could adapt this for your needs):

public class ModelStep {
    public string StepName { get; set; }
    public string Prompt { get; set; }
    public bool IsOptional {get; set;}
    public UserInputType InputType { get; set; }
    public TypeValidator BasicValidator { get; set; }
    public SpecificValidator AdditionalValidator { get; set; }
    public Action <List<Answer>, string> AfterInputAction { get; set; }
    public Func<List<Answer>, string, string> EvalNextStep { get; set; }
}

The StepName property is the key to everything (note that it corresponds to the StepName property of the AnswerModel). The prompt is the prompt you will use when prompting for an answer. I'm not sure if a UserInputType property is needed, but I envision it to look something like:

public enum UserInputType {
    String,
    Integer,
    Numeric,
    Enum,
}

The two Validators are used to validate the user input. The TypeValidator class would likely be abstract with concrete subclasses like:

  • StringValidator
  • IntegerValidator
  • DoubleValidator
  • EnumValidator<T> where T : enum

A TypeValidator's role in live is to take the user's input, validate that it's the proper type, and then return either an error message or the response as a properly typed object.

SpecificValidator objects would do additional validation. SpecificValidator is also likely an abstract class, with concrete subclasses like:

  • LessThanValidator<T> where T : IComparable
  • GreaterThanValidator<T> where T : IComparable
  • RangneValidator<T> where T : IComparable

The AdditionalValidator property is optional. It would provide additional validation if it was needed. It would return an error message if the validation failed.

The AfterInputAction delegate would optionally point to a function that takes all of the answers so far and the current step name, and do something with that information if needed.

The EvalNextStep delegate would take the same inputs as AfterInputAction delegate and return the "next step" to run. As noted above, this would allow you to create a simple "state machine". You may not need this, but it could make it interesting.

The Controller

The controller is the meat of the program, but it's real simple. At the start of your program, you'd hand the controller the InputModel and something that indicates the first step, and the controller would simply walk through the InputModel collection, prompting users and soliciting responses.

However, since all user interaction is in one place, it would be easy to implement your "Quit" feature. You could also implement other similar features, like:

  • Back (Go back to the previous question and see your answer. You could build a "back stack" and allow users to go back more than once if you wanted)
  • Forward (if someone used "Back", allow them to go forward - likely with a "forward stack")
  • Skip (if the question is optional, a user could skip it)

Again, since all the interaction is in the same code, you could easily provide some sort of consistent indication as to which of these commands (Quit, Back, etc.) was allowed.

The View

The temptation is to have the controller directly interact with the console using Console.WriteLine, Console.ReadLine, etc.

However, there is some advantage to abstracting this into a View and defining the View using an interface. Something like:

public interface IConsoleView {
    void Write(string stringToWrite);
    void WriteLine(string stringToWrite);
    string ReadLine(string prompt);
}

The default implementation of this interface would be braindead easy to create using the Console class. However, by making it an interface and using Dependency Injection to inject an interface implementation, you get several advantages:

  • You make your app testable - you could inject in a test View that played prompts and recorded answers, allowing you to test your logic
  • You could have a ResponseFileView that allow you to accept a "response file" that consists of the answers to the questions, allowing automation of your UI
  • etc.

You might want to extend the definition of the interface from what I have above (possibly having do-nothing implementations of the extra functions in your default Console class implementation). For example:

void WriteStepName(string stepName);
void WriteUserResponse (string userResponse);

Functions like these might be useful in the test and response file scenarios. You would provide empty implementations in the normal view.

Sorry this went on a for a while, but I've been thinking about it the last day or so. Whatever you do, don't try to do this with additional threads, that will just cause you headaches.

Flydog57
  • 6,851
  • 2
  • 17
  • 18