Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Help with console 1

Status
Not open for further replies.

barrin

Technical User
Feb 6, 2006
25
CA
Hi,

I have two components, a RichEdit which acts as a console, and an Edit which acts as input to the console.

What I want to be able to do is prompt for several pieces of information in the console (RichEdit), while providing the information through the input (Edit).

My problem is that I am unsure how to link the two components efficiently.

Obviously this is how I prompt for the Info
ConsoleRichEdit.Lines.Add('Enter Info:');

Now I need to wait for the input, because there are other prompts to come (they are all part of a procedure). So there has to be some sort of pause in the prompting procedure, while something else handles the input. This is what I'm most unsure about.

Putting the text in the Edit is easy enough, then I need to submit it. The enter key would be used for this, is the only way to use the OnKeyPress event, then test for enter?

Now I have to access that input from the other procedure. I could use the ConsoleEdit.Text variable.

I'm sure this has been done a million times before, and there's probably a standard way to do it. That's why I'm asking. Any comments are appreciated.

Thanks,
Pat

 
Testing for #13 in the OnKeyPress is ok, you could also trap the OnExit event for your ConsoleEdit control. That way if they tab or click out of the edit box it counts as a submit.

Perhaps a better way would be to add a Submit button to the right of ConsoleEdit. Trap keypresses at the form level and when Enter is pressed, direct it to the button OnClick code.

There's no need to handle pausing - the form will wait patiently until the Submit button is pressed. At that point increment a global counter so you always know at what point in your series of questions you're at, and then proceed to the next one.
 
Thanks for the reply,

I am already trapping at the form level, so the submit button was easy to implement, but I'm not sure how I'm going to manipulate that data that I collect inside that prompting procedure. That is, how does the program know to go back to that procedure, when the console is used for several other things as well?

I'm at the point where I trapped the enter key. What parameter do I put in for Sender:TObject when calling an onclick procedure? After in the onclick procedure, i could simply store the Data to its variable -if- I knew which one the data belonged to, but the program has no way of knowing besides a long case statement or the like. I could store it in a global variable, along with an index number for the question that I am on, but how do i then get back into that procedure to deal with the data/index variables?
 
try this sort of code
Code:
[navy][i]// for automatic syntax highlighting see faq102-6487 
[/i][/navy][b]var[/b]
  GQuestion: Integer;

[b]procedure[/b] TForm1.OnActivate(Sender: TObject);
[b]begin[/b]
  GQuestion := [purple]1[/purple];
  AskQuestion;
[b]end[/b];

[b]procedure[/b] TForm1.SubmitButtonClick(Sender: TObject);
[b]begin[/b]
  [b]if[/b] ConsoleEdit.Text <> [teal]''[/teal] [b]then[/b]
  [b]begin[/b]
    inc(GQuestion);
    AskQuestion;
  [b]end[/b]
  [b]else[/b]
    ShowMessage([teal]'Please type your response to the question below'[/teal]);
[b]end[/b];

[b]procedure[/b] TForm1.AskQuestion;
[b]const[/b]
  Questions : [b]array[/b][[purple]1..4[/purple]] [b]of[/b] String = (
    [teal]'Enter info for Question 1:'[/teal],
    [teal]'Second Question:'[/teal],
    [teal]'Third Question:'[/teal],
    [teal]'Final Question:'[/teal]);
[b]begin[/b]
  [b]if[/b] GQuestion = [purple]5[/purple] [b]then[/b]
    FinishedQuestions;
  ConsoleRichEdit.Lines.Add(Questions[GQuestion]);
  ConsoleEdit.SetFocus;
[b]end[/b];

[b]procedure[/b] TForm1.FinishedQuestions;
[b]begin[/b]
  [navy][i]// code to run when all questions answered
[/i][/navy][b]end[/b];

There's no range checking here, so you should add that. But this sort of code is what you need. You don't have to store the questions in a fixed array like that, you could easily populate a TStringList or something from a database or config file.
 
I will try that when I get home today... I just have a quick question though: When I trap the enter key, I need to call the SubmitButtonClick procedure. What do I include as a parameter for Sender: TObject? I tried without anything, and it told me I was missing parameters. I am unfamiliar with what this parameter does.

Thanks, Pat
 
you can pass nil (zero pointer) as Sender. In most cases one may chose Self (= calling object) as Sender;

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
When an event is called normally, the Sender is usually the object that initiated the event. eg. in the SubmitButtonClick event in my code example above, Sender = SubmitButton.

As Daddy pointed out, you can easily pass nil; you just need to be sure your event code doesn't expect the Sender variable to be set.

An example of when you would use the Sender variable would be if you had, say, a lot of TEdit boxes, and had them all refer to the same OnExit event. Your OnExit code may verify that no invalid characters were entered in TEdit(Sender).Text, otherwise show an error message and call TEdit(Sender).SetFocus, to keep the edit focus on the same TEdit box.

Also, just noticed a logic error in my code, the AskQuestion code should be more like:
Code:
[b]if[/b] GQuestion = [purple]5[/purple] [b]then[/b]
  FinishedQuestions
[b]else[/b]
[b]begin[/b]
  ConsoleRichEdit.Lines.Add(Questions[GQuestion]);
  ConsoleEdit.SetFocus;
[b]end[/b];
 
For some reason, the forum isn't letting me post my reply. I think it will let me post in chunks though.

Hi,

I got my console working using the following code. The only problem is that it will be somewhat difficult each time I want to implement a new set of questions. I will have to write a whole procedure, instead of just passing the array of questions through a parameter or something. I don't really like global variables either, but I don't see a way around them.

Code:
var
  CMessage: String;
  CMode: Integer = 0;
  QList: Integer = 0;

procedure TRPG.ConsolePrompt(Msg: String; Mode: Integer);
begin
  case Mode of  //0 = Message, 1 = Answer
    0: begin
         ConsoleRichEdit.Lines.Add(Msg);
         ConsoleEdit.SetFocus;
       end;
    1: begin
         CMessage := Msg;
         case CMode of //1 = CreateObject
          1: MECreateObjectButtonClick(nil);
         end;
    end;
  end;
end;
 
procedure TRPG.MECreateObjectButtonClick(Sender: TObject);
const
QArray: array[1..4] of String = (
'Enter Object Name:',
'Enter Object Image:',
'Enter Object Action:',
'Enter Object Console Message:');
begin
if (QList = 0) then
begin
SetLength(Map.MapObjects, Length(Map.MapObjects) + 1);
CMode := 1; //1 = CreateObject
QList := 1;
ConsolePrompt(QArray[QList], 0);
end
else if (QList = 1) then
begin
Map.MapObjects[Length(Map.MapObjects)-1].Name := CMessage;
QList := QList + 1;
ConsolePrompt(QArray[QList], 0);
end
else if (QList = 2) then
begin
Map.MapObjects[Length(Map.MapObjects)-1].Image := CMessage;
QList := QList + 1;
ConsolePrompt(QArray[QList], 0);
end

 
else if (QList = 3) then
begin
Map.MapObjects[Length(Map.MapObjects)-1].Action := StrToInt(CMessage);
QList := QList + 1;
ConsolePrompt(QArray[QList], 0);
end
else if (QList = 4) then
begin
Map.MapObjects[Length(Map.MapObjects)-1].CMessage := CMessage;
Map.MapObjects[Length(Map.MapObjects)-1].Loc.X := Party.Location.X;
Map.MapObjects[Length(Map.MapObjects)-1].Loc.Y := Party.Location.Y;
Map.Tiles[Party.Location.X,Party.Location.Y].TType := 1; //Cant Walk on Obj.
CMode := 0;
QList := 0;
Map.OutputMap;
end;
end;

procedure TRPG.ConsoleButtonClick(Sender: TObject);
begin
if (ConsoleEdit.Text <> '') then
begin
ConsoleRichEdit.Lines.Add(ConsoleEdit.Text);
if (CMode <> 0) then
ConsolePrompt(ConsoleEdit.Text, 1);
ConsoleEdit.Clear;
end;
end;

[/code]
 
Also, I've been trying to trap the Ctrl key using a similar method to the enter key, but it doesn't seem to even count as a key in the WMHotKey procedure (it isn't even called). I assume this has something to do with shift states...
Code:
procedure TRPG.RPGCreate(Sender: TObject);
var
  Key: Word;
  Keycode: Integer;
  Shift: TShiftState; 
  Modifiers: Integer;
begin
  Keycode := 13; //ENTER
  ShortCutToKey(Keycode, Key, Shift);
  KeyID.Enter := GlobalAddAtom('ENTER');
  RegisterHotKey(Handle, KeyID.Enter, Modifiers, Key);

  Keycode := 17; //CTRL
  ShortCutToKey(Keycode, Key, Shift);
  KeyID.Ctrl := GlobalAddAtom('CTRL');
  RegisterHotKey(Handle, KeyID.Ctrl, Modifiers, Key);
end;
 
procedure TRPG.WMHotKey(var Msg: TWMHotKey);
begin
if (Msg.HotKey = KeyID.Enter) then
ConsoleButtonClick(nil)
else if (Msg.HotKey = KeyID.Ctrl) then
Map.Action;
end;

[/code]


I apologize for all the posts. I'm not sure why it wouldn't process my posts. Is there a maximum size for posts?

Thanks, Pat
 
not sure why you're bothering with all that shortcutkey stuff. Just attach this sort of code to your form's OnKeyDown event
Code:
[b]if[/b] (Shift = [ssCtrl]) [b]and[/b] (Key = VK_RETURN) [b]then[/b]
  ShowMessage([teal]'Ctrl+Enter pressed'[/teal]);

The help file has a list of virtual key codes you can use, just look under the OnKeyDown topic and follow the link.

As I indicated in my second post, you can use any method you like of getting the questions into an array. Probably the best way would be to use a TStringList to hold them, because it works exactly like an array when accessing the strings, but it's easy to get data from a number of sources, including TStringList.LoadFromFile.

I don't know the size of your application, so adding a database might be overkill - you could also just have separate .txt files with each containing a set of questions. Or a .ini file with each section containing a set of questions. Hard coding the questions is not the way to go in my opinion.

Liking/Disliking global variables is a moot point. They're frowned upon simply because it's too easy to misuse them, and make all variables global variables. Beginner programmers usually don't understand or value the concept of limited scope.

You could make the GQuestion variable a field in your TRPG form class, just add it to the private section. In practical terms it won't limit the scope much if at all, but you can feel happy having not used a global variable.
 
oops, misread your post. In my hotkey code, change VK_RETURN to VK_CONTROL. That will work.
 
The reason I was using the shortcutkey etc. was because I needed to trap the keyboard at the form level, so that when I pressed the arrow keys, my code would get executed directly rather than moving to the next tab in a tabset if it happens to be selected. I discovered that changing the Modifier to 2 fixed my problem.

As for the questions, I think I'll make a 'one-size-fits-all' sort of procedure with a dynamic array of strings as a parameter to deal with the question asking, and a smaller procedure to dimension and fill the arrays for each set of questions. I'd rather have the questions hard-coded, just because it's easier to manipulate them, and there wont be an extremely large amount of them. There will always be an event procedure needed anyway so I will have room for them.

I get the feeling that pointers aren't as common a practice anymore, but could I have an array of pointers associated with each array of questions to 'direct' the console entries to respective variables?
 
Delphi makes a good attempt at deprecating pointers. They're still useful in the right situation - most times though you can work around them.

To go that path, I'd recommend using a TObjectList which contains references to a number of TStringLists. TObjectList can be initialized to 'own' it's objects, so that you when you Free it, it also Frees all of the TStringLists.

I started writing up some example code for you to use pointers, but really it's not worth the effort. So do something like
Code:
[b]var[/b]
  GQuestions: TObjectList;
  GQuestionBatch, GQuestionNum: Integer;

[b]procedure[/b] SetupQuestions;
[b]var[/b]
  sl : TStringList;
[b]begin[/b]
  GQuestions := TObjectList.Create;
  sl := TStringList.Create;
  GQuestions.Add(sl);
  sl.Add([teal]'First Question, First Batch'[/teal]);
  sl.Add([teal]'Second Question, First Batch'[/teal]);
  [navy][i]// etc
[/i][/navy]  sl := TStringList.Create;
  GQuestions.Add(sl);
  sl.Add([teal]'First Question, Second Batch'[/teal]);
  [navy][i]// etc
[/i][/navy]  GQuestionBatch := [purple]0[/purple]; 
  GQuestionNum := [purple]0[/purple];
[b]end[/b];

[b]procedure[/b] AskQuestion;
[b]begin[/b]
  Memo1.Add(TStringList(GQuestions[GQuestionBatch])[GQuestionNum]);
[b]end[/b];

If you had your questions in a text file or files, you could create a simple loop to go through each file and load the questions using TStringList.LoadFromFile. Just name each file something like [tt]questions1.txt[/tt] and pop it in a loop.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top