einfaches benutzerdefiniertes Ereignis

75

Ich versuche, benutzerdefinierte Ereignisse zu lernen, und ich habe versucht, eines zu erstellen, aber es scheint, als hätte ich ein Problem

Ich habe ein Formular, eine statische Klasse und ein benutzerdefiniertes Ereignis erstellt. Ich versuche zu erreichen, dass durch Drücken der Taste Form die statische Klassenfunktion aufgerufen wird und dann von Zeit zu Zeit ein Ereignis ausgelöst wird, um den aktuellen Status zu melden. Form1 hört zu, wenn das Ereignis ausgelöst wird, und wenn dies der Fall ist, ändert es den Text von label1

Folgendes habe ich bisher

public partial class Form1 : Form
{
    public EventHandler<Progress> progress; 

    public Form1()
    {
        InitializeComponent();
        progress += SetStatus;
    }

    private void SetStatus(object sender, Progress e)
    {
        label1.Text = e.Status;
    }

    private void button1_Click_1(object sender, EventArgs e)
    {
         TestClass.Func();
    }

 }

Datei 2

class TestClass
{
    public static void Func()
    {
        //time consuming code
        Report status 
        // time consuming code
        report status
    }
}

public class Progress : EventArgs
{
    public string Status { get; private set; }

    private Progress() {}

    public Progress(string status)
    {
        Status = status;
    }
}

Was ich jetzt nicht verstehe, ist, wie ich ein Ereignis aus TestClass auslösen kann, damit Form1 das Ereignis verarbeiten und label.Text ändern kann

Rechnung
quelle
Ihre Testklasse muss eine Veranstaltung bereitstellen und das Formular muss diese abonnieren.
Henk Holterman
Ja, das ist in Ordnung, aber ich verstehe nicht, wie ich ein Ereignis aus einer anderen Klasse erheben kann
Bill
1
@ Bill, das kannst du nicht direkt machen. Es ist beabsichtigt. Wenn Sie wirklich möchten, können Sie eine öffentliche Methode erstellen RaiseProgress(), die das Ereignis auslöst, aber ich bin mir nicht sicher, ob dies eine gute Idee ist.
Svick

Antworten:

137

Dies ist eine einfache Möglichkeit, benutzerdefinierte Ereignisse zu erstellen und diese auszulösen. Sie erstellen einen Delegaten und ein Ereignis in der Klasse, aus der Sie werfen. Abonnieren Sie dann das Ereignis aus einem anderen Teil Ihres Codes. Sie haben bereits eine benutzerdefinierte Ereignisargumentklasse, sodass Sie darauf aufbauen können, um andere Ereignisargumentklassen zu erstellen. NB: Ich habe diesen Code nicht kompiliert.

public partial class Form1 : Form
{
    private TestClass _testClass;
    public Form1()
    {
        InitializeComponent();
        _testClass = new TestClass();
        _testClass.OnUpdateStatus += new TestClass.StatusUpdateHandler(UpdateStatus);
    }

    private void UpdateStatus(object sender, ProgressEventArgs e)
    {
        SetStatus(e.Status);
    }

    private void SetStatus(string status)
    {
        label1.Text = status;
    }

    private void button1_Click_1(object sender, EventArgs e)
    {
         TestClass.Func();
    }

}

public class TestClass
{
    public delegate void StatusUpdateHandler(object sender, ProgressEventArgs e);
    public event StatusUpdateHandler OnUpdateStatus;

    public static void Func()
    {
        //time consuming code
        UpdateStatus(status);
        // time consuming code
        UpdateStatus(status);
    }

    private void UpdateStatus(string status)
    {
        // Make sure someone is listening to event
        if (OnUpdateStatus == null) return;

        ProgressEventArgs args = new ProgressEventArgs(status);
        OnUpdateStatus(this, args);
    }
}

public class ProgressEventArgs : EventArgs
{
    public string Status { get; private set; }

    public ProgressEventArgs(string status)
    {
        Status = status;
    }
}
themartinmcfly
quelle
3
Ein weiterer wichtiger Punkt zu Ereignissen wird im Link besprochen . Um sich von Ereignissen abzumelden, ist es - = anstelle von + =. Zum Beispiel _testClass.OnUpdateStatus - = UpdateStatus; Wo der Code zum Abbestellen abgelegt werden soll, ist eine andere Frage, aber es gibt auch andere Fragen zum Stapelüberlauf, die sich damit befassen.
themartinmcfly
1
Eine schlechte Wahl der Namen. Beide Klassen haben eine UpdateStatusFunktion, die verwirrend sein kann. Ich würde die Funktion aus Gründen der Übersichtlichkeit lieber Form1mit einem anderen Namen aufrufen ProcessUpdateStatusEvent. Ansonsten sehr gute Erklärung.
Bogdan Alexandru
Das schreibgeschützte Attribut in ProgressEventArgs wird nicht kompiliert. Das Entfernen dieses Attributs und dies funktioniert wunderbar.
Loren Shaw
Dieser Code ist verwirrender als nötig und nicht threadsicher. Lesen Sie diese Erklärung codeblog.jonskeet.uk/2015/01/30/… und die Antwort von @Volomike.
Xan-Kun Clark-Davis
20

Sie haben kein Ereignis erstellt. Um das zu tun, schreibe:

public event EventHandler<Progress> Progress;

Anschließend können Sie Progressinnerhalb der Klasse aufrufen, in der sie als normale Funktion oder als Delegat deklariert wurde:

Progress(this, new Progress("some status"));

Wenn Sie also den Fortschritt in melden möchten TestClass, sollte das Ereignis ebenfalls vorhanden und statisch sein. Sie können es von Ihrem Formular aus wie folgt abonnieren:

TestClass.Progress += SetStatus;

Außerdem sollten Sie wahrscheinlich umbenennen Progresszu ProgressEventArgs, so dass es ist klar , was es ist.

svick
quelle
15

Ereignisse sind in C # ziemlich einfach, aber die MSDN-Dokumente machen sie meiner Meinung nach ziemlich verwirrend. Normalerweise wird in den meisten Dokumentationen erläutert, wie eine Klasse von der EventArgsBasisklasse geerbt wird, und dafür gibt es einen Grund . Es ist jedoch nicht der einfachste Weg, Events zu machen, und für jemanden, der schnell und einfach etwas möchte, und in einer Zeitkrise ist die Verwendung des ActionTyps Ihr Ticket.

Ereignisse erstellen und abonnieren

1. Erstellen Sie Ihre Veranstaltung direkt nach Ihrer classErklärung in Ihrer Klasse .

public event Action<string,string,string,string>MyEvent;

2. Erstellen Sie Ihre Event-Handler-Klassenmethode in Ihrer Klasse.

private void MyEventHandler(string s1,string s2,string s3,string s4)
{
  Console.WriteLine("{0} {1} {2} {3}",s1,s2,s3,s4);
}

3. Wenn Ihre Klasse aufgerufen wird, weisen Sie sie an, das Ereignis mit Ihrem neuen Ereignishandler zu verbinden. Der Grund, warum der +=Operator verwendet wird, liegt darin, dass Sie Ihren speziellen Ereignishandler an das Ereignis anhängen. Sie können dies tatsächlich mit mehreren separaten Ereignishandlern tun. Wenn ein Ereignis ausgelöst wird, wird jeder Ereignishandler in der Reihenfolge ausgeführt, in der Sie sie hinzugefügt haben.

class Example
{
  public Example() // I'm a C# style class constructor
  {
    MyEvent += new Action<string,string,string,string>(MyEventHandler);
  }
}

4. Wenn Sie bereit sind, lösen Sie das Ereignis irgendwo in Ihrem Klassencode wie folgt aus (auch bekannt als Raise):

MyEvent("wow","this","is","cool");

Das Endergebnis, wenn Sie dies ausführen, ist, dass die Konsole "Wow, das ist cool" ausgibt. Wenn Sie "cool" mit einem Datum oder einer Sequenz geändert und diesen Ereignisauslöser mehrmals ausgeführt haben, wird das Ergebnis in einer FIFO-Sequenz angezeigt, wie Ereignisse normalerweise funktionieren sollten.

In diesem Beispiel habe ich 4 Zeichenfolgen übergeben. Sie können diese jedoch in einen akzeptablen Typ ändern oder mehr oder weniger Typen verwenden oder sogar das <...>Out entfernen und nichts an Ihren Event-Handler übergeben.

Wenn Sie mehrere benutzerdefinierte Ereignishandler hätten und diese alle mit dem +=Operator für Ihr Ereignis abonniert hätten, hätte Ihr Ereignisauslöser sie alle nacheinander aufgerufen.

Ereignisanrufer identifizieren

Was aber, wenn Sie den Anrufer für dieses Ereignis in Ihrem Ereignishandler identifizieren möchten? Dies ist nützlich, wenn Sie einen Ereignishandler wünschen, der mit Bedingungen reagiert, die darauf basieren, wer das Ereignis ausgelöst / ausgelöst hat. Es gibt einige Möglichkeiten, dies zu tun. Nachfolgend finden Sie Beispiele, die in der Reihenfolge ihrer Schnelligkeit angezeigt werden:

Option 1. (Schnellste) Wenn Sie es bereits kennen, übergeben Sie den Namen als Literalzeichenfolge an den Ereignishandler, wenn Sie ihn auslösen.

Option 2. (etwas schnell) Fügen Sie dies Ihrer Klasse hinzu und rufen Sie es über die aufrufende Methode auf. Übergeben Sie diese Zeichenfolge dann an den Ereignishandler, wenn Sie sie auslösen:

private static string GetCaller([System.Runtime.CompilerServices.CallerMemberName] string s = null) => s;

Option 3. (Am wenigsten schnell, aber immer noch schnell) Rufen Sie in Ihrem Ereignishandler beim Auslösen die Zeichenfolge für den Namen der aufrufenden Methode folgendermaßen ab:

string callingMethod = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().ReflectedType.Name.Split('<', '>')[1];

Abmelden von Ereignissen

Möglicherweise haben Sie ein Szenario, in dem Ihr benutzerdefiniertes Ereignis mehrere Ereignishandler enthält, Sie möchten jedoch einen speziellen aus der Liste der Ereignishandler entfernen. Verwenden Sie dazu den -=Operator wie folgt:

MyEvent -= MyEventHandler;

Ein Wort der kleinen Vorsicht. Wenn Sie dieses und jenes Ereignis ausführen und keine Ereignishandler mehr haben und dieses Ereignis erneut auslösen, wird eine Ausnahme ausgelöst. (Ausnahmen können Sie natürlich mit Try / Catch-Blöcken abfangen.)

Alle Ereignisse löschen

Okay, nehmen wir an, Sie sind mit Ereignissen fertig und möchten nicht mehr verarbeiten. Setzen Sie es einfach wie folgt auf null:

MyEvent = null;

Die gleiche Vorsicht gilt auch für das Abbestellen von Ereignissen. Wenn Ihr benutzerdefinierter Ereignishandler keine Ereignisse mehr enthält und Sie ihn erneut auslösen, löst Ihr Programm eine Ausnahme aus.

Volomike
quelle
1
Als jemand, der nach der "schnellen und einfachen" Lösung sucht, habe ich gehofft, diese Antwort zu finden.
Arthur Hebert
9

Wie bereits erwähnt, benötigt das Fortschrittsfeld das Keyword-Ereignis

public event EventHandler<Progress> progress;

Aber ich glaube nicht, dass Sie dort Ihre Veranstaltung haben wollen. Ich denke, Sie wollen die Veranstaltung tatsächlich in TestClass. Wie sieht das folgende aus? (Ich habe noch nie versucht, statische Ereignisse einzurichten, daher bin ich mir nicht sicher, ob das Folgende kompiliert wird oder nicht, aber ich denke, dies gibt Ihnen eine Vorstellung von dem Muster, das Sie anstreben sollten.)

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        TestClass.progress += SetStatus;
    }

    private void SetStatus(object sender, Progress e)
    {
        label1.Text = e.Status;
    }

    private void button1_Click_1(object sender, EventArgs e)
    {
         TestClass.Func();
    }

 }

public class TestClass
{
    public static event EventHandler<Progress> progress; 

    public static void Func()
    {
        //time consuming code
        OnProgress(new Progress("current status"));
        // time consuming code
        OnProgress(new Progress("some new status"));            
    }

    private static void OnProgress(EventArgs e) 
    {
       if (progress != null)
          progress(this, e);
    }
}


public class Progress : EventArgs
{
    public string Status { get; private set; }

    private Progress() {}

    public Progress(string status)
    {
        Status = status;
    }
}
Josh Russo
quelle