2011-10-17 14 views
5

Soy más nuevo que C# y acabo de descubrir cómo usar yield return para crear una enumeración IEnumerable personalizada. Intento utilizar MVVM para crear un asistente, pero me costaba imaginar cómo controlar el flujo de una página a la siguiente. En algunos casos, es posible que desee que aparezca un cierto paso; en otros, no se aplica.Navegación del asistente con IEnumerable/yield return

De todos modos, mi problema es que estoy usando un IEnumerable para devolver cada página subsiguiente, que funciona realmente bien, pero sé que probablemente estoy haciendo algo impropio/involuntario con el idioma. La clase niño sólo tiene que anular los Pasos abstractas de acceso IEnumerable:

public class HPLDTWizardViewModel : WizardBase 
{ 
    protected override IEnumerable<WizardStep> Steps 
    { 
    get 
    { 
     WizardStep currentStep; 

     // 1.a start with assay selection 
     currentStep = new AssaySelectionViewModel(); 
     yield return currentStep; 
     // 1.b return the selected assay. 
     SigaDataSet.Assay assay = ((AssaySelectionViewModel)currentStep).SelectedAssay; 
     sigaDataSet = (SigaDataSet)assay.Table.DataSet; 

     // 2.a get the number of plates 
     currentStep = new NumPlatesViewModel(sigaDataSet); 
     yield return currentStep; 
     ... 
    } 
    } 
} 

la clase padre contiene la lógica de navegación utilizando los pasos atributos empadronador:

public abstract class WizardBase : ViewModelBase 
{ 
    private ICommand _moveNextCommand; 
    private ICommand _cancelCommand; 
    private IEnumerator<WizardStep> _currentStepEnumerator; 

    #region Events 

    /// <summary> 
    /// Raised when the wizard window should be closed. 
    /// </summary> 
    public event EventHandler RequestClose; 

    #endregion // Events 

    #region Public Properties 

    /// <summary> 
    /// Gets the steps. 
    /// </summary> 
    /// <value>The steps.</value> 
    protected abstract IEnumerable<WizardStep> Steps { get;} 

    /// <summary> 
    /// Gets the current step. 
    /// </summary> 
    /// <value>The current step.</value> 
    public WizardStep CurrentStep 
    { 
    get 
    { 
     if (_currentStepEnumerator == null) 
     { 
     _currentStepEnumerator = Steps.GetEnumerator(); 
     _currentStepEnumerator.MoveNext(); 
     } 

     return _currentStepEnumerator.Current; 
    } 
    } 

    #endregion //Public Properties 

    #region Commands 

    public ICommand MoveNextCommand 
    { 
    get 
    { 
     if (_moveNextCommand == null) 
     _moveNextCommand = new RelayCommand(
      () => this.MoveToNextPage(), 
      () => this.CanMoveToNextPage()); 

     return _moveNextCommand; 
    } 
    } 

    public ICommand CancelCommand 
    { 
    get 
    { 
     if (_cancelCommand == null) 
     _cancelCommand = new RelayCommand(() => OnRequestClose()); 

     return _cancelCommand; 
    } 
    } 

    #endregion //Commands 

    #region Private Helpers 

    /// <summary> 
    /// Determines whether this instance [can move to next page]. 
    /// </summary> 
    /// <returns> 
    /// <c>true</c> if this instance [can move to next page]; otherwise, <c>false</c>. 
    /// </returns> 
    bool CanMoveToNextPage() 
    { 
    if (CurrentStep == null) 
     return false; 
    else 
     return CurrentStep.IsValid(); 
    } 

    /// <summary> 
    /// Moves to next page. 
    /// </summary> 
    void MoveToNextPage() 
    { 
    _currentStepEnumerator.MoveNext(); 

    if (_currentStepEnumerator.Current == null) 
     OnRequestClose(); 
    else 
     OnPropertyChanged("CurrentStep"); 
    } 

    /// <summary> 
    /// Called when [request close]. 
    /// </summary> 
    void OnRequestClose() 
    { 
    EventHandler handler = this.RequestClose; 
    if (handler != null) 
     handler(this, EventArgs.Empty); 
    } 

    #endregion //Private Helpers 
} 

Y aquí es la clase abstracta WizardStep cual cada página del asistente implementa:

public abstract class WizardStep : ViewModelBase 
{ 
    public abstract string DisplayName { get; } 

    public abstract bool IsValid(); 

    public abstract List<string> GetValidationErrors(); 
} 

Como ya he dicho, esto funciona de maravilla porque puedo navegar por la lista con el enumerador. La lógica de navegación está en una clase principal abstracta y todo lo que el niño tiene que hacer es anular el atributo Pasos. Los WizardSteps en sí contienen lógica para que sepan cuándo son válidos y el usuario puede continuar. Estoy usando MVVM por lo que el siguiente botón está vinculado a las funciones CanMoveToNextPage() y MoveToNextPage() a través de un comando.

Supongo que mi pregunta es: ¿Qué tan malo es abusar del modelo de enumeración en este caso? ¿Hay una mejor manera? Realmente necesito definir el flujo de control de alguna manera, y simplemente encaja muy bien con la capacidad de rendimiento de retorno para que pueda tener la lógica de flujo volver al acceso de Pasos para obtener la página siguiente.

+1

Eche un vistazo a esta publicación del blog http://blogs.msdn.com/b/shawnhar/archive/2010/10/01/iterator-state-machines.aspx – asawyer

+1

¿Qué es '_currentStepEnumerator'? Bueno, me imagino que es un 'IEnumerator' pero ¿podría aclarar cómo se declara? ¿Es un miembro estático? Finalmente, no veo nada malo con su código: si simplifica la lógica de su aplicación, estoy bastante seguro de que podría usarla. Buena pregunta de todos modos :) –

+0

Actualizado con la clase completa de WizardBase. ¡Gracias por tus útiles comentarios! – millejos

Respuesta

1

Creo que siempre que algo cumpla con los requisitos, sea fácil de mantener y fácil de leer, entonces no puede ser tan malo.

Obviamente, como has adivinado, este no es el uso clásico de IEnumerable. Pero, los retornos de rendimiento casi siempre se utilizan (en mi experiencia, al menos) para situaciones poco convencionales como esta. Siempre que no necesite soporte "< Back", dejaría la solución tal como está.

En cuanto a las alternativas, he usado múltiples enfoques, ninguno es realmente para mi gusto. Los magos con trayectorias ramificadas siempre son un poco desordenados.

Para los asistentes pesados, una opción es una máquina de estado. Escriba uno o dos métodos que sepa cómo atravesar estados, y qué transiciones son válidas. Cree un UserControl para cada estado y exhíbalos a un TabControl a través de ListCollectionView.

Una buena solución liviana que utilicé una vez es apilar todas las páginas del asistente uno sobre el otro en una cuadrícula y alternar su visibilidad uniéndose a un estado representado por una enumeración. Usando un ValueConverter puedes incluso evitar números mágicos. El cambio de una página a otra simplemente se convierte en una cuestión de incrementar o disminuir la propiedad Estado.