Appeler une feuille secondaire à partir d'un formulaire utilisateur

4

J'essaie de créer une macro qui lira les dates des cellules de ma feuille de calcul et les comparera à une date (mois et année) saisie dans un Userform. Pour ce faire, j'ai le Userform appeler un sous Reporttrouvé dans Sheet1. Les entiers qui représentent le mois et l'année entrés par l'utilisateur sont transmis avec l'appel.

L'erreur se produit toujours sur la ligne Call Sheet1.Report(intMonth, intYear):, qui appelle Report. L'erreur se lit comme suit: Run-time error '1004': Application-defined or object-defined error.

Voici mon code abrégé, commençant par Userform:

Private Sub cmdOK_Click()

    'Transform month field into an integer (1-12)
    Dim intMonth As Integer
    Select Case cboMonth.Value
        Case Is = strJan 'January - 01
            intMonth = 1
        Case Is = strFeb 'February - 02
            intMonth = 2
        Case Is = strMar 'March - 03
            intMonth = 3
        'and so on...
    End Select

    'Read year field as an Integer
    Dim intYear As Integer
    intYear = txtYear.Value

    Call Sheet1.Report(intMonth, intYear)

End Sub

Ensuite, voici le code de Report. Il est encore incomplet car je n'ai pas réussi à passer l'appel. Comme je l' ai mentionné plus tôt, j'ai toujours frappé l'erreur sur la ligne d'appel: Call Sheet1.Report(intMonth, intYear).

Public Sub Report(myMonth As Integer, myYear As Integer)

    'Some incomplete code...
    'Like I said, the macro never gets past the call.

End Sub

Une idée de comment réparer ça? Toute aide est très appréciée. Merci!

Mykrus
la source
Où avez-vous défini cboMonth et txtYear ?
angelofdev
Votre exemple de code a fonctionné pour moi. Gardez à l'esprit que votre utilisation de "Sheet1" en tant qu'objet est le nom de l'objet de la feuille et non le nom de la feuille de calcul visible sur l'onglet de la feuille Excel réelle. Si vous souhaitez l'appeler par le nom de la feuille de travail, essayez ceci: ThisWorkbook.Worksheets("Sheet1").Report intMonth, intYear REMARQUE: vous n'avez pas besoin de l' Callinstruction si vous supprimez la parenthèse.
HackSlash
@angelofdev, cboMonth et txtYear font référence à des champs de Userform, respectivement à une liste déroulante pour le mois et à une zone de texte pour l'année. Ils ne sont pas définis dans le code, mais j'ai quelques vérifications en place pour m'assurer qu'ils sont correctement définis dans le formulaire Userform
mykrus
@HackSlash, j'ai essayé votre code avec le nom de la feuille de calcul et cela a fonctionné! Merci :)
mykrus

Réponses:

2

Maintenant que vous avez prouvé que votre code fonctionne avec ma suggestion:

ThisWorkbook.Worksheets("Sheet1").Report intMonth, intYear

Prenons les conseils de Mathieu. Cliquez sur l'objet Feuille dans la vue Projet de votre fenêtre VBE:

VBAProject> Objets Microsfot Excel> Feuille1 (Feuille1)

La première partie est le nom de l'objet de la feuille, la deuxième partie entre parenthèses est le nom de la feuille de calcul, comme dans l'onglet Excel. Affichez la vue de la fenêtre Propriétés à partir du menu déroulant "Affichage" du VBE ou en appuyant sur F4. La première chose dans la fenêtre des propriétés de la feuille de calcul doit être (nom) et c'est le nom de l'objet que vous appelez dans votre code. Changez-le en quelque chose de descriptif comme "Rapport". Ensuite, utilisez un nom descriptif pour votre macro tel que "Mise à jour".

Vous pouvez maintenant créer un nouveau rapport en appelant:

Report.Update intMonth, intYear

J'utilise cette convention car je suppose que votre macro met à jour la feuille de rapport. Vous pouvez également suivre son avis sur le style de code "modèle-vue-présentateur", mais cela sort du cadre de votre question.

HackSlash
la source
3

Avoir une UserForminstance par défaut exécutant l’émission est peut-être la chose la plus facile à faire, mais c’est aussi une cause directe de beaucoup de problèmes - des bogues faciles à introduire, mais difficiles à trouver, aux problèmes de maintenance et d’extensibilité: solution rapide, fonctionne "est le modèle" Smart UI ", qui fonctionne génial pour un prototype . Les grands projets qui évoluent constamment avec le temps exigent une architecture plus intelligente.

Les programmeurs l'appellent "model-view-presenter". La vue est la forme. Les données sont le modèle , et puis il y a le présentateur qui coordonne tout.

Appeler une feuille secondaire à partir d'un formulaire utilisateur

La vérité est que vous ne le faites pas. Un modal UserFormest un dialogue dont le rôle n'est rien de plus que de recueillir les données de l'utilisateur. En le rendant uniquement responsable de la manipulation des données et en laissant le macro / appelant responsable du flux de contrôle, vous rendez le code plus robuste et plus facile à gérer, en particulier si le formulaire peut faire beaucoup de choses.

Commencez avec un MonthlyReportParamsmodule de classe simple :

Option Explicit
Public Month As Integer ' encapsulate into properties to implement 
Public Year As Integer  ' logic for validation on assignment.

Public Property Get IsValid() As Boolean
    IsValid = Month >= 1 And Month <= 12 And _
              Year >= 1900 And Year <= 2100
End Property

Maintenant UserForm, il ne reste plus qu’à travailler avec ces données, ce modèle .

Option Explicit
Private params As MonthlyReportParams
Private cancelled As Boolean

Private Sub Class_Initialize()
    Set params = New MonthlyReportParams
End Sub

Public Property Get Model() As MonthlyReportParams
    Set Model = params
End Property

Public Property Set Model(ByVal value As MonthlyReportParams)
    Set params = value
    MonthBox.value = params.Month
    YearBox.value = params.Year
End Property

Public Property Get IsCancelled() As Boolean
    IsCancelled = cancelled
End Property

Private Sub MonthBox_Change()
    ' make sure the textboxes contain numeric values before assigning to Integer
    If IsNumeric(MonthBox.Value) Then params.Month = CInt(MonthBox.Value)
    OnValidate
End Sub

Private Sub YearBox_Change()
    ' make sure the textboxes contain numeric values before assigning to Integer
    If IsNumeric(YearBox.Value) Then params.Year = CInt(YearBox.Value)
    OnValidate
End Sub

Private Sub OkButton_Click()
    Me.Hide
End Sub

Private Sub CancelButton_Click()
    OnCancel
End Sub

Private Sub OnCancel()
    cancelled = True
    Me.Hide
End Sub

Private Sub OnValidate()
    OkButton.Enabled = Model.IsValid
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
' runs when form is just about to close
    If CloseMode = VbQueryClose.vbFormControlMenu Then
        ' user clicked the [X] button
        Cancel = True ' don't destroy the form
        OnCancel
    End If
End Sub

Et maintenant, la macro qui ouvre ce formulaire peut reprendre le contrôle de ce qui se passe: le formulaire n’exécute plus le show, et nous pouvons lire tout ce qui se passe au même endroit:

Public Sub RunMonthlyReport(Optional ByVal targetSheet As Worksheet = Nothing)

    If targetSheet Is Nothing Then
        ' no sheet was specified; work of the ActiveSheet
        Debug.Assert Not ActiveSheet Is Nothing
        Set targetSheet = ActiveSheet
    End If

    ' create the model
    Dim m As MonthlyReportParams
    Set m = New MonthlyReportParams
    m.Month = Month(Now)
    m.Year = Year(Now)

    ' create the dialog, assign the model
    With New MonthlyReportParamsDialog
        Set .Model = m
        .Show ' next line only runs after dialog has closed

        If Not .IsCancelled Then
            ' run the report with the values in the model
            targetSheet.Report m.Month, m.Year
        End If
    End With

End Sub

Vous trouverez des informations supplémentaires sur les avantages de ce "renversement des responsabilités" dans cet article , ainsi qu'une logique de rappel dans cet article - disclaimer: j'ai écrit les deux; ce blog est le blog officiel du projet OSS complémentaire de Rubberduck VBIDE, que je possède.

Mathieu Guindon
la source
Merci pour la réponse détaillée! Donc, d'après ce que j'ai compris, vous suggérez d'implémenter une classe avec différentes propriétés pour représenter les paramètres d'entrée (mois et année). Userform écrirait alors sur cette classe et le module Report en lirait. Cela déchargerait le travail de Userform sur le module, ce qui le rendrait plus robuste. Est-ce exact?
Mykrus
@ mykrus exactement! Avec des ajustements mineurs, il rend également la forme réutilisable.
Mathieu Guindon le
0

HackSlash a répondu à ma question:

Votre exemple de code a fonctionné pour moi. Gardez à l'esprit que votre utilisation de "Sheet1" en tant qu'objet est le nom de l'objet de la feuille et non le nom de la feuille de calcul visible sur l'onglet de la feuille Excel réelle. Si vous souhaitez l'appeler par le nom de la feuille de travail, essayez ceci: ThisWorkbook.Worksheets ("Sheet1"). Report intMonth, intYear REMARQUE: vous n'avez pas besoin de l'instruction Call si vous supprimez la parenthèse. - HackSlash Il y a 7 minutes

Mykrus
la source
1
Notez que l'accès à la feuille par nom plutôt que par nom de code comme vous l'avez fait, le rend vulnérable à exploser dès qu'un utilisateur renomme la feuille de calcul. Si la feuille existe ThisWorkbookau moment de la compilation, définissez sa (Name)propriété dans la fenêtre des propriétés (F4), en recherchant la feuille dans l' explorateur de projet (Ctrl + R). Ensuite, vous pouvez utiliser ce nom / identifiant dans le code pour faire référence à cette feuille, sans avoir besoin de la déréférencer d'une Worksheetscollection, ni de déclarer une variable pour celle-ci.
Mathieu Guindon
J'ai ajouté une réponse. Si vous êtes satisfait, vous pouvez supprimer cette réponse et en choisir une autre comme solution. J'ai ajouté le conseil de Mathieu parce qu'il a raison.
HackSlash