Delegar responsabilidades a los controles de WinForm: ¿deberían los controles estar al tanto de las acciones de los demás?

Tengo un puñado de controles en un formulario:

  • Una casilla de verificación que es responsable de habilitar/deshabilitar todos los demás controles en la página en función de su estado marcado.
  • Unos pocos botones de radio que son responsables de habilitar/deshabilitar controles específicos en la página en función de sus estados marcados.
  • Otros controles que son manipulados por los controles anteriores.

Se plantean algunos escenarios:

  • Cuando el formulario se inicializa, cargo el estado de la casilla de verificación. Luego, habilita o deshabilita el resto de controles del formulario.
  • A medida que el formulario continúa inicializándose, cargo el estado de los botones de opción. Esto tiene la posibilidad de deshacer el requisito anterior si un botón de radio está marcado pero deshabilitado. Como tal, verifico para asegurarme de que el botón de opción esté habilitado primero.
  • Una vez que se ha cargado el formulario, el usuario puede marcar o desmarcar los botones de opción. Este es un caso trivial, solo ejecuto el código que cumplió con el último requisito. Otro caso, sin embargo, es que el usuario puede marcar o desmarcar la casilla de verificación. Cuando la casilla de verificación se habilita, quiere volver a habilitar todos los controles en la página porque los deshabilitó. Sin embargo, hacerlo romperá los requisitos del botón de opción.

Este escenario es bastante trivial de manejar con fuerza bruta. He creado un par de métodos para resaltar:

private void ChkBxSnmPv3OnCheckedChanged(object sender, EventArgs eventArgs)
{
    snmpSettingsErrorProvider.Clear();

    foreach (Control control in grpBxSNMPv3.Controls)
    {
        if (control != sender)
            control.Enabled = ((CheckBox)sender).Checked;
    }
}

private void rdBtnAuthNoPriv_CheckedChanged(object sender, EventArgs e)
{
    RadioButton authNoPrivRadioButton = ((RadioButton)sender);

    if (authNoPrivRadioButton.Enabled)
    {
        bool isChecked = authNoPrivRadioButton.Checked;

        SetControlState(cmbBxAuthProtocol, isChecked);
        SetControlState(mskdTxtBxAuthPassword, isChecked);
        SetControlState(mskdTxtBxAuthPasswordConfirm, isChecked);

        SetControlState(cmbBxPrivacyProtocol, !isChecked);
        SetControlState(mskdTxtBxPrivacyPassword, !isChecked);
        SetControlState(mskdTxtBxPrivacyPasswordConfirm, !isChecked);
    }
}
//More methods for other checkedChange and also for when rdBtn's enable.

Idea aproximada del diseño:

enter image description here

Con todo lo dicho, mi pregunta es 'simple':

  • Los métodos deberían funcionar sin la suposición de que existen otros métodos. Sin embargo, si mantengo la lógica de rdBtn al tanto de la existencia de chkBx, entonces tendré un código que tiene que luchar entre sí.

Podría escribir mi código así:

private void ChkBxSnmPv3OnCheckedChanged(object sender, EventArgs eventArgs)
{
    snmpSettingsErrorProvider.Clear();

    txtBxEngineID = ((CheckBox)sender).Checked;
    rdBtnAuthNoPriv = ((CheckBox)sender).Checked;
    rdBtnAuthPriv = ((CheckBox)sender).Checked;
    rdBtnNoAuthNoPriv = ((CheckBox)sender).Checked;

    //Pass work for enabling Auth and Priv fields to rdBtn events.
}

Esta solución es más eficiente y garantiza que no veré ningún parpadeo. Sin embargo, también significa que para una 'finalización exitosa' de habilitar todos los controles en la página, mi chkBx ahora tiene que confiar en la lógica de rdBtn. ¿Es esta una buena práctica de programación?

preguntado el 21 de mayo de 12 a las 18:05

La pregunta no tiene mucho sentido, estos ya son fragmentos de código que existen en el formulario. O UserControl, no puedo decirlo. No código que está dentro de la clase de control. De eso se tratan los eventos, dejar algún tipo de otros el código sabe que sucedió algo interesante. Este código pertenece en gran medida a la forma, la única clase que sabe algo sobre la colección de controles que alberga. -

Oye lo siento. Estoy de acuerdo: mi publicación inicial no tiene suficiente sentido. Lo editaré hoy cuando tenga un poco de tiempo para que sea más comprensible para fines heredados. Sin embargo, estoy confundido por tu segundo punto. Entonces, ¿los eventos nunca deberían usarse si una sola clase está involucrada? ¿Tienes lectura para mí para respaldar eso? El código está en un control de usuario que se carga en un formulario. -

2 Respuestas

Creo que este es un código sensato para mantener el formulario, sin embargo, haría algunas sugerencias;

1) La transmisión tiene un costo de procesamiento menor, por lo que debe evitar la transmisión dentro de un bucle. De hecho, como concepto general, debe evitar realizar cualquier acción repetida dentro de un bucle cuando se garantiza que el resultado seguirá siendo el mismo. Entonces podrías mejorar tu primer método así;

private void ChkBxSnmPv3OnCheckedChanged(object sender, EventArgs eventArgs)
{
    snmpSettingsErrorProvider.Clear();

    // cast the sender once only
    CheckBox cb = sender as CheckBox;
    if (null == cb) return;

    foreach (Control control in grpBxSNMPv3.Controls)
    {
        if (control != sender)
            control.Enabled = cb.Checked;
    }
}

2) Sugeriría mover la lógica de habilitación/deshabilitación a un método separado y luego llamarlo desde sus controladores de eventos de control. Esto le permitirá reutilizar la misma lógica si así lo decide, desde algún otro control. Al acoplar estrechamente los comportamientos para controlar los eventos, encuentro que conduce a un código duplicado. Al igual que;

private void ChkBxSnmPv3OnCheckedChanged(object sender, EventArgs eventArgs)
{
    snmpSettingsErrorProvider.Clear();

    // cast the sender once only
    CheckBox cb = sender as CheckBox;
    if (null == cb) return;

    SetEnabled(grpBxSNMPv3, cb.Checked, new[] { cb });
}

private void SetEnabled(Control parent, bool isEnabled, Control[] exludeControls)
{
    if (null == parent) return;

    foreach (Control control in parent.Controls)
    {
        if (!excludeControls.Contains(control))
            control.Enabled = isEnabled;
    }
}

Ahora tiene un método reutilizable para habilitar/deshabilitar todos los controles contenidos por otro.

3) Con respecto a su pregunta final, sí, creo que este enfoque está bien. Menos acoplamiento siempre es algo bueno. Piense en cómo diseñar sus métodos para que sean más reutilizables y creo que encontrará una solución limpia.

contestado el 21 de mayo de 12 a las 23:05

+1 por las sugerencias. De hecho, terminé siguiendo todas estas sugerencias antes de actualizar mi hilo, por lo que las grandes mentes piensan igual. Seguí adelante y publiqué una solución limpia para futuras personas. Avísame si ves algo malo al respecto. :) - Sean Anderson

Esto es con lo que terminé yendo. Estoy bastante bien con eso, aparte de las dos listas que estoy inicializando. Probablemente deberían estar en sus propios controles, pero todavía no me atrevo a hacerlo.

public partial class DeviceSnmpSettings : UserControl, INotifyPropertyChanged
{
    private readonly List<Control> AuthenticationControls = new List<Control>(6);
    private readonly List<Control> PrivacyControls = new List<Control>(6);
    public event PropertyChangedEventHandler PropertyChanged;

    public DeviceSnmpSettings()
    {
        InitializeComponent();
        InitializeAuthControls();
        InitializePrivacyControls();
    }

    public DeviceSnmpSettings(Point location)
        : this()
    {
        Location = location;
    }

    //TODO: Move out into sub-user control?
    private void InitializeAuthControls()
    {
        AuthenticationControls.Add(lblAuthPassword);
        AuthenticationControls.Add(mskdTxtBxAuthPassword);
        AuthenticationControls.Add(lblAuthProtocol);
        AuthenticationControls.Add(cmbBxAuthProtocol);
        AuthenticationControls.Add(lblAuthPasswordConfirm);
        AuthenticationControls.Add(mskdTxtBxAuthPasswordConfirm);
    }
    //TODO: Move out into sub-user control?
    private void InitializePrivacyControls()
    {
        PrivacyControls.Add(lblPrivacyPassword);
        PrivacyControls.Add(mskdTxtBxPrivacyPassword);
        PrivacyControls.Add(lblPrivacyProtocol);
        PrivacyControls.Add(cmbBxPrivacyProtocol);
        PrivacyControls.Add(lblPrivacyPasswordConfirm);
        PrivacyControls.Add(mskdTxtBxPrivacyPasswordConfirm);
    }

    private bool SNMPv3Enabled
    {
        get { return chkBxSNMPv3.Checked; }
        set { chkBxSNMPv3.Checked = value; }
    }

    private SNMPV3Mode SecurityMode
    {
        get
        {
            SNMPV3Mode mode = SNMPV3Mode.NoAuthNoPriv;

            if (rdBtnAuthNoPriv.Checked)
                mode = SNMPV3Mode.AuthNoPriv;
            else if(rdBtnAuthPriv.Checked)
                mode = SNMPV3Mode.AuthPriv;

            return mode;
        }
        set
        {
            switch (value)
            {
                case SNMPV3Mode.NoAuthNoPriv:
                    rdBtnNoAuthNoPriv.Checked = true;
                    break;
                case SNMPV3Mode.AuthNoPriv:
                    rdBtnAuthNoPriv.Checked = true;
                    break;
                default:
                    rdBtnAuthPriv.Checked = true;
                    break;
            }

            OnSecurityModeChanged();
        }
    }

    protected virtual void OnSecurityModeChanged()
    {
        AuthenticationControls.ForEach(control => SetControlEnabledState(control, AuthenticationEnabled));
        PrivacyControls.ForEach(control => SetControlEnabledState(control, PrivacyEnabled));
        NotifyPropertyChanged("SecurityMode");
    }

    private void NotifyPropertyChanged(string property)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(property));
    }

    private bool AuthenticationEnabled
    {
        get
        {
            return SNMPv3Enabled && (SecurityMode == SNMPV3Mode.AuthPriv || SecurityMode == SNMPV3Mode.AuthNoPriv);
        }
    }

    private bool PrivacyEnabled
    {
        get { return SNMPv3Enabled && SecurityMode == SNMPV3Mode.AuthPriv; }
    }

    private void ChkBxSnmPv3OnCheckedChanged(object sender, EventArgs eventArgs)
    {
        SetControlEnabledStates();
    }

    private void SetControlEnabledStates()
    {
        snmpSettingsErrorProvider.Clear();

        foreach (Control control in grpBxSNMPv3.Controls)
        {
            //Check each of the lists for the control to prevent flickering.
            if (control != chkBxSNMPv3 && !AuthenticationControls.Contains(control) && !PrivacyControls.Contains(control))
                control.Enabled = SNMPv3Enabled;
        }

        //Need to validate that our radio button's checked state is reflected properly.
        AuthenticationControls.ForEach(control => SetControlEnabledState(control, AuthenticationEnabled));
        PrivacyControls.ForEach(control => SetControlEnabledState(control, PrivacyEnabled));
    }

    public void LoadFields(NetworkDiscovery networkDiscovery)
    {
        SNMPv3Enabled = networkDiscovery.Snmpv3Enabled;
        SecurityMode = networkDiscovery.SecurityMode;
        txtBxSNMPv3Username.Text = networkDiscovery.Username;
        mskdTxtBxAuthPassword.Text = networkDiscovery.AuthPassword;
        mskdTxtBxAuthPasswordConfirm.Text = networkDiscovery.AuthPassword;
        cmbBxAuthProtocol.SelectedItem = networkDiscovery.AuthProtocol.ToString();
        mskdTxtBxPrivacyPassword.Text = networkDiscovery.PrivacyPassword;
        mskdTxtBxPrivacyPasswordConfirm.Text = networkDiscovery.PrivacyPassword;
        cmbBxPrivacyProtocol.SelectedItem = networkDiscovery.PrivacyProtocol.ToString();

        SetControlEnabledStates();
    }

    private void SetControlEnabledState(Control control, bool enabled)
    {
        control.Enabled = enabled;
                    //Clear errors set on errorProvider when control is disabled.
        if (!control.Enabled)
            snmpSettingsErrorProvider.SetError(control, string.Empty);
    }

    private void rdBtnNoAuthNoPriv_CheckedChanged(object sender, EventArgs e)
    {
        if (((RadioButton)sender).Checked)
            SecurityMode = SNMPV3Mode.NoAuthNoPriv;
    }

    private void rdBtnAuthNoPriv_CheckedChanged(object sender, EventArgs e)
    {
        if (((RadioButton)sender).Checked)
            SecurityMode = SNMPV3Mode.AuthNoPriv;
    }

    private void rdBtnAuthPriv_CheckedChanged(object sender, EventArgs e)
    {
        if (((RadioButton)sender).Checked)
            SecurityMode = SNMPV3Mode.AuthPriv;
    }
}

contestado el 21 de mayo de 12 a las 23:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.