Laden...

Label Objekt zum Blinken gebracht

Erstellt von Pardasus vor 6 Jahren Letzter Beitrag vor 6 Jahren 4.761 Views
P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 6 Jahren
Label Objekt zum Blinken gebracht

Moin,
ich habe mir etwas gebaut, womit ein beliebiges Label Objekt zum Blinken gebracht werden kann und selbst aus einem "fremden" Thread heraus, über das Objekt der Text einfach abgeändert werden kann.

Eigentlich bin ich ganz zufrieden damit, bis auf das Dispose() vielleicht hat da jemand noch ein Tipp für mich? Wenn man nach dem Dispose() z.b. wieder Start() ausführt, kommt eine Fehlermeldung, irgendwie noch unschön. Ich weis das man nach dem Dispose() den Start() nicht wieder ausführen kann 😉 freue mich auf Feedback zum Code.


    public class LabelFlash : IDisposable
    {
        private System.Timers.Timer _timer = new System.Timers.Timer(1000);
        private Label _labelObj = null;
        private bool _changeCanStop = false;

        /// <summary>
        /// Gibt das Aktuell genutzte Label zurück oder legt dieses fest.
        /// </summary>
        public Label Label
        {
            get { return _labelObj; }
            set
            {
                _labelObj.Visible = true;
                _labelObj = value;
            }
        }

        /// <summary>
        /// Gibt das Intervall in dem das Label blinken soll in ms zurück oder legt dieses fest.
        /// </summary>
        public double Interval
        {
            get { return _timer.Interval; }
            set
            {
                _timer.Interval = value;
            }
        }

        /// <summary>
        /// Legt fest ob das Label weiter Blinken soll wenn der Text geändert wird. Standard ist Deaktiviert
        /// </summary>
        public bool ChangeStop
        {
            get { return _changeCanStop; }
            set { _changeCanStop = value; }
        }

        /// <summary>
        /// Legt den Text des Label fest oder ruft diesen ab.
        /// </summary>
        public string Text
        {
            get
            {
                if (_labelObj.InvokeRequired)
                {
                    return (string)_labelObj.Invoke(new Func<string>(() => _labelObj.Text));
                }
                else
                {
                    return _labelObj.Text;
                }
            }
            set
            {
                _labelObj.ForeColor = System.Drawing.Color.Black;

                if (_labelObj.InvokeRequired)
                {
                    _labelObj.Invoke(new Func<string>(() => _labelObj.Text = value));
                }
                else
                {
                    _labelObj.Text = value;
                }
            }
        }

        /// <summary>
        /// Legt den Text des Labels fest und ändert die Farbe auf Rot ab.
        /// </summary>
        public string AlertText
        {
            set
            {
                _labelObj.ForeColor = System.Drawing.Color.Red;

                if (_labelObj.InvokeRequired)
                {
                    _labelObj.Invoke(new Func<string>(() => _labelObj.Text = value));
                }
                else
                {
                    _labelObj.Text = value;
                }
            }
        }

        /// <summary>
        /// Bringt ein Label zum Blinken in einem festgestelten ms Interval.
        /// </summary>
        /// <param name="LabelObj">Label welches blinken soll.</param>
        public LabelFlash(Label LabelObj)
        {
            _labelObj = LabelObj;
            _timer.Elapsed += _timer_Elapsed;
            _labelObj.TextChanged += _labelObj_TextChanged;
        }

        /// <summary>
        /// Bringt ein Label zum Blinken in dem übergebenen ms Interval.
        /// </summary>
        /// <param name="LabelObj">Label welches blinken soll.</param>
        /// <param name="Interval">Interval in ms.</param>
        public LabelFlash(Label LabelObj, double Interval)
        {
            _labelObj = LabelObj;
            _timer.Interval = Interval;
            _timer.Elapsed += _timer_Elapsed;
            _labelObj.TextChanged += _labelObj_TextChanged;
        }

        /// <summary>
        /// Startet das Blinken des Labels.
        /// </summary>
        /// <returns></returns>
        public LabelFlash Start()
        {
            _timer.Start();
            return this;
        }

        /// <summary>
        /// Startet das Blinken des Labels.
        /// </summary>
        /// <param name="ChangeCantStop">Legt fest ob das Label weiter Blinken soll wenn der Text geändert wird.</param>
        /// <returns></returns>
        public LabelFlash Start(bool ChangeCantStop)
        {
            _timer.Start();
            _changeCanStop = ChangeCantStop;
            return this;
        }

        /// <summary>
        /// Stop das Blinken des Labels und macht es wieder sichtbar.
        /// </summary>
        public void Stop()
        {
            _timer.Stop();
            _labelObj.Visible = true;
        }

        // Wenn Aktiviert, hört das Label auf zu Blinken wenn der Text geändert wird.
        private void _labelObj_TextChanged(object sender, EventArgs e)
        {
            if (_changeCanStop)
            {
                Stop();
            }
        }

        // Wird im Defenierten Interval ausgeführt und lässt das Label Asynchron Blinken.
        private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (_labelObj.InvokeRequired)
            {
                _labelObj.Invoke(new Action(() => _timer_Elapsed(sender, e)));
            }
            else
            {
                _labelObj.Visible = !_labelObj.Visible;
            }
        }

        protected virtual void Dispose(bool disposing)
        {
            if(disposing)
            {
                ((IDisposable)_timer).Dispose();
                _timer = null;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(true);
        }
    }

2.078 Beiträge seit 2012
vor 6 Jahren

Du solltest dazu schreiben, dass es sich um WindowsForms handelt 😉

Zu deiner eigentlichen Frage bezüglich Dispose:

Ich würde den Timer nicht diposen, zumindest nicht sofort.
Ich würde ihn im ersten Schritt normal beenden, der müsste dafür eine Stop-Methode haben. Danach kannst Du immer noch diposen. Ob das bei diesem Timer notwendig oder sinnvoll ist, kann ich dir aber nicht sagen, ich halte mich aber an dieses Vorgehen, wenn die Dispose-Methode nicht im Destruktor aufgerufen wurde. Das würde bei dir zwar (vermutlich) keinen Unterschied machen, aber früher oder später wirst Du Situationen erleben, wo das einen großen Unterschied macht 😉

Ansonsten weiß ich nicht, was dich daran stört. Es ist in vielen Fällen so, dass ein Objekt nach einem Dispose nicht mehr verwendet werden kann. Grundsätzlich würde ich mich eher darauf verlassen, dass ein Objekt nach einem Dispose nicht mehr verwendet werden, selbst wenn es funktioniert.

Einziger Knackpunkt:

GC.SuppressFinalize(true);

Die Methode verhindert, dass der Destruktor von GC aufgerufen wird. Du verhinderst, dass der GC den Destruktor von dem bool-Wert aufruft.
Du musst this übergeben.

Allgemein zu der Klasse:

Mir scheint es so, als würdest Du zwei Aufgaben vermischen.
Zum Einen lässt die Klasse das Label blinken und zum Anderen schreibt sie den Text des Labels mit verschiedenen Farben. Abgesehen davon, dass die Farben von außen nicht geändert werden können, hat das Text und Farbe ändern nichts mit dem Blinken an sich zu tun, oder?
Das würde ich daher komplett raus lassen, immerhin verschenkst Du dadurch Flexibilität und erhöhst unnötig die Komplexität.
Abgesehen davon würde das Ändern der Farbe aus einem anderen Thread heraus wahrscheinlich crashen 😉

Wenn ich mich richtig erinnere, brauchst Du beim reinen Abfragen der Eigenschaften (wie Du es bei den Text-Properties tust) kein Invoke. Du änderst den Wert und damit den Zustand des Objektes nicht, abrufen sollte also aus jedem Thread heraus funktionieren. Teste das aber lieber nochmal - oder jemand korrigiert mich hier.

Die ChangeStop-Property kannst Du auch automatisch implementieren lassen, das spart eine Zeile Code 😄

Ich würde - nur meine subjektive Ansicht - das Label nicht von außen ändern lassen, die Label-Property würde ich also entfernen. Alles was von außen geändert werden kann, musst Du verwalten bzw. Du musst die Änderungen beachten können. Wenn das nicht unbedingt notwendig ist, erhöht es nur die Komplexität ohne wirklichen Gewinn.
In deinem Fall würde das z.B. Probleme verursachen, denn Du musst die TextChanged-Methode vom Event des alten Labels entfernen und dem Neuen hinzufügen. Das heißt, dein ChangeStop-Feature funktioniert mit einem neuen Label nicht und eine Änderung an einem alten Label würde zu merkwürdigem Verhalten führen. Außerdem würde damit das alte Label-Objekt am Leben gehalten werden, selbst wenn es nicht mehr genutzt wird und der GC es sonst weg werfen würde.

Ich persönlich versuche Konstruktoren immer so zu bauen, dass alle Überladungen nichts weiter tun als ihre Parameter aufzubereiten und an einen anderen Konstruktor weiter zu reichen - oder Default-Werte übergeben. Daraus resultiert, dass von vielen Konstruktoren nur Einer die eigentliche Arbeit tut und die Anderen nur weiter leiten. Spätestens bei komplexen Initialisierungen wird das hilfreich.
Du könntest z.B. für den interval-Parameter optional deklarieren und mit einem sinnvollen Default-Wert versehen. Oder die Überladung ohne diesen Parameter gibt diesen Default-Wert weiter.
Das geht natürlich nur, wenn die Initialisierungs-Varianten sich nicht zu stark von einander unterscheiden, aber da muss man dann je nach Situation schauen.

Methoden-Parameter werden mit kleinem ersten Buchstaben geschrieben 😉 Das ist zumindest sehr weit verbreitet, ich glaub Microsoft hat das in einer Guideline veröffentlicht.

16.792 Beiträge seit 2008
vor 6 Jahren

Wenn man nach dem Dispose() z.b. wieder Start() ausführt, kommt eine Fehlermeldung, irgendwie noch unschön.

Prinzipiell ist das auch korrekt.
Ein Objekt, das disposed ist, sollte bei einem weiteren Zugriff eine ObjectDisposedException werfen.

P
1.090 Beiträge seit 2011
vor 6 Jahren

Es fehlt auch der Finalizer, der wird vom GC aufgerufen wenn man vergessen sollte Dispose aufzurufen.

Für den Timer sollte auf jeden Fall Dispose aufgerufen werden. (Wenn ich es richtig im Kopft habe) Eine der Timer Varianten wird auf dem Speicher gepinnt. Und das verhindert das der GC ihn wegräumen kann.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 6 Jahren

Erst einmal Danke für das Feedback!

Mir scheint es so, als würdest Du zwei Aufgaben vermischen.
Zum Einen lässt die Klasse das Label blinken und zum Anderen schreibt sie den Text des Labels mit verschiedenen Farben. Abgesehen davon, dass die Farben von außen nicht geändert werden können, hat das Text und Farbe ändern nichts mit dem Blinken an sich zu tun, oder?
Das würde ich daher komplett raus lassen, immerhin verschenkst Du dadurch Flexibilität und erhöhst unnötig die Komplexität.
Abgesehen davon würde das Ändern der Farbe aus einem anderen Thread heraus wahrscheinlich crashen 😉

Da hast Du recht, es sieht aus als würde ich was vermischen. Ich habe ein kleines Programm was im Hintergrund z.b. Daten einliest und es soll Signalisiert werden "Hey ich tun gerade was im Hintergrund". Wenn dann etwas schief läuft, soll der Text aufhören zu Blinken und Rot werden. Damit ich aus einen beliebigen Thread heraus das machen kann, habe ich mir auch gleich noch gedacht das sich das Objekt darum kümmern kann, dass der Text auch wirklich ausgegeben wird (die ganze Invoke Geschichte).
Aus meiner Perspektive sah das alles klar aus und gehört zusammen 😃

Die ChangeStop-Property kannst Du auch automatisch implementieren lassen, das spart eine Zeile Code 😄

Da stehe ich gerade auf dem schlauch, wie meinst Du das?

Ich würde - nur meine subjektive Ansicht - das Label nicht von außen ändern lassen, die Label-Property würde ich also entfernen. Alles was von außen geändert werden kann, musst Du verwalten bzw. Du musst die Änderungen beachten können. Wenn das nicht unbedingt notwendig ist, erhöht es nur die Komplexität ohne wirklichen Gewinn.

Du meinst, das _labelObj sollte nicht nach dem Instanzieren des Objekts änderbar sein? Mmh, das stimmt. Wenn man das möchte, muss man sich ein neues Objekt Instanzieren, dann kann nichts schief gehen. Danke!

Ich persönlich versuche Konstruktoren immer so zu bauen, dass alle Überladungen nichts weiter tun als ihre Parameter aufzubereiten und an einen anderen Konstruktor weiter zu reichen - oder Default-Werte übergeben. Daraus resultiert, dass von vielen Konstruktoren nur Einer die eigentliche Arbeit tut und die Anderen nur weiter leiten. Spätestens bei komplexen Initialisierungen wird das hilfreich.
Du könntest z.B. für den interval-Parameter optional deklarieren und mit einem sinnvollen Default-Wert versehen. Oder die Überladung ohne diesen Parameter gibt diesen Default-Wert weiter.
Das geht natürlich nur, wenn die Initialisierungs-Varianten sich nicht zu stark von einander unterscheiden, aber da muss man dann je nach Situation schauen.

Wie ruft den der eine Konstruktor den anderen auf?

Methoden-Parameter werden mit kleinem ersten Buchstaben geschrieben 😉 Das ist zumindest sehr weit verbreitet, ich glaub Microsoft hat das in einer Guideline veröffentlicht.

Super, danke! 😃 ich bin immer noch dabei mein Stil zu finden. Versuche mich aber sehr an "feste" Regeln zu halten.

Zu den Dispose() mal Grundsätzlich. Da ich bis jetzt sehr kleine, unbedeutsame Anwendung geschrieben habe, habe ich mich nie damit ernsthaft Beschäftigt bei meinen eigenen Objekten.
Klar, wenn ich z.b. ein Objekt benutzt wie FileStream etc. wo ein Dispose() dabie ist, nutze ich diesen auch. Habe aber nie wirklich selbst ein in mein Objekt eingebaut.
Ist vielleicht jetzt der falsche bereich im Forum, aber wie würde ich für diese Objekt von mir ein Dispose() richtig hinterlegen?

2.078 Beiträge seit 2012
vor 6 Jahren

Da hast Du recht, es sieht aus als würde ich was vermischen. Ich habe ein kleines Programm was im Hintergrund z.b. Daten einliest und es soll Signalisiert werden "Hey ich tun gerade was im Hintergrund". Wenn dann etwas schief läuft, soll der Text aufhören zu Blinken und Rot werden. Damit ich aus einen beliebigen Thread heraus das machen kann, habe ich mir auch gleich noch gedacht das sich das Objekt darum kümmern kann, dass der Text auch wirklich ausgegeben wird (die ganze Invoke Geschichte).
Aus meiner Perspektive sah das alles klar aus und gehört zusammen 😃

Ich sehe das immer noch als gemischte Aufgaben 😛
Vermutlich willst Du eher etwas, was sich um das korrekte ausführen bei mehreren Threads kümmert und da würde ich dann eine InvokeIfNecessary-Erweiterungsmethode definieren. Die sähe dann z.B. so aus:

public static void InvokeIfNecessary(this Control control, Action action)
{
    if (_labelObj.InvokeRequired)
        _labelObj.Invoke(action);
    else
        action();
}

Ich hoffe, ich hab jetzt nichts über den Haufen geworfen, ich hab lange nichts mehr mit WinForms gemacht.

Wenn Du das Text schreiben und ändern der Farbe raus ziehst, hast Du langfristig betrachtet, mehr Möglichkeiten. Wenn Du z.B. irgendwann mehrere Labels hast, wo Du verschiedene Farben oder drei Zustände anstatt zwei hast, dann musst Du jedes Mal deine Klasse so erweitern, dass sie das kann.
Und das nur um zwei Property-Zuweisungen zu "verstecken"?

Da stehe ich gerade auf dem schlauch, wie meinst Du das?

public int MyProperty1 { get; set; }
public int MyProperty2 { get; private set; }
public int MyProperty3 { get; }
  1. Ist eine normale Property, wie dein ChangeStop. Der Compiler generiert auch einfach nur ein BackingField dafür, technisch ändert sich also nichts, es ist bloß weniger Schreibarbeit.
  2. Kann nur in der selben Klasse geschrieben werden.
  3. Kann gar nicht geschrieben werden, außer per Reflection.

Du meinst, das _labelObj sollte nicht nach dem Instanzieren des Objekts änderbar sein? Mmh, das stimmt. Wenn man das möchte, muss man sich ein neues Objekt Instanzieren, dann kann nichts schief gehen. Danke!

Genau. Du hast auch keinen Nachteil dadurch, außer einen minimal größeren Speicherbedarf, aber das ist heutzutage völlig irrelevant, dass es mehr Nachteile als Vorteile bringt, wenn Du in dem Maße darauf achtest.

Wie ruft den der eine Konstruktor den anderen auf?

public MyClass()
    : this(5)
{
    // Der Code hier wird nach MyClass(int) aufgerufen
}
public MyClass(int zahl)
    : this(zahl.ToString())
{
    // Der Code hier wird nach MyClass(string) aufgerufen
}
public MyClass(string text)
{
    _text = text;
    // Und was noch so nötig ist
}

Das this in dem Fall "zeigt" auf einen anderen Konstruktor, der zu den übergebenen Parametern passt. Meine Kommentare zeigen dabei die Reihenfolge auf, denn der Konstruktor, auf den this "zeigt", der wird immer vor dem eigentlichen Konstruktor-Inhalt ausgeführt.

Bezüglich Dispose:

Ich würde sagen, Du brauchst dieses Dispose-Muster gar nicht, solange Du nur verwaltete Ressourcen verwendest.
Wenn Du z.B. von der Windows-API irgendein Handle anfragst, das später wiederfrei gegeben werden muss, dann brauchst Du das Dispose-Muster in seiner umfangreichen Form:

public MyClass()
{
    // ...
}
// Das ist der Destruktor:
~MyClass()
{
    Dispose(false);
}

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        // verwaltete Ressourcen frei geben
    }

    // nicht verwaltete Ressourcen (wie das Handle) frei geben.
}

Wenn Du Dispose aufrufst, dann werden alle Ressourcen normal frei gegeben und der Garbage Collector weiß, dass er den Destruktor nicht mehr aufrufen soll.
Wenn das aus irgendeinem Grund nicht klappt, dann wird früher oder später der GC aufräumen und den Destruktor aufrufen. Er stellt damit sicher, dass die nicht verwalteten Ressourcen frei gegeben werden, wenn alle anderen Sicherungen versagt haben. Die verwalteten Ressourcen interessieren an der Stelle nicht, denn die sind dem GC bekannt (daher verwaltet), der wird bei denen auch noch aufräumen.

Der Timer ist eine verwaltete Ressource. Der bzw. die intern verwendeten Ressourcen kümmern sich selber darum, dass alle nicht verwalteten Ressourcen frei gegeben werden.
In dem Fall sähe das dann so aus:

public MyClass()
{
}

public void Dispose()
{
    _timer.Dispose();
}

Prinzipiell solltest Du darauf achten, dass jede Klasse, die Ressourcen verwendet, die frei gegeben werden müssen, immer IDisposable implementiert und je nach Art der Ressource auch sicher stellt, dass nicht verwaltete Ressourcen aufgeräumt werden können, selbst wenn Dispose nicht aufgerufen wurde.
Der Nutzer einer Klasse sollte nur noch als einzige Verantwortung sicher stellen, dass er Dispose aufruft. Alles Weitere sollte die Klasse intern für sich klären.

Beachte aber:
Du kannst nicht einfach alles im Destruktor aufräumen und auf IDispoable verzichten, denn Du weißt darin nicht, welches Objekt noch existiert (es könnte sein, dass irgendetwas plötzlich null ist). Es gibt noch mehr Nachteile, aber ich will jetzt nichts schreiben, was ich nicht sicher weiß.

Eigentlich ist der Destruktor in dem Konzept nur ein Notfall-Pflaster. Wenn der Destruktor gebraucht wird, hast Du etwas falsch gemacht, denn eigentlich sollte der nie aufgerufen werden, da immer Dispose alles regelt.
In dem Zuge solltest Du dir auch mal using anschauen, denn das sorgt dafür, dass auch im Fall einer Exception das Dispose aufgerufen wird.

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 6 Jahren

WoW, vielen Dank für dein sehr ausführlichen Beitrag und tollen Tipps! Ich habe den Code mal angepasst.
Ist das Dispose() nun auch richtig?


    public class LabelFlash : IDisposable
    {
        private System.Timers.Timer _timer = new System.Timers.Timer(1000);

        /// <summary>
        /// Gibt das Aktuell genutzte Label zurück.
        /// </summary>
        public Label LabelObj { get; } = null;

        /// <summary>
        /// Gibt das Intervall in dem das Label blinken soll in ms zurück oder legt dieses fest.
        /// </summary>
        public double Interval
        {
            get { return _timer.Interval; }
            set { _timer.Interval = value; }
        }

        /// <summary>
        /// Legt fest ob das Label weiter Blinken soll wenn der Text geändert wird. Standard ist Deaktiviert
        /// </summary>
        public bool ChangeStop { get; set; } = false;

        /// <summary>
        /// Legt den Text des Label fest oder ruft diesen ab.
        /// </summary>
        public string Text
        {
            get
            {
                if (LabelObj.InvokeRequired)
                {
                    return (string)LabelObj.Invoke(new Func<string>(() => LabelObj.Text));
                }
                else
                {
                    return LabelObj.Text;
                }
            }
            set
            {
                LabelObj.ForeColor = System.Drawing.Color.Black;

                if (LabelObj.InvokeRequired)
                {
                    LabelObj.Invoke(new Func<string>(() => LabelObj.Text = value));
                }
                else
                {
                    LabelObj.Text = value;
                }
            }
        }

        /// <summary>
        /// Legt den Text des Labels fest und ändert die Farbe auf Rot ab.
        /// </summary>
        public string AlertText
        {
            set
            {
                LabelObj.ForeColor = System.Drawing.Color.Red;

                if (LabelObj.InvokeRequired)
                {
                    LabelObj.Invoke(new Func<string>(() => LabelObj.Text = value));
                }
                else
                {
                    LabelObj.Text = value;
                }
            }
        }

        /// <summary>
        /// Bringt ein Label zum Blinken in einem festgestelten ms Interval.
        /// </summary>
        /// <param name="labelObj">Label weches blinken soll.</param>
        public LabelFlash(Label labelObj)
        {
            this.LabelObj = labelObj;
            _timer.Elapsed += _timer_Elapsed;
            this.LabelObj.TextChanged += LabelObj_TextChanged;
        }

        /// <summary>
        /// Bringt ein Label zum Blinken in dem übergebenen ms Interval.
        /// </summary>
        /// <param name="labelObj">Label welches blinken soll.</param>
        /// <param name="interval">Interval in ms.</param>
        public LabelFlash(Label labelObj, double interval) : this(labelObj)
        {
            _timer.Interval = interval;
        }

        /// <summary>
        /// Startet das Blinken des Labels.
        /// </summary>
        /// <returns></returns>
        public LabelFlash Start()
        {
            _timer.Start();
            return this;
        }

        /// <summary>
        /// Startet das Blinken des Labels.
        /// </summary>
        /// <param name="changeStop">Legt fest ob das Label weiter Blinken soll wenn der Text geändert wird.</param>
        /// <returns></returns>
        public LabelFlash Start(bool changeStop)
        {
            _timer.Start();
            ChangeStop = changeStop;
            return this;
        }

        /// <summary>
        /// Stop das Blinken des Labels und macht es wieder sichtbar.
        /// </summary>
        public void Stop()
        {
            _timer.Stop();
            LabelObj.Visible = true;
        }

        // Wenn Aktiviert, hört das Label auf zu Blinken wenn der Text geändert wird.
        private void LabelObj_TextChanged(object sender, EventArgs e)
        {
            if (ChangeStop)
            {
                Stop();
            }
        }

        // Wird im Defenierten Interval ausgeführt und lässt das Label Asynchron Blinken.
        private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (LabelObj.InvokeRequired)
            {
                LabelObj.Invoke(new Action(() => _timer_Elapsed(sender, e)));
            }
            else
            {
                LabelObj.Visible = !LabelObj.Visible;
            }
        }

        ~LabelFlash()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _timer.Dispose();
            }

            LabelObj.TextChanged -= LabelObj_TextChanged;
            _timer.Elapsed -= _timer_Elapsed;
        }
    }

2.078 Beiträge seit 2012
vor 6 Jahren

In deinem Fall reicht ein ganz simples

public void Dispose()
{
    _timer.Dispose();
}

Das Event beim Timer zu entfernen ist vermutlich überflüssig, da der Timer sowieso nicht mehr arbeitet.

Ach was mir dabei auch auffällt:
Ich würde das Event am Label nicht im Konstruktor registrieren, sondern in Start(), denn erst dann ist es von Bedeutung, oder?
Das Timer-Event kann auch im Konstruktor definiert werden, die Klasse hat ja intern die Kontrolle, wann es geworfen wird.

So würde ich es machen:

public void Start()
{
    _label.TextChanged += LabelObj_TextChanged;
    _timer.Start();
}
public void Stop()
{
    _timer.Stop();
    _label.TextChanged -= LabelObj_TextChanged;
    
    _label.InvokeIfNecessary(() => LabelObj.Visible = true);
}
public void Dispose()
{
    Stop();
    _timer.Dispose();
}

Nagel mich nicht darauf fest, dass das funktioniert. Der Code ist im Browser runter getippt und nicht getestet, aber ich denke, das Prinzip wird klar.

Ach ja:
Das Zuweisen der ForeColor kann immer noch zu Problemen führen 😛