Windows Forms - простой конечный автомат - PullRequest
2 голосов
/ 25 ноября 2011

У меня есть работающий тестовый конечный автомат в консольном приложении - 3 состояния и 5 событий.

Проблема: как работать в Windows Forms, т.е. у меня есть основной цикл, который выполняется все времяглядя на состояние ... и если да, то где ... если я использую события, например btnPress.

Цель состоит в том, чтобы приложение могло находиться в нескольких различных состояниях / экранах, и оно должно быть твердым, поэтому использованиеконечный автомат для принудительного исполнения, где мы находимся, и что нет крайних случаев необработанных.

Код рабочей консоли:

namespace StateMachineTest {
class Program {
    static void Main(string[] args) {
        var fsm = new FiniteStateMachine();
        while (true) {
            if (fsm.State == FiniteStateMachine.States.EnterVoucherCode) {
                Console.WriteLine("State: " + fsm.State);
                Console.WriteLine("Enter Voucher Code:");
                string voucherCode = Console.ReadLine();
                Console.WriteLine("voucher is " + voucherCode);
                Console.WriteLine();
                fsm.ProcessEvent(FiniteStateMachine.Events.PressNext);
            }

            if (fsm.State == FiniteStateMachine.States.EnterTotalSale) {
                Console.WriteLine("State: " + fsm.State);
                Console.WriteLine("Enter Total Sale or x to simulate back");
                string voucherSaleAmount = Console.ReadLine();
                if (voucherSaleAmount == "x")
                    fsm.ProcessEvent(FiniteStateMachine.Events.PressBackToVoucherCode);
                else {
                    Console.WriteLine("total sale is " + voucherSaleAmount);
                    Console.WriteLine();
                    fsm.ProcessEvent(FiniteStateMachine.Events.PressRedeem);
                }
            }

            if (fsm.State == FiniteStateMachine.States.ProcessVoucher) {
                Console.WriteLine("State: " + fsm.State);
                Console.WriteLine("Press 1 to fake a successful redeem:");
                Console.WriteLine("Press 2 to fake a fail redeem:");
                Console.WriteLine("Press 3 to do something stupid - press the Next Button which isn't allowed from this screen");
                Console.WriteLine();
                string result = Console.ReadLine();

                //EnterVoucherCode state
                if (result == "1")
                    fsm.ProcessEvent(FiniteStateMachine.Events.ProcessSuccess);
                if (result == "2")
                    fsm.ProcessEvent(FiniteStateMachine.Events.ProcessFail);
                if (result == "3")
                    fsm.ProcessEvent(FiniteStateMachine.Events.PressNext);
            }

            //how to handle async calls?
            //how to handle many many states.. matrix could get unwieldy
        }
    }
}

class FiniteStateMachine {
    //first state is the default for the system
    public enum States { EnterVoucherCode, EnterTotalSale, ProcessVoucher };
    public enum Events { PressNext, PressRedeem, ProcessSuccess, ProcessFail, PressBackToVoucherCode };
    public delegate void ActionThing();

    public States State { get; set; }

    private ActionThing[,] fsm;

    public FiniteStateMachine() {
        //array of action delegates
        fsm = new ActionThing[3, 5] { 
        //PressNext,     PressRedeem,            ProcessSuccess,      ProcessFail,      PressBackToVoucherCode
        {PressNext,      null,                   null,                null,             null},                          //EnterVoucherCode.... can pressnext
        {null,           PressRedeem,            null,                null,             PressBackToVoucherCode},        //EnterTotalSale... can pressRedeem or pressBackToVoucherCode
        {null,           null,                   ProcessSuccess,      ProcessFail,      null} };                        //moving from ProcessVoucher... can be a processSuccess or ProcessFail.. can't go back to redeem
    }
    public void ProcessEvent(Events theEvent) {
        try {
            var row = (int)State;
            var column = (int)theEvent;
            //call appropriate method via matrix.  So only way to change state is via matrix which defines what can and can't happen.
            fsm[row, column].Invoke();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.Message); //possibly catch here to go to an error state? or if do nothing like here, then it will continue on in same state
        }
    }

    private void PressNext() { State = States.EnterTotalSale; }
    private void PressRedeem() { State = States.ProcessVoucher; }
    private void ProcessSuccess() { State = States.EnterVoucherCode; }
    private void ProcessFail() { State = States.EnterVoucherCode; }
    private void PressBackToVoucherCode() { State = States.EnterVoucherCode; }
}

}

Не работает код WinForms:

    //goal is to get a fsm demo working with 3 states and 5 events.
//need number buttons, redeem and back to work.
public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    private void MainForm_Load(object sender, EventArgs e) {
        SystemSettings.ScreenOrientation = ScreenOrientation.Angle90;

        var fsm = new FiniteStateMachine();
        while (true)
        {
            if (fsm.State == FiniteStateMachine.States.EnterVoucherCode)
            {
                //Console.WriteLine("State: " + fsm.State);

                //if next/redeem button is pressed
                //fsm.ProcessEvent(FiniteStateMachine.Events.PressNext);
            }

            if (fsm.State == FiniteStateMachine.States.EnterTotalSale)
            {
                Console.WriteLine("State: " + fsm.State);
                Console.WriteLine("Enter Total Sale or x to simulate back");
                string voucherSaleAmount = Console.ReadLine();
                if (voucherSaleAmount == "x")
                    fsm.ProcessEvent(FiniteStateMachine.Events.PressBackToVoucherCode);
                else
                {
                    Console.WriteLine("total sale is " + voucherSaleAmount);
                    Console.WriteLine();
                    fsm.ProcessEvent(FiniteStateMachine.Events.PressRedeem);
                }
            }

            if (fsm.State == FiniteStateMachine.States.ProcessVoucher)
            {
                Console.WriteLine("State: " + fsm.State);
                Console.WriteLine("Press 1 to fake a successful redeem:");
                Console.WriteLine("Press 2 to fake a fail redeem:");
                Console.WriteLine("Press 3 to do something stupid - press the Next Button which isn't allowed from this screen");
                Console.WriteLine();
                string result = Console.ReadLine();

                //EnterVoucherCode state
                if (result == "1")
                    fsm.ProcessEvent(FiniteStateMachine.Events.ProcessSuccess);
                if (result == "2")
                    fsm.ProcessEvent(FiniteStateMachine.Events.ProcessFail);
                if (result == "3")
                    fsm.ProcessEvent(FiniteStateMachine.Events.PressNext);
            }
        }
    }

    private void btn_0_MouseUp(object sender, MouseEventArgs e)
    {
            txtCode.Text += '0';
    }

    private void btn_1_MouseUp(object sender, MouseEventArgs e)
    {
            txtCode.Text += '1';
    }

    private void btn_2_MouseUp(object sender, MouseEventArgs e)
    {
            txtCode.Text += '2';
    }

    private void btn_del_MouseUp(object sender, MouseEventArgs e)
    {
            txtCode.Text = txtCode.Text.Substring(0, txtCode.Text.Length - 1);
    }

    private void btn_redeem_MouseUp(object sender, MouseEventArgs e)
    {
            txtCode.Visible = false;
            txtStatus.Visible = true;
            txtStatus.Text = "PROCESSING PLEASE WAIT";
    }

enter image description here

Код из: Простой пример конечного автомата в C #?

Ответы [ 2 ]

3 голосов
/ 25 ноября 2011

Нет необходимости иметь цикл событий опроса, который постоянно проверяет состояние, это то, что WinForm делает автоматически.Ваши элементы пользовательского интерфейса должны подключать обработчики событий, и эти обработчики событий должны отвечать за проверку / переключение состояния.

Это очень грязная реализация.Если вы примените шаблон State ( Глава 9 шаблонов Head First Design имеет действительно чистый пример), вы сможете использовать свою форму в качестве клиента, который содержит другой объект, соответствующийКонтекст, который вызывается обработчиками событий ваших элементов пользовательского интерфейса.

1 голос
/ 25 ноября 2011

Ваш код пахнет во многих отношениях.Прежде всего, winforms работает с использованием одного потока, поэтому с помощью вашего цикла вы блокируете поток и, следовательно, форму.Во-вторых, вы работаете с консольной логикой в ​​своем приложении winforms ... вы даже тестировали это?В-третьих, вы никогда не устанавливаете состояние машины в другое состояние.Вы собираетесь заставить кнопки устанавливать следующее состояние?

Конечный автомат должен зациклить что-то вроде этого.

public class StateManager
{
    public void Transition(IState state)
    {
        state.Transition(CurrentState, StateManager);
    }

    public IState CurrentState { get; private set; }

    public event EventHandler StateSwitched;
}

public class FirstState : IState
{
    private Form _form;

    public FirstState(Form form)
    {
        _form = form;
    }

    public void Transition(IState oldState, StateManager stateManager)
    {
        _form.Closing += (sender, e) =>
        {
            stateManager.Transition(new SecondState(_form));
        };
    }
}

public class SecondState : IState
{
...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...