如何在Perl中构建一个简单的菜单? [英] How can I build a simple menu in Perl?

查看:107
本文介绍了如何在Perl中构建一个简单的菜单?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究需要一些基本菜单功能的Perl脚本.最终,我希望每个菜单都有一些选项,然后再选择返回上一个菜单或退出.

I'm working on a Perl script that requires some basic menu functionality. Ultimately I would like each menu to have a few options and then the option to either return to the previous menu or exit.

示例:

这是一个菜单:

  1. 选择1
  2. 选择2
  3. 返回上一级菜单
  4. 退出

选择一个选项:

我目前有一个菜单子例程来制作菜单,但是没有任何功能可以使它返回到上一个菜单.

I currently have a menu subroutine making the menus, but there is no functionality allowing it to go back to the previous menu.

    sub menu
    {
        for (;;) {
            print "--------------------\n";
            print "$_[0]\n";
            print "--------------------\n";
            for (my $i = 0; $i < scalar(@{ $_[1]}); $i++) {
                print $i + 1, "\.\t ${ $_[1] }[$i]\n";
            }
            print "\n?: ";
            my $i = <STDIN>; chomp $i;
            if ($i && $i =~ m/[0-9]+/ && $i <= scalar(@{ $_[1]})) {
                return ${ $_[1] }[$i - 1];
            } else {
                print "\nInvalid input.\n\n";
            }
        }
    }

    # Using the menu
    my $choice1  = menu('Menu1 header', \@list_of_choices1);

    # I would like this menu to give the option to go back to
    # the first menu to change $choice1
    my $choice2 = menu('Menu2 header', \@list_of_choices2);

我不想对所有菜单进行硬编码,并在所有处理过程中使用if/elsif语句,所以我将菜单变成了一个函数.

I don't want to hard code all of the menus and use if/elsif statements for all of the processing so I turned the menu into a function.

我的菜单目前看起来像这样...

My menus currently look like this...

菜单标题:

  1. Choice1
  2. Choice2
  3. Choice3

?:(在此处输入)

该解决方案仍然不允许用户返回上一级菜单或退出.我当时正在考虑制作一个菜单类来处理菜单,但是我对面向对象的Perl还是不太满意.这是一个只有几个菜单的小程序,因此使用复杂的菜单构建模块可能会显得过大.我想让我的代码尽可能的简洁.

This solution still doesn't allow the user to go back to the previous menu or exit though. I was considering making a menu class to handle the menus, but I am still not very good with object oriented Perl. This is a small program with only a few menus so using a complex menu building module may be overkill. I would like to keep my code as light as possible.

感谢您的快速回复!但是,仍然存在一个问题.当我从菜单1"中选择一个选项并进入菜单2"时,我希望将菜单1"中的选择保存下来以备后用:

Thanks for the quick responses! However there is still an issue. When I select an option from "Menu1" and it progresses to "Menu2" I would like the save the choice from "Menu1" for later use:

菜单1:

  1. Choice1<-如果已选择,则存储值并转到下一个菜单
  2. Choice2<-...
  3. 退出<-退出

菜单2:

  1. Choice1<-如果已选择,则存储值并转到下一个菜单
  2. Choice2<-...
  3. 返回<-返回上一级菜单以重新选择值
  4. 退出<-退出

选择Choice1或Choice2应该将一个值存储在变量中,以供以后使用 进入下一个菜单.然后,如果您选择从Menu2返回第一个菜单,它将为您提供重新选择选项并重新定义变量的选项.我试图避免使用全局变量,这使这变得相当困难.

Selecting either Choice1 or Choice2 should store a value in a variable for later use and progress to the next menu. Then if you choose to go back to the first menu from Menu2, it will give you the option to reselect your choice and redefine the variable. I'm trying to avoid using global variables which makes this quite difficult.

在浏览完所有菜单并设置了所有这些变量的值之后,我想运行一个子例程来处理所有选择并输出最终输出.

After progressing through all of the menus and setting the values of all of these variables, I want to run a subroutine to process all of the choices and print a final output.

 sub main () {

   # DO MENU STUFF HERE

   # PROCESS RESULTS FROM MENU CHOICES
   my $output = process($menu1_choice, $menu2_choice, $menu3_choice, ... );
 }

如果有人使用类或其他数据结构对此采取了面向对象的方法,尽管这可能有些过头了,但我仍然很乐意看到它,并尝试将想法付诸实践!

Also if anyone has an object oriented approach to this using classes or some other data structure, although it may be overkill, I would still love to see it and try to wrap my head around the idea!

推荐答案

在使用Perl进行了几个月的编程之后,我了解了更多有关如何处理对象的知识,并根据Friedo的回答编写了一个简单的面向对象的菜单构建模块.

After a few more months of programming with Perl I learned much more about how to deal with objects and wrote a simple object oriented menu building module based off of friedo's answer.

# Menu.pm

#!/usr/bin/perl

package Menu;

use strict;
use warnings;

# Menu constructor
sub new {

    # Unpack input arguments
    my $class = shift;
    my (%args) = @_;
    my $title       = $args{title};
    my $choices_ref = $args{choices};
    my $noexit      = $args{noexit};

    # Bless the menu object
    my $self = bless {
        title   => $title,
        choices => $choices_ref,
        noexit  => $noexit,
    }, $class;

    return $self;
}

# Print the menu
sub print {

    # Unpack input arguments
    my $self = shift;
    my $title   =   $self->{title  };
    my @choices = @{$self->{choices}};
    my $noexit  =   $self->{noexit };

    # Print menu
    for (;;) {

        # Clear the screen
        system 'cls';

        # Print menu title
        print "========================================\n";
        print "    $title\n";
        print "========================================\n";

        # Print menu options
        my $counter = 0;
        for my $choice(@choices) {
            printf "%2d. %s\n", ++$counter, $choice->{text};
        }
        printf "%2d. %s\n", '0', 'Exit' unless $noexit;

        print "\n?: ";

        # Get user input
        chomp (my $input = <STDIN>);

        print "\n";

        # Process input
        if ($input =~ m/\d+/ && $input >= 1 && $input <= $counter) {
            return $choices[$input - 1]{code}->();
        } elsif ($input =~ m/\d+/ && !$input && !$noexit) {
            print "Exiting . . .\n";
            exit 0;
        } else {
            print "Invalid input.\n\n";
            system 'pause';
        }
    }
}

1;

使用此模块,您可以构建菜单并将它们链接在一起相对容易.请参见下面的用法示例:

Using this module you can build menus and link them together relatively easy. See example of usage below:

# test.pl

#!/usr/bin/perl

use strict;
use warnings;

use Menu;

my $menu1;
my $menu2;

# define menu1 choices
my @menu1_choices = (
    { text => 'Choice1',
      code => sub { print "I did something!\n"; }},
    { text => 'Choice2',
      code => sub { print "I did something else!\n"; }},
    { text => 'Go to Menu2',
      code => sub { $menu2->print(); }},
);

# define menu2 choices
my @menu2_choices = (
    { text => 'Choice1',
      code => sub { print "I did something in menu 2!\n"; }},
    { text => 'Choice2',
      code => sub { print "I did something else in menu 2!\n"; }},
    { text => 'Go to Menu1',
      code => sub { $menu1->print(); }},
);

# Build menu1
$menu1 = Menu->new(
    title   => 'Menu1',
    choices => \@menu1_choices,
);

# Build menu2
$menu2 = Menu->new(
    title   => 'Menu2',
    choices => \@menu2_choices,
    noexit  => 1,
);

# Print menu1
$menu1->print();

此代码将创建一个带有子菜单的简单菜单.进入子菜单后,您可以轻松返回上一级菜单.

This code will create a simple menu with a submenu. Once in the submenu you can easily go back to the previous menu.

感谢所有的精彩回答!他们真的帮助我解决了这个问题,如果没有所有帮助,我认为我不会得到如此好的解决方案!

Thanks for all of the great answers! They really helped me figure this out and I don't think i would have ended up with such a good solution without all the help!

更好的解决方案:

告别那些难看的哈希数组!

Menu.pm和Item.pm模块内部的一些代码可能看起来有些混乱,但是这种新设计使构建菜单本身的界面更加整洁和高效.

Some of the code internal to the Menu.pm and Item.pm modules may look slightly confusing, but this new design makes the interface of building the menus themselves much cleaner and more efficient.

在经过一些仔细的代码重做并将各个菜单项变成自己的对象之后,我能够创建一个更加简洁的界面来创建菜单.这是我的新代码:

After some careful code reworking and making the individual menu items into their own objects I was able to create a much cleaner interface for creating menus. Here is my new code:

这是一个测试脚本,显示了如何使用模块来构建菜单的示例.

This is a test script showing an example of how to use the modules to build menus.

# test.pl

#!/usr/bin/perl

# Always use these
use strict;
use warnings;

# Other use statements
use Menu;

# Create a menu object
my $menu = Menu->new();

# Add a menu item
$menu->add(
    'Test'  => sub { print "This is a test\n";  system 'pause'; },
    'Test2' => sub { print "This is a test2\n"; system 'pause'; },
    'Test3' => sub { print "This is a test3\n"; system 'pause'; },
);

# Allow the user to exit directly from the menu
$menu->exit(1);

# Disable a menu item
$menu->disable('Test2');
$menu->print();

# Do not allow the user to exit directly from the menu
$menu->exit(0);

# Enable a menu item
$menu->enable('Test2');
$menu->print();

Menu.pm模块用于构建菜单对象.这些菜单对象可以包含多个Menu :: Item对象.这些对象存储在一个数组中,因此可以保留其顺序.

The Menu.pm module is used to build menu objects. These menu objects can contain multiple Menu::Item objects. The objects are stored in an array so their order is preserved.

# Menu.pm

#!/usr/bin/perl

package Menu;

# Always use these
use strict;
use warnings;

# Other use statements
use Carp;
use Menu::Item;

# Menu constructor
sub new {

    # Unpack input arguments
    my ($class, $title) = @_;

    # Define a default title
    if (!defined $title) {
        $title = 'MENU';
    }

    # Bless the Menu object
    my $self = bless {
        _title => $title,
        _items => [],
        _exit  => 0,
    }, $class;

    return $self;
}

# Title accessor method
sub title {
    my ($self, $title) = @_;
    $self->{_title} = $title if defined $title;
    return $self->{_title};
}

# Items accessor method
sub items {
    my ($self, $items) = @_;
    $self->{_items} = $items if defined $items;
    return $self->{_items};
}

# Exit accessor method
sub exit {
    my ($self, $exit) = @_;
    $self->{_exit} = $exit if defined $exit;
    return $self->{_exit};
}

# Add item(s) to the menu
sub add {

    # Unpack input arguments
    my ($self, @add) = @_;
    croak 'add() requires name-action pairs' unless @add % 2 == 0;

    # Add new items
    while (@add) {
        my ($name, $action) = splice @add, 0, 2;

        # If the item already exists, remove it
        for my $index(0 .. $#{$self->{_items}}) {
            if ($name eq $self->{_items}->[$index]->name()) {
                splice @{$self->{_items}}, $index, 1;
            }
        }

        # Add the item to the end of the menu
        my $item = Menu::Item->new($name, $action);
        push @{$self->{_items}}, $item;
    }

    return 0;
}

# Remove item(s) from the menu
sub remove {

    # Unpack input arguments
    my ($self, @remove) = @_;

    # Remove items
    for my $name(@remove) {

        # If the item exists, remove it
        for my $index(0 .. $#{$self->{_items}}) {
            if ($name eq $self->{_items}->[$index]->name()) {
                splice @{$self->{_items}}, $index, 1;
            }
        }
    }

    return 0;
}

# Disable item(s)
sub disable {

    # Unpack input arguments
    my ($self, @disable) = @_;

    # Disable items
    for my $name(@disable) {

        # If the item exists, disable it
        for my $index(0 .. $#{$self->{_items}}) {
            if ($name eq $self->{_items}->[$index]->name()) {
                $self->{_items}->[$index]->active(0);
            }
        }
    }

    return 0;
}

# Enable item(s)
sub enable {

    # Unpack input arguments
    my ($self, @enable) = @_;

    # Disable items
    for my $name(@enable) {

        # If the item exists, enable it
        for my $index(0 .. $#{$self->{_items}}) {
            if ($name eq $self->{_items}->[$index]->name()) {
                $self->{_items}->[$index]->active(1);
            }
        }
    }
}

# Print the menu
sub print {

    # Unpack input arguments
    my ($self) = @_;

    # Print the menu
    for (;;) {
        system 'cls';

        # Print the title
        print "========================================\n";
        print "    $self->{_title}\n";
        print "========================================\n";

        # Print menu items
        for my $index(0 .. $#{$self->{_items}}) {
            my $name   = $self->{_items}->[$index]->name();
            my $active = $self->{_items}->[$index]->active();
            if ($active) {
                printf "%2d. %s\n", $index + 1, $name;
            } else {
                print "\n";
            }
        }
        printf "%2d. %s\n", 0, 'Exit' if $self->{_exit};

        # Get user input
        print "\n?: ";
        chomp (my $input = <STDIN>);

        # Process user input
        if ($input =~ m/^\d+$/ && $input > 0 && $input <= scalar @{$self->{_items}}) {
            my $action = $self->{_items}->[$input - 1]->action();
            my $active = $self->{_items}->[$input - 1]->active();
            if ($active) {
                print "\n";
                return $action->();
            }
        } elsif ($input =~ m/^\d+$/ && $input == 0 && $self->{_exit}) {
            exit 0;
        }

        # Deal with invalid input
        print "\nInvalid input.\n\n";
        system 'pause';
    }
}

1;

Item.pm模块必须存储在一个名为菜单"的子文件夹中,以便正确引用它.该模块使您可以创建包含名称和子例程引用的Menu :: Item对象.这些对象将是用户从菜单中选择的.

The Item.pm Module must be stored in a subfolder called "Menu" In order for it to be referenced properly. This module lets you create Menu::Item objects that contain a name and a subroutine reference. These objects will be what the user selects from in the menu.

# Item.pm

#!/usr/bin/perl

package Menu::Item;

# Always use these
use strict;
use warnings;

# Menu::Item constructor
sub new {

    # Unpack input arguments
    my ($class, $name, $action) = @_;

    # Bless the Menu::Item object
    my $self = bless {
        _name   => $name,
        _action => $action,
        _active => 1,
    }, $class;

    return $self;
}

# Name accessor method
sub name {
    my ($self, $name) = @_;
    $self->{_name} = $name if defined $name;
    return $self->{_name};
}

# Action accessor method
sub action {
    my ($self, $action) = @_;
    $self->{_action} = $action if defined $action;
    return $self->{_action};
}

# Active accessor method
sub active {
    my ($self, $active) = @_;
    $self->{_active} = $active if defined $active;
    return $self->{_active};
}

1;

此设计是对我以前设计的巨大改进,使菜单的创建变得更加轻松和简洁.

This design is a vast improvement over my previous design and makes creating menus much easier and cleaner.

让我知道您的想法.

有任何评论,想法或改进想法吗?

Any comments, thoughts, or improvement ideas?

这篇关于如何在Perl中构建一个简单的菜单?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆