2
votes

Exécuter l'événement lorsqu'un formulaire se charge

J'essaye de créer un concours de popularité pour les formulaires dans notre interface principale. Il y a de nombreux éléments qui ne sont plus utilisés, mais obtenir des détails sur ceux qui sont utilisés et ceux qui ne le sont plus s'avère difficile.

J'ai donc eu l'idée d'enregistrer un formulaire lorsqu'il est chargé puis dans un an environ, je vais diriger un groupe et avoir une idée des formulaires utilisés, à quelle fréquence et par qui. Maintenant, le problème est que je ne veux pas ajouter une ligne à chaque bloc InitializeComponent de formulaires. Au lieu de cela, je voudrais mettre cela dans le fichier Program.cs et comment intercepter tous les chargements de formulaire afin que je puisse les enregistrer.

Est-ce possible?

Modifier

En utilisant le commentaire de @ Jimi, j'ai pu trouver ce qui suit.

using CrashReporterDotNET;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows.Automation;
using System.Windows.Forms;

namespace Linnabary
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //This keeps the user from opening multiple copies of the program
            string[] clArgs = Environment.GetCommandLineArgs();
            if (PriorProcess() != null && clArgs.Count() == 1)
            {
                MessageBox.Show("Another instance of the WOTC-FE application is already running.");
                return;
            }

            //Error Reporting Engine Setup
            Application.ThreadException += ApplicationThreadException;
            AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;


            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            //This is the SyncFusion License Key.
            Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("<Removed>");

            //Popularity Contest
            Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent,
                         AutomationElement.RootElement, TreeScope.Subtree, (UIElm, evt) =>
                          {
                              try
                              {
                                  AutomationElement element = UIElm as AutomationElement;
                                  string AppText = element.Current.Name;
                                  if (element.Current.ProcessId == Process.GetCurrentProcess().Id)
                                  {
                                      Classes.Common.PopularityContest(AppText);
                                  }
                              }
                              catch (Exception)
                              {
                                  //throw;
                              }
                          });


            Application.Run(new Forms.frmMain());
        }

        private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
        {
            ReportCrash((Exception)unhandledExceptionEventArgs.ExceptionObject);
            Environment.Exit(0);
        }

        private static void ApplicationThreadException(object sender, ThreadExceptionEventArgs e)
        {
            ReportCrash(e.Exception);
        }

        public static void ReportCrash(Exception exception, string developerMessage = "")
        {
            var reportCrash = new ReportCrash("<Removed>")
            {
                CaptureScreen = true,
                DeveloperMessage = Environment.UserName,
                ToEmail = "<Removed>"
            };
            reportCrash.Send(exception);
        }

        public static Process PriorProcess()
        {
            Process curr = Process.GetCurrentProcess();
            Process[] procs = Process.GetProcessesByName(curr.ProcessName);
            foreach (Process p in procs)
            {
                if ((p.Id != curr.Id) && (p.MainModule.FileName == curr.MainModule.FileName))
                {
                    return p;
                }
            }
            return null;
        }
    }
}

Cependant, je me demande s'il existe un moyen d'obtenir le nom du formulaire au lieu de le Texte. Puisque cela accède à TOUTES les fenêtres et qu'il est donc en dehors de l'espace géré, j'en doute. Pourtant, cela fonctionne et je publierai ceci comme réponse demain si personne d'autre ne le fait.


9 commentaires

Vous voudrez peut-être jeter un œil au modèle de conception Observer


Vous pouvez ajouter à Program.cs un AutomationEventHandler . Cet événement est déclenché lorsqu'une fenêtre est sur le point d'être affichée (n'importe quelle fenêtre). Vous pouvez déterminer si cette fenêtre appartient au processus en cours (votre application) et, le cas échéant, la consigner. Il existe un exemple de travail en C # ici: Exécutez l'application actuelle en tant qu'instance unique et affichez l'instance précédente (deuxième section de code) qui détecte le contraire, supprimez simplement le ! .


@Cid J'ai créé une classe qui utilise IObserver mais qui ne se déclenche pas lors de l'ouverture d'un formulaire. Je n'ai jamais utilisé IObserver auparavant, donc je suis probablement hors de ma profondeur sur celui-ci.


@Jimi Ce code semble plus destiné à empêcher l'exécution d'une deuxième instance du programme. Je ne vois pas comment je peux déclencher quelque chose sur un chargement de sous-formulaire.


Nan. Ce code dans son ensemble est utilisé pour empêcher une deuxième instance d'une application (et quelque chose de plus que cela). La deuxième partie du code (qui se trouve dans le constructeur du formulaire dans ce code et doit être déplacée vers sub Main dans votre cas), détecte uniquement lorsqu'une fenêtre est ouverte et détermine si elle ne fait pas partie de le processus actuel. Cette logique peut bien sûr être inversée. Si vous avez besoin d'un exemple, faites-le moi savoir.


@Jimi Le code que j'ai ajouté à ma question pose un problème. Il s'exécute deux fois pour n'importe quelle forme que mon programme charge, mais une fois pour démarrer le programme. Comment puis-je l'empêcher de s'exécuter deux fois lors des chargements de formulaires?


Éditer; Seuls les formulaires chargés à partir de la double saisie de l'écran principal. Les formulaires de sous-formulaire ne le font pas.


Peut-être avez-vous trouvé un bogue dans votre code. Cet événement est déclenché une fois par fenêtre (je ne sais pas ce que vous entendez par écran principal ). Avez-vous .Hide () et .Show () une nouvelle instance? Concernant les noms des formulaires, vous pouvez utiliser la collection Application.OpenForms , en filtrant le handle d'un formulaire (clause .Where () ). Bien sûr, vous aurez besoin d'appeler le thread de l'interface utilisateur, vous avez donc besoin d'un délégué IAsyncResult qui renvoie true si l'élément Form.Handle.Equals ((IntPtr). Current.NativeWindowHandl‌ e) .


Ou, vous avez mis ce code à deux endroits différents (ce qui, bts, est sans contexte dans votre édition). Vous n'avez besoin de ce code que dans la méthode Main de Program.cs .


4 Réponses :


0
votes

Oui, ça devrait être facile. Il existe des hooks d'événement comme OnLoad, OnShow, OnClose () pour tous les formulaires et la plupart des contrôles utilisateur. Si vous vouliez voir, à un niveau plus granulaire, quels contrôles sont utilisés par vos utilisateurs, vous pouvez connecter OnClick (), OnMouseOver () et une centaine d'autres événements.

... et vous pouvez créer vos propres événements personnalisés.

Alors, connectez les événements en sélectionnant le formulaire, puis les propriétés (clic droit ou touche F4). Dans la fenêtre des propriétés en haut, vous avez un bouton "Afficher les événements" qui ressemble à un éclair. Cliquez dessus, puis choisissez, dans la liste, l'événement que vous souhaitez utiliser pour cette journalisation.

 entrez la description de l'image ici


2 commentaires

C'est comme ça que je le fais en ce moment. Je me demandais s'il y avait un moyen de le faire globalement pour ne pas avoir à ajouter une ligne à chaque formulaire. Il est possible de détecter les erreurs globalement, j'ai donc pensé qu'il serait possible de voir les formulaires se charger globalement.


Si tous vos formulaires ont hérité d'une classe parente qui avait un événement OnLoad (), vous pourrez peut-être le faire de cette façon; cela pourrait être une solution globale. Mais il semble que vous essayez de réduire votre charge de travail, pas d'y ajouter.



0
votes

Une solution pas si chère (peut-être) peut être celle-ci:

Créez une nouvelle classe MyBaseForm , qui hérite de System.Windows.Forms.Form , et gérez son événement de chargement comme vous le souhaitez.

Maintenant le plus dur: modifiez toutes les classes de formulaires existantes pour qu'elles héritent de MyBaseForm et non de la valeur par défaut System.Windows.Forms.Form ; et assurez-vous de faire de même pour chaque futur formulaire que vous ajouterez à votre solution.

Pas du tout à l'épreuve des balles, il peut être facile d'oublier de modifier la classe de base pour un nouveau formulaire et / ou de rater la modification pour une classe de formulaire existante

Mais vous pouvez l'essayer


2 commentaires

J'utilise une classe statique dans une classe commune pour faire le travail de base de données. Mon principal problème est d'essayer de le faire fonctionner sans toucher à tous les codes de formulaires. En gros, je suis paresseux ^ - ^


@Kayot il semble que vous recherchez un événement "SomeFormIDontKnowWhich_Loaded". Autant que je sache, un tel événement n'existe pas. Faites-moi savoir si vous trouvez quelque chose d'utile. Mais en fait, ce que je suggère est une très petite modification de tous les formulaires existants, une recherche de ": Form" (c'est-à-dire des classes héritant de formulaire) peut vous faciliter la tâche



0
votes

Application d'un IMessageFilter au application pour détecter le message WM_Create , puis déterminer si le handle cible appartenait à un Form serait la solution idéale avec une performance minimale. Malheureusement, ce message n'est pas transmis au filtre. Comme alternative, j'ai sélectionné le message WM_Paint pour réduire l'impact sur les performances. Le code de filtre suivant crée un dictionnaire de noms de types de formulaires et un nombre de formulaires avec ce nom d'élimination ultime. L'événement Form.Closed n'est pas fiable dans toutes les conditions de fermeture, mais l'événement Disposed semble fiable.

    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        FormCreationFilter mf = new FormCreationFilter();
        Application.AddMessageFilter(mf);

        Application.Run(new Form1());
        Application.RemoveMessageFilter(mf);

        foreach (KeyValuePair<string, Int32> kvp in mf.formCounter)
        {
            Debug.Print($"{kvp.Key} opened {kvp.Value} times. ");
        }
    }

Créez une instance et installez le filtre comme dans l'exemple suivant. La boucle foreach est juste affichée pour illustrer l'accès au décompte.

internal class FormCreationFilter : IMessageFilter
{
    private List<Form> trackedForms = new List<Form>();
    internal Dictionary<string, Int32> formCounter = new Dictionary<string, Int32>(); // FormName, CloseCount

    public bool PreFilterMessage(ref Message m)
    {
        // Ideally we would trap the WM_Create, butthe message is not routed through
        // the message filter mechanism.  It is sent directly to the window.
        // Therefore use WM_Paint as a surrgogate filter to prevent the lookup logic 
        // from running on each message.
        const Int32 WM_Paint = 0xF;
        if (m.Msg == WM_Paint)
        {
            Form f = Control.FromChildHandle(m.HWnd) as Form;
            if (f != null && !(trackedForms.Contains(f)))
            {
                trackedForms.Add(f);
                f.Disposed += IncrementFormDisposed;
            }
        }
        return false;
    }

    private void IncrementFormDisposed(object sender, EventArgs e)
    {
        Form f = sender as Form;
        if (f != null)
        {
            string name = f.GetType().Name;
            if (formCounter.ContainsKey(name))
            {
                formCounter[name] += 1;
            }
            else
            {
                formCounter[name] = 1;
            }
            f.Disposed -= IncrementFormDisposed;
            trackedForms.Remove(f);
        }
    }
}


0 commentaires

2
votes

Je poste le code requis pour détecter et enregistrer l'activité des formulaires, à des fins de test ou de comparaison.
Comme indiqué, ce code ne doit être inséré que dans le fichier Program.cs , dans la méthode Main .

Cette procédure enregistre le titre / la légende de chaque nouveau formulaire ouvert et le nom du formulaire.
D'autres éléments peuvent être ajoutés au journal, éventuellement en utilisant une méthode dédiée.

Lorsqu'un nouveau WindowPattern.WindowOpenedEvent détecte qu'une nouvelle fenêtre est créée, le AutomationElement.ProcessId est comparé au ProcessId de l'application pour déterminer si la nouvelle fenêtre appartient à l'application.

La collection Application.OpenForms () est ensuite analysée, en utilisant le Form.AccessibleObject converti en Control.ControlAccessibleObject pour comparer le AutomationElelement.NativeWindowHandle avec un Form.Handle propriété, pour éviter d'invoquer le thread d'interface utilisateur pour obtenir le handle d'un formulaire (qui peut générer des exceptions ou des verrous de thread, car les formulaires se chargent simplement à ce moment-là).

using System.Diagnostics;
using System.IO;
using System.Security.Permissions;
using System.Windows.Automation;

static class Program
{
    [STAThread]
    [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
    static void Main(string[] args)
    {
        Automation.AddAutomationEventHandler(
            WindowPattern.WindowOpenedEvent, AutomationElement.RootElement,
            TreeScope.Subtree, (uiElm, evt) => {
                AutomationElement element = uiElm as AutomationElement;
                if (element == null) return;
                try 
                {
                    if (element.Current.ProcessId == Process.GetCurrentProcess().Id)
                    {
                        IntPtr elmHandle = (IntPtr)element.Current.NativeWindowHandle;
                        Control form = Application.OpenForms.OfType<Control>()
                            .FirstOrDefault(f => (f.AccessibilityObject as Control.ControlAccessibleObject).Handle == elmHandle);

                        string log = $"Name: {form?.Name ?? element.Current.AutomationId} " +
                                     $"Form title: {element.Current.Name}{Environment.NewLine}";
                        File.AppendAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "formLogger.txt"), log);
                    }
                }
                catch (ElementNotAvailableException) { /* May happen when Debugging => ignore or log */ }
            });
    }
}


1 commentaires

Cela fonctionne bien mieux que ce que je faisais. J'ai changé cela pour utiliser la base de données, mais l'utilisation du fichier texte est bonne pour les personnes qui n'ont pas de base de données ou qui veulent garder les choses simples.