How to create a menu with the list of elements that are in a text file? "C"

Hello again.. How do I create an interactive menu with the elements that are located in a text file ? For example the file.txt looks like this:

myText.txt

New User
Old User
Delete User
Exit



I want to reproduce this as a menu in the console, like the one I wrote in next example, but the only difference is that its elements are stored here:

1
2
3
4
5
6
7
8
9
10
11
char *selector[] =
{
    "\t  New user",
    "\t  New user",
    "\t  Old  user",
    "\t  Old  user",
    "\tDelete  user",
    "\tDelete  user",
    "\t    Exit",
    "\t    Exit"
};


Here is how the code looks like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Interactive menu //

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#define END_SELECTED 6 // last highlighted choise selected
#define ARRAYSIZE(sel)	(sizeof(sel) / sizeof(sel[0]))
#define KEY_UP		72
#define KEY_DOWN	80
#define KEY_ENTER	13

void hideCursor();
void changeUser();
void SelChangeUser(unsigned int select);
void newUser();
void oldUser();
void deleteUser();

int main()
{
	hideCursor();
	changeUser();
	
	return (0);
}

void hideCursor()
{
	HANDLE cursorHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO info;
    info.dwSize = 100;
    info.bVisible = FALSE;
    SetConsoleCursorInfo(cursorHandle, &info);
}

void changeUser()
{
	static int select = 0;
	int x;
	
	SelChangeUser(select);
	
	while((x = _getch()))
	{
		switch(x)
		{
		case KEY_UP:
			{
				select -= 2;
				if(select < 0)
					select = 0;
				SelChangeUser(select);
			}break;
			
		case KEY_DOWN:
			{
				select += 2;
				if(select > END_SELECTED)
					select = END_SELECTED;
				SelChangeUser(select);
			}break;
			
		case KEY_ENTER:
			{
				switch(select)
				{
				case 0:
					{
						newUser();
						SelChangeUser(select);
					}break;
					
				case 2:
					{
						oldUser();
						SelChangeUser(select);
					}break;
					
				case 4:
					{
						deleteUser();
						SelChangeUser(select);
					}break;
					
				case 6:
					{
                        printf("\n\n\n\n\t\t\t\tExit Program");
                        exit(0);
					}break;
				}
			}break;
		}
	}
}

void SelChangeUser(unsigned int line)
{
	char *selector[] =
    {
        "\t  New user",
        "\t  New user",
        "\t  Old user",
        "\t  Old user",
		"\tDelete  user",
		"\tDelete  user",
        "\t    Exit",
        "\t    Exit"
    };
	
	unsigned int i;
	
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15);
	system("cls");
    printf("\n\t\t\t\tInteractive Menu\n");
    printf("\t\t\t\t-------------\n\n\n");
    printf("\tChoose an option:\n\n\n\n\n");
	
	for(i = 0; i < ARRAYSIZE(selector); i += 2)
    {
        if(i == select)
        {
            //if selected change color to Green
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 10);
            printf("%s\n\n", selector[i]);
        }
        else
        {
            // otherwise white color
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15);
            printf("%s\n\n", selector[i + 1]);
        }
    }
}

void newUser()
{...
}

void oldUser()
{...
}

void deleteUser()
{...
}
Last edited on
The example in the file text is not the real list, but is just to show you what I meant., Otherwise the file text will contain a list of names and another problem will be that the number of the elements of Char *name[] will be unknown at compile time, cose the list will be modify every time. So I just want that this menu with this list to behave like the example I gave you before. Thanks..
Last edited on
As you are using Windows, one way is to utilise LoadLibrary() and GetProcAddress() with the library name and proc name stored in the file along with the menu text. The file can then be parsed and the specified library/proc names used to get the address of the required function. The required menu functions are compiled into the needed .exe/.dll with the required name. So each menu option has it's own function.
Last edited on
Hello Mif,

I had to think about this for awhile and it took some extra time to look up the C code that I have forgotten, but I did come up with this for a start:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>

int main()
{
    const char* inFileName = "Input.txt";  // <--- Put File name here.

    FILE* inFile = fopen(inFileName, "r");

    if (!inFile)
    {
        printf("\n\n      File \"%s\" did not open\n", inFileName);

        return 1;
    }

    char ch = '\0';
    int rowCount = 0, charCount = 0, longestLine = 0;

    while ((ch = fgetc(inFile)) != EOF)
    {
        if (ch != '\n')
        {
            charCount++;

            putc(ch, stdout); // <--- Used for testing. Comment or remove when finished.
        }
        else
        {
            rowCount++;

            if (charCount > longestLine)
            {
                longestLine = charCount;
            }

            charCount = 0;
                
            putc('\n', stdout); // <--- Used for testing. Comment or remove when finished.
        }
    }
    
    clearerr(inFile);
    rewind(inFile);

    return 0;
}

As a start this reads the file and counts the number of lines in the file and also finds the longest string in the file.

With these 2 variables you could create an array to store the lines in the file so that you can use it whenever you need.

Andy
@Mif,
Just a tip, I advise against using <windows.h> unless either 1: You are not going to be giving this software to anyone, or 2: If you are going to give it to someone, you should make sure they have the header file.

BTW, do what Andy suggested, it looks like it will work better.

Best,
max
@agent max,

Actually the "windows.h" is not as big a problem as the "conio.h" file.
salem c once wrote:

#include<conio.h>
Obsolete since 1990, when the world stopped using DOS as a primary operating system.


Also not every compiler has a "conio.h" header file to use.

Andy
Hmm, well clang (the compiler I use) doesn't have either of those, and I'm not sure if the windows stuff would even work on my Mac.

But why would anyone use a file that's been obsolete since the 90s?
The problem is that users try to turn CLI programs (command-line interface) into TUI programs (text user interface) and want to have fancy things like colors, screen clearing, and cursor hiding. All aesthetic stuff that just adds boilerplate to the code and doesn't actually help you learn how to program as a beginner. In Windows, <conio.h> provides this functionality, even though it's some old DOS-based code from the '90s.

If OP really wants such a program and finds it more fun to work with a TUI-like program, then we're just fighting against the grain by choosing the "don't use <conio.h>" hill to die on, instead of answering the actual issue, which is actually a bit complicated, but for different reasons than the use of TUI/conio.h. But Andy's code actually shows an example of how to load text from a file, which I believe is helpful and on track with what Mif is looking to do.
Last edited on
well clang (the compiler I use) doesn't have either of those


windows.h is from Microsoft. This comes as part of the Windows SDK available for free download from Microsoft. This SDK works with any c/c++ compiler.

From the OP

create an interactive menu with the elements that are located in a text file


If you are going to create an interactive menu with elements located in a text file, then parsing the file is just one element. The much bigger element is how you associate a menu element with the required function to execute when that option is chosen.

You could have, say, a number associated with each element and then in the program have say a switch or a struct/array to associate the number with the function/code to execute. However, a this is clearly a windows program that is only going to be used on a Windows computer, then there is the option of using LoadLibrary()/GetProcAddress() as per my previous post.

There's elements of the problem missing from the original description. What about adding additional menu options? Is this required without having to re-compile the program? Or are the only options available to the user are to remove an item or change an items description?

What's the reasoning/purpose behind the requirement?
Last edited on
Hello guys again.. Well I could cut the part with colors clear screen and cursor hide, actually doesn't matter I just use that indead for fancy things like
Genado
said, but that is not my goal, I did use those before I realize I want to do other things like my OP question. Of course I could get rid of:

THIS:
 
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 10);


and put this in the code for visibility:
1
2
3
4
5
6
7
8
9
10
11
char *selector[] =
    {
        "\t  -> New user",
        "\t     New user",
        "\t  -> Old user",
        "\t     Old user",
	"\t-> Delete  user",
	"\t   Delete  user",
        "\t    -> Exit",
        "\t       Exit"
    };


or get rid of the hideCursor() function either way this really does not matter more than the other.

Non of these actually make the code look worst, they don't provide tones of lines and make it unreadable. Not even system("cls") function which provide 1 line of code and if not clear the screen you can take my code test it and see how behaves without it. I like all of your comments, I really do, but do not take it personally Genado the things you just said with CLI and TUI does not interest anyone, and my question wasn't refer to that in particular thing. Other than that let's say I did not provide that at all, how would you see a solution for my issue, of course I don't expect anyone solve it for me I just want some ideas.
But what I was trying to say, is how to store into an array my list from the text, that later I'll use it as a menu, and behave like the example code I gave you at the begining. I believe everyone has played lots of games and I just want this to turn like a menu in case someone needs to choose from a list of players and just hit ENTER to choose that player , which was my question from the start.

As I have think about first to store double name in another file that later I'll have to store that into an array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void MenuSelectExistingUser(unsigned int select)
{
    struct USER name; // a structure that I have not provide first.
    FILE *file = fopen("newUser.txt", "r"); // read from the file that contain the users
    FILE *file2 = fopen("oldUser.txt", "a+"); // store in double name that are in the first file
    int k = 0; // I can use this to count the number of lines (k * 2) later when need

    printf("\n\t\t\t\tNew type menu\n");
    printf("\t\t\t\t-------------\n\n\n");
    printf("\tSelect user:\n\n\n\n\n");

    for(k = 0; fgets(name.user, 255, file) != NULL; k++)
    {
        fprintf(file2, "%s", name.user);
        fprintf(file2, "%s", name.user);
    }

    fclose(file);
    rewind(file2);
    ...
    ..
    .
}


EDIT
I said double lines before because the array has to look like this:

1
2
3
4
5
6
7
8
9
10
11
char *selector[] =
    {
        "\t  New user",
        "\t  New user",
        "\t  Old user",
        "\t  Old user",
        "\tDelete  user",
	"\tDelete  user",
        "\t    Exit",
        "\t    Exit"
    };
Last edited on
I'll try Andy's code, and see if I can store the names in to an array, and I'll come and share if all works fine.

Thank you in advance for snippet code I appreciated ..

Mif
Hello seeplus, to answer your question, Yes this requires without recompiling the program. As you see in the code there's an options that says newUser. Well I have implemented that option but was not need to provided as what I've post is enough to understand what did I meant. So if the file.txt changes, than YES another element in the oldUser option has been added. This means the code goes from the beginning and reopen file and stores again in the array what it find in it, and if another User has been added will show even that one. In the end the purpose of all of this is to have a pointer that have the address of the user I just selected (from the MENU that I don't yet have an idea how to implement it :\), and the game goes with that name ahead and save the features for that user in another text file.

Otherwise if the elements of the users will have a limit I wouldn't bother with such a question
I would use the same code as changeUser() function does and have the number of elements fixed. like:

1
2
3
4
5
6
7
8
9
10
11
char *selector[] =
    {
        "\t George",
        "\t George",
        "\t  Kevin",
        "\t  Kevin",
        "\t Donald",
	"\t Donaldr",
        "\t   Mary",
        "\t   Mary"
    };


which I would know the last choice highlighted the number of elements ...but unfortunately I have to do that at the runtime with the number of elements in the list the last element and to store the names from the file in to an array, late to put them in the code and to behave like the one I posted.
Last edited on
The question seems to be: How to read a list from file?

This is where C++ is way simpler than C. C++ Standard Library can dynamically allocate necessary amount of memory. (Recent C Standard Library might have something similar.)

With C it is something like:
1
2
3
4
5
6
7
8
9
10
11
int SList = 20;
char* list[ SList ];
int entries = 0;
char buf[ 80 ];
// https://en.cppreference.com/w/c/io/fgets
while ( entries < SList && fgets( buf, sizeof buf, file ) != NULL )
{
  list[entries] = malloc( strlen(buf) + 1 );
  strcpy( list[entries], buf );
  ++entries;
}

The list will have entries dynamically allocated C-strings.

Handy Andy did show how to calculate the number of lines beforehand. Then the list too could be a dynamically allocated array of correct size.
Last edited on
Ok... That's what I meant. and now a list from the file text shows up and I can select one of them with my methode. the only problem I found here keskiverto when I debug says that the array list cannot be accessed. Error cannot access memory at the address .....
I have done #define SList 20 and works fine. Now list shows up and I might thing I need another variable which will be the END_SELECTED, the last item is selected from the list, which I have to parse it to this function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void newUser()
{
        const int END_SELECT; // this is the last selected item
	static int select = 0;
	int x;
	
	SelNewUser(select);
	
        END_SELECT = (with some variable from SelNewUser function)

	while((x = _getch()))
	{
		switch(x)
		{
		case KEY_UP:
			{
				select -= 2;
				if(select < 0)
					select = 0;
				SelNewUser(select);
			}break;
			
		case KEY_DOWN:
			{
				select += 2;
				if(select > END_SELECTED)
					select = END_SELECTED;
				SelNewUser(select);
			}break;
			
		case KEY_ENTER:
			{
				// I'll deal with the selected one
			}break;
		}
	}
}
Error cannot access memory at the address .....

Object lifetime, scope, and passing data between functions. Are you familiar with those topics?

You do know that everything that you dynamically allocate (with malloc()) should be appropriately deallocated with free()?

#define SList 20

Local variable
Global variable
Magic constant

A code could have 42 in several places. It would be impossible to tell whether all those values represent the same logical thing.
A macro is a "search-and-replace" text edit that preprocessor does during compilation. Such named constant we at least know to (hopefully) be same thing and it can be changed in one place.
Global variable can be changed during runtime and it has type, which can be checked and enforced.
Local variable you have to pass as argument, when you need it in another function.
Ohh yea of course I know all these things , I used your code in a function and those are not global variables just local, if this is why you asked me, otherwise the example you wrote is yours. I did not pass any of these variables as argument yet. But you should probably know what's the behavior of such a thing, I meant the error when reading memory. I know that if in your example you would be wrote something like this const int SList = 20; instead of int SList = 20; wouldn't be a problem, and you probably miss this one, I'm a beginner but i can understand this could be a mistake and maybe I didn't catch the next one saying:
You do know that everything that you dynamically allocate (with malloc()) should be appropriately deallocated with free()?. I do know I have to free the memory but I can't see you writing down the thing you asked me. All of these are wrote by you, and for that I thank you., Yes is what I wanted, but those errors by mistake are in your code.
However I do thank you..
This is a simple C test program to show the use of GetProcAddress() in a Windows program to implement a file based menu. The called functions here are in the same .exe as the main program, but they could be in any specified .exe.dll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#define _CRT_SECURE_NO_WARNINGS
#define _USE_MATH_DEFINES
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX

#include <windows.h>
#include <stdio.h>
#include <string.h>

const size_t MAXSTR = 256;

typedef void(*Proc)();

struct Menus {
	char name[MAXSTR] {};
	char* func;
	Proc proc;
};

extern "C" {
	__declspec(dllexport) void func1()
	{
		puts("This is func1\n");
	}

	__declspec(dllexport)void func2()
	{
		puts("This is func2\n");
	}

	__declspec(dllexport)void func3()
	{
		puts("This is func3\n");
	}
}

int main()
{
	const size_t MAXITEMS = 20;

	FILE* menfil = fopen("menu.txt", "r");

	if (menfil == NULL) {
		puts("Cannot open menu file");
		return 1;
	}

	Menus menus[MAXITEMS];
	size_t numitem = 0;

	for (; fgets(menus[numitem].name, MAXSTR, menfil) != NULL; ++numitem) {
		const size_t ln = strlen(menus[numitem].name);

		if (menus[numitem].name[ln - 1] == '\n')
			menus[numitem].name[ln - 1] = 0;

		char* com = strchr(menus[numitem].name, ',');

		if (com != NULL) {
			*com = 0;
			menus[numitem].func = com + 1;
			menus[numitem].proc = (Proc)GetProcAddress(/*ll*/GetModuleHandle(NULL), menus[numitem].func);
		} else
			menus[numitem].func = NULL;
	}

	while (1) {
		for (size_t i = 0; i < numitem; ++i)
			printf("%zd  %s\n", i + 1, menus[i].name);

		printf("%d  %s\n", 0, "quit");
		printf("Enter option: ");

		int opt = 0;

		scanf("%d", &opt);

		if (opt == 0)
			break;

		if (opt < 1 || opt > numitem)
			puts("Invalid option\n");
		else
			if (menus[opt - 1].proc != NULL)
				menus[opt - 1].proc();
			else
				puts("Invalid function specified\n");
	}

	return 0;
}


The menu.txt file is:


Option 1,func1
Option 2,func2
Option 3,func3


The name following the , match the name of an exported function from the program.

As an example of use:


1  Option 1
2  Option 2
3  Option 3
0  quit
Enter option: 12
Invalid option

1  Option 1
2  Option 2
3  Option 3
0  quit
Enter option: 1
This is func1

1  Option 1
2  Option 2
3  Option 3
0  quit
Enter option: 2
This is func2

1  Option 1
2  Option 2
3  Option 3
0  quit
Enter option: 3
This is func3

1  Option 1
2  Option 2
3  Option 3
0  quit
Enter option: 4
Invalid option

1  Option 1
2  Option 2
3  Option 3
0  quit
Enter option: 0

Last edited on
Topic archived. No new replies allowed.