2011-05-09 90 views
6

Tengo un formulario de usuario que contiene muchos cuadros de texto. Cuando cambian los valores de estos cuadros de texto, necesito volver a calcular los valores de mi resultado final en función de los valores del cuadro de texto llamando a una subrutina AutoCalc().Formulario de usuario de Excel VBA - Ejecutar Sub cuando algo cambia

que tienen alrededor de 25 cajas y no quiero añadir un evento de cambio() de forma individual a cada cuadro de texto llamando a dicha subrutina. ¿Cuál es la forma más rápida y eficiente de llamar a AutoCalc() cada vez que cambia algún valor?

Respuesta

14

Esto se puede lograr mediante el uso de un módulo de clase . En el ejemplo que sigue, asumiré que ya tiene una forma de usuario con algunos cuadros de texto.

En primer lugar, crear un módulo de clase en su proyecto de VBA (por no llamarlo clsTextBox - asegúrese de cambiar la propiedad 'Nombre' del módulo de clase)

Private WithEvents MyTextBox As MSForms.TextBox 

Public Property Set Control(tb As MSForms.TextBox) 
    Set MyTextBox = tb 
End Property 

Private Sub MyTextBox_Change() 
    AutoCalc() //call your AutoCalc sub/function whenever textbox changes 
End Sub 

Ahora, en el formulario de usuario, añadir el código folowing:

Dim tbCollection As Collection 

Private Sub UserForm_Initialize() 
    Dim ctrl As MSForms.Control 
    Dim obj As clsTextBox 

    Set tbCollection = New Collection 
     For Each ctrl In Me.Controls 
      If TypeOf ctrl Is MSForms.TextBox Then 
       Set obj = New clsTextBox 
       Set obj.Control = ctrl 
       tbCollection.Add obj 
      End If 
     Next ctrl 
    Set obj = Nothing 

End Sub 
+1

Hola, ¿cuál es la necesidad de crear tbCollection? ¿Qué hace? – Ashok

+1

La colección se usa para almacenar y conservar los objetos de cuadro de texto creados. –

5

Tome un vistazo a this de cómo crear una clase que responde a un cambio en cualquier cuadro de texto. El ejemplo es para botones, pero se puede modificar. Sin embargo, tenga en cuenta que los controles de Textbox no tienen un evento Exit (ese evento es en realidad parte de la forma de usuario) por lo que realmente tendrá que usar el evento Change.

+0

¡Gracias! Me guía en la dirección correcta ... – Ashok

6

el uso de clases, como la respuesta anterior sugiere, es una buena estrategia para hacer frente a muchos controles de una manera concisa y elegante, sin embargo:

1) No veo problemas para crear 25 eventos con 1 línea, llamando a una rutina privada de usuario común, a menos que la cantidad de controles sea dinámica. Es una filosofía KISS.

2) En general, considero que el evento Change es muy perturbador porque hace todos los cálculos que ingresó cada dígito. Es más sensato y moderado hacer esto utilizando la salida evento o antes de la actualización caso, porque hace que el nuevo cálculo solamente al decidir sobre un valor. Por ejemplo, el Google Instant me molesta al intentar devolver respuestas, consumiendo recursos, sin que el usuario haya definido la pregunta.

3) Hubo un problema de validación. Acepto que puede evitar las claves incorrectas con el evento Modificar; sin embargo, si necesita validar los datos, no puede saber si el usuario continuará escribiendo o si los datos están listos para su validación.

4) Usted debe recordar que Cambio o salida eventos no obliga al usuario a pasar en los campos de texto, por lo que necesita el sistema de volver a validar y volver a calcular cuando se trata de salir del formulario sin cancelar.

El siguiente código es simple pero eficaz para las formas estáticas.

Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean) 
Call AutoCalc(Cancel) 
End Sub 

Private Sub TextBox2_Exit(ByVal Cancel As MSForms.ReturnBoolean) 
Call AutoCalc(Cancel) 
End Sub 
..... 
Private Sub TextBox25_Exit(ByVal Cancel As MSForms.ReturnBoolean) 
Call AutoCalc(Cancel) 
End Sub 

Private Function Valid 
..... 
End Function 

Private Sub AutoCalc(Canc As Variant) 
If Not Valid() Then Canc=True 
' Calculation 
End Sub 

Es que son adictos a ahorrar tiempo, puede crear una rutina genérica VBA con el fin de generar el código para los eventos relacionados con los controles en una forma que encaja una máscara. Este código puede estar en una hoja de borrador (es más seguro generar código directamente, que tiene errores en algunas versiones de Excel) y copiar y pegar en un módulo de formulario.

Sub GenerateEvent(Form As String, Mask As String, _ 
    Evento As String, Code As String) 
' Form - Form name in active workbook 
' Mark - String piece inside control name 
' Evento - Event name to form procedure name 
' Code - Code line inside event 
Dim F As Object 
Dim I As Integer 
Dim L As Long 
Dim R As Range 
Dim Off As Long 
Set F = ThisWorkbook.VBProject.VBComponents(Form) 
Set R = ActiveCell ' Destination code 
Off = 0 
For I = 0 To F.Designer.Controls.Count - 1 
    If F.Designer.Controls(I).Name Like "*" & Mask & "*" Then 
     R.Offset(Off, 0) = "Private Sub " & _ 
      F.Designer.Controls(I).Name & "_" & Evento & "()" 
     R.Offset(Off + 1, 0) = "  " & Code 
     R.Offset(Off + 2, 0) = "End Sub" 
     Off = Off + 4 
    End If 
Next I 
End Sub 

Sub Test() 
Call GenerateEvent("FServCons", "tDt", "Exit", _ 
    "Call AtuaCalc(Cancel)") 
End Sub 
0

Sin embargo, tenga en cuenta que los controles de cuadro de texto no tienen un evento de salida (ese evento es en realidad parte del formulario de usuario) para que realmente tendrán que utilizar el evento Change.

I'm confused. Quizás esto se agregó en 2007, o quizás no entiendo los matices. Uso el evento Exit en los controles de TextBox. Cuando pierdo el control de Tab, o hago clic con el mouse en otro control, desencadena el evento Exit.

0

Tuve un problema similar en el que quiero validar aproximadamente 48 cuadros de texto diferentes usando una rutina común y el enfoque del módulo de clase parecía interesante (muchas menos líneas de código duplicadas). Pero no quería validar cada carácter ingresado, solo quería verificar después de la actualización. Y si los datos ingresados ​​no eran válidos, quería borrar el cuadro de texto y permanecer en el mismo cuadro de texto que requiere el uso de Cancelar = Verdadero en la rutina de Salida. Después de varias horas de probar esto y no tener mis controladores de eventos AfterUpdate y Exit nunca se activan, descubrí por qué.

Si crea una clase como la siguiente:

Private WithEvents MyTextBox As MSForms.TextBox 

Public Property Set** Control(tb As MSForms.TextBox) 

    Set MyTextBox = tb 

End Property 

y después de entrar en el explorador de objetos VBE y seleccionar MyTextBox, podrás ver los eventos enumerados soportados no incluyen AfterUpdate o Salir. Estos eventos están disponibles si ingresa al UserForm y utiliza el navegador de objetos VBE y observa una instancia de un TextBox, pero parecen heredarse de los controles de los que forma parte TextBox. Definir una nueva clase utilizando MSForms.TextBox no incluye esos eventos. Si intenta definir esos controladores de eventos manualmente, compilarán y parecerá que funcionarán (pero no es así). En lugar de convertirse en controladores de eventos del objeto de la clase, serán subrutas privadas que aparecerán en (General) en el navegador de objetos VBE y nunca se ejecutarán. Parece que la única forma de crear un controlador de eventos válido es seleccionar el objeto de clase en el navegador de objetos VBE y luego seleccionar el evento deseado de la lista de eventos enumerados.

Después de muchas horas de búsqueda no he podido encontrar ninguna referencia para mostrar cómo se puede construir un modelo de herencia similar dentro de una clase privada para que AfterUpdate y Exit aparezcan como eventos disponibles para las clases creadas. Por lo tanto, la recomendación (arriba) de tener un controlador de eventos separado para cada TextBox en un UserForm, puede ser el único enfoque que funcionará si desea utilizar AfterUpdate y/o Exit.

0

Así que las primeras 9 líneas donde me las dio en un foro no puedo recordar dónde. Pero construí sobre eso y ahora me gustaría utilizar un botón de comando para volver a calcular si el uso cambia una variable enumerada en este sub.

<pre><code>'Private Sub txtWorked_Exit(ByVal Cancel As  MSForms.ReturnBoolean) 
11 Dim OTRate  As Double 
    OTRate = Me.txtHourlyRate * 1.5 
If Me.txtWorked > 40 Then 
    Me.txtBasePay.Value = Format(Me.txtHourlyRate.Value * 40, "$#,##0.00") 
    Me.txtOvertime = Format((Me.txtWorked - 40) * OTRate, "$#,##0.00") 
Else 
    Me.txtOvertime.Value = "0" 
    Me.txtBasePay.Value = Format(Me.txtHourlyRate.Value * Me.txtWorked.Value, "$#,##0.00") 
End If 
Dim Gross, W2, MASSTax, FICA, Medi, Total, Depends, Feds As Double 
    Gross = CDbl(txtBonus.Value) + CDbl(txtBasePay.Value) + CDbl(txtOvertime.Value) 
    W2 = txtClaim * 19 
    Me.txtGrossPay.Value = Format(Gross, "$#,##0.00") 
    FICA = Gross * 0.062 
    Me.txtFICA.Value = Format(FICA, "$#,##0.00") 
    Medi = Gross * 0.0145 
    Me.txtMedicare.Value = Format(Medi, "$#,##0.00") 
    MASSTax = (Gross - (FICA + Medi) - (W2 + 66)) * 0.0545 
If chkMassTax = True Then 
    Me.txtMATax.Value = Format(MASSTax, "$#,##0.00") 
Else: Me.txtMATax.Value = "0.00" 
End If 
If Me.txtClaim.Value = 1 Then 
    Depends = 76.8 

ElseIf Me.txtClaim.Value = 2 Then 
    Depends = 153.8 

ElseIf Me.txtClaim.Value = 3 Then 
    Depends = 230.7 
Else 
    Depends = 0 
End If 
    If (Gross - Depends) < 765 Then 
    Feds = ((((Gross - Depends) - 222) * 0.15) + 17.8) 
    Me.txtFedIncome.Value = Format(Feds, "$#,##.00") 
ElseIf (Gross - Depends) > 764 Then 
    Feds = ((((Gross - Depends) - 764) * 0.25) + 99.1) 
    Me.txtFedIncome.Value = Format(Feds, "$#,##.00") 
Else: 
    Feds = 0 
End If 
    Total = (txtMATax) + (FICA) + (Medi) + (txtAdditional) + (Feds) 
    Me.txtTotal.Value = Format(Total, "$#,##0.00") 
    Me.txtNetPay.Value = Format(Gross - Total, "$#,##0.00") 

End Sub 
Private Sub cmdReCalculate_Click() 

End Sub'</pre></code> 
Cuestiones relacionadas