Laden...

Forenbeiträge von Master15 Ingesamt 78 Beiträge

12.10.2020 - 18:54 Uhr

Aber damit ziehst du doch das HelloCommand aus dem EmployeeViewModel raus und definierst es im MainViewModel.
Letztlich würde das dazu führen, dass man das für jedes Command so machen müsste und dann per C#-Code das weiterreicht.
Das ergibt für mich keinen Sinn oder ich verstehe dich nicht richtig.

12.10.2020 - 10:19 Uhr

Wenn du in deinem ersten Beispiel ein CommandParameter verwendest, und diesen in CanExecute abfragst, dann funktioniert es wie gewünscht.

Unter "3. Commands" wird eine CommandParameter-Eigenschaft erwähnt und in einem Beispiel mit einem ItemsControl veranschaulicht.

Im Beispielprojekt (MVVMTestProject.zip) gibt es eine ListView für die Mitarbeiter.
Da kann ich natürlich einen Button einfügen und auch das HelloCommand aufrufen:

<ListView ItemsSource="{Binding Employees}" SelectedItem="{Binding SelectedEmployee}">
	<ListView.ItemTemplate>
		<DataTemplate DataType="{x:Type myApp:EmployeeViewModel}">
			<StackPanel Orientation="Horizontal">
				<TextBlock>
					<Run Text="{Binding FirstName}" />
					<Run Text="{Binding LastName}" />
					<Run Text=" - " />
					<Run Text="{Binding Team.Name}" />
				</TextBlock>
				<Button Content="Hallo" Command="{Binding Path=HelloCommand}" />
			</StackPanel>
		</DataTemplate>
	</ListView.ItemTemplate>
</ListView>

Wofür ich die CommandParameter-Eigenschaft brauche, ist mir nicht klar.
Der Button ruft das HelloCommand auch so korrekt auf und wird auch ausgegraut, wenn CanExecute false entspricht.

Mir geht es z.B. um Buttons, die nicht in der ListView stecken, aber dennoch Commands eines "Sub-ViewModels" aufrufen.

<!-- Show Buttons databound to the ViewModel's Commands -->
<StackPanel Margin="0,5,0,0" Orientation="Horizontal">
	<Button Content="Neuen Mitarbeiter hinzufügen" 
			Command="{Binding AddNewEmployeeCommand}" 
			Style="{StaticResource GreenButton}" />
	<Button Content="Ausgewählten Mitarbeiter löschen" 
			Command="{Binding RemoveEmployeeCommand}" 
			CommandParameter="{Binding SelectedEmployee}" 
			Style="{StaticResource RedButton}"
			Margin="5,0,0,0" />
	<Button Content="Hallo Mitarbeiter" 
			Command="{Binding Path=SelectedEmployee.HelloCommand}"
			Margin="5,0,0,0" />
</StackPanel>

Ist SelectedEmployee gleich null, dann wird der Button "Hallo Mitarbeiter" nicht ausgegraut.
Wie JimStark und MarsStein schreiben, bleibt einen scheinbar nicht anderes übrig, in dem Fall zusätzlich über IsEnabled zu gehen.

<Button Content="Hallo Mitarbeiter" 
		Command="{Binding Path=SelectedEmployee.HelloCommand}"
		IsEnabled="{Binding Path=SelectedEmployee, Converter={StaticResource NullToBooleanConverter}}"
		Margin="5,0,0,0" />

Alternativ kann man natürlich auch im MainViewModel ein IsEmployeeSelected-Property (return SelectedEmployee != null) einbauen, was im Beispielprojekt schon enthalten ist:

<Button Content="Hallo Mitarbeiter" 
		Command="{Binding Path=SelectedEmployee.HelloCommand}"
		IsEnabled="{Binding Path=IsEmployeeSelected}"
		Margin="5,0,0,0" />

Deinen Satz, dass es mit einem CommandParameter wie gewünscht funktioniert, ist mir nicht klar.
Kannst du das bitte etwas genauer erklären, wie das mit einem CommandParameter funktioniert, aber ohne IsEnabled?

11.10.2020 - 18:37 Uhr

OK, danke für die Info.

Ich hatte angenommen, dass das Binding eines Commands intelligenter sei und prüft, ob das Objekt bzw. Command evt. null ist und dass dann generell als CanExecute=false angenommen wird.
Da bleibt dann vermutlich doch nur der Weg über IsEnabled und einem NullToBooleanConverter. Hatte gehofft das zu vermeiden.

Damit erübrigt sich dann auch meine Frage mit dem Fokus. Da muss ich mir im MainViewModel selbst merken, welches Dokument/Panel gerade aktiv ist und entsprechend das Command daran binden und ebenfalls mit IsEnabled arbeiten, falls gerade gar nichts aktiv ist.

11.10.2020 - 14:38 Uhr

Hallo zusammen,

ich habe vor über 10 Jahren eine private WPF-Anwendung entwickelt, in der ich eine Art MVC-Pattern nutze, wobei ich damals schon (Data)Bindings eingesetzt habe.

Mein Core-Projekt (Model-Schicht mit Anwendungslogik, Datenhaltungsschicht) habe ich mittlerweile unter .NET Core 3.1 laufen.
Die WPF-Oberfläche wollte ich aus diversen Gründen mal neu machen und MVVM eine Chance geben.

Ich habe früher auf RoutedCommand gesetzt und mit InputBindings bzw. CommandBindings gearbeitet.

Beim MVVM Pattern nutzt man ja z.B. DelegateCommand/RelayCommand und legt die jeweiligen Commands im ViewModel an.
So sind z.B. im MVVM-Beispiel hier aus dem Forum folgende Zeilen im MainViewModel enthalten:

public ICommand AddNewEmployeeCommand { get; set; }
public ICommand RemoveEmployeeCommand { get; set; }

Angenommen man bräuchte im EmployeeViewModell aber auch noch Commands, hier mal als primitives Beispiel einfach ein HelloCommand:

public ICommand HelloCommand { get; set; }

public EmployeeViewModel()
{
	HelloCommand = new RelayCommand(HelloCommand_Execute);
}

private void HelloCommand_Execute(object obj)
{
	System.Diagnostics.Debug.WriteLine($"HelloCommand_Execute: Hello {FullName}");
}

In MVVM-Beispiel würde ich das aus dem MainWindow gerne per Button auslösen:

<Button Content="Hallo Mitarbeiter" 
		Command="{Binding Path=SelectedEmployee.HelloCommand}" 
		Margin="5,0,0,0"
		Background="LightYellow" />

Prinzipiell funktioniert das schon, wenn ein Mitarbeiter ausgewählt ist.
Wenn jedoch kein Mitarbeiter ausgewählt ist, wird der Button nicht ausgegraut, was nicht sonderlich schön ist.

Sicherlich wird der ein oder andere jetzt fragen, warum ich den HelloCommand nicht einfach im MainViewModel anlege (also alle Commands im MainViewModel).
Es gibt bei Anwendungen durchaus Commands wie Cut/Copy/Paste, die man in mehreren ViewModels braucht, aber unterschiedliche Umsetzungen benötigen.
Hätte man z.B. ein DocumentViewModel (sei mal dahingestellt, ob das für Texte, Zeichnungen ausgelegt ist) und ein SolutionExplorerViewModel, dann möchte man z.B. bei Copy bei einem aktiven Dokument eine Linie kopieren bzw. im aktiven SolutionExplorer eine Datei kopieren. Also je nach Fokus ist das Ziel des Commands ein anderes. Aber selbst wenn gar kein Dokument ausgewählt ist bzw. der SolutionExplorer nicht den Fokus hat, sollten die MenuItems, (Ribbon)Buttons, ... ausgegraut werden.

Man kann zwar ein CommandTarget setzen, aber ich sehe keine Änderung:

<Button Content="Hallo Mitarbeiter" 
		Command="{Binding Path=SelectedEmployee.HelloCommand}"
		CommandTarget="{Binding Path=SelectedEmployee}"
		Margin="5,0,0,0" />

Wie setzt man das richtig um, wenn man nicht nur im MainViewModell Commands hat?

14.05.2013 - 19:33 Uhr
<Button
        Height="{x:Static SystemParameters.IconHeight}"
        Width="{x:Static SystemParameters.IconWidth}"
        Content="{Binding Path=Height, RelativeSource={RelativeSource Self}}"
        />
07.01.2013 - 19:33 Uhr

Hallo herbivore,

danke für deine Antwort.

es liegt überhaupt nicht in der Zuständigkeit der SyncObservableCollection, den richtigen Dispatcher zu ermitteln/erzeugen. Diese Klasse kann überhaupt nicht wissen, welches der richtige Dispatcher ist. Im Grunde liegt es noch nicht mal in ihrer Zuständigkeit, überhaupt zu dispatchen. Wenn die Klasse aus Komfortgründen dispatchen soll, dann sollte der Benutzer der Klasse den richtigen Dispatcher übergeben, z.B. als Konstruktorparameter.

Also das mit dem Konstruktor-Parameter hatte ich mir auch schon gedacht und vermutet, dass das als Einwand kommen wird. Eigentlich finde ich den Dispatcher in der Collection einfach nur nervig. Der hat da nichts zu suchen. Am liebsten würden ich den komplett rausschmeißen, wäre da nicht WPF mit den Bindings, wenn man doch mal die Collection direkt in der View nutzen will (was in MVC erlaubt ist). Freilich könnte man eine Art Schicht (bzw. ViewModel) nochmal zwischenschalten, dort dann das CollectionChanged der Collection abfangen und die Steuerelemente bzw. eine ObservableCollection (die an Steuerelemente gebunden ist) dann über Dispatcher.Invoke befüllen.
Aber wenn man mit vielen Collections arbeitet, wird das im Code kein Spaß mehr (bzw. copy&paste). Ich frage mich mittlerweile ernsthaft, ob man eventuell ein Art Konverter bzw. eigene CollectionView zwischenschalten könnte, die sich um das Dispatchen kümmert.

Aber auch das alles wird in der FAQ behandelt, siehe den ersten Absatz in "Spezielle Probleme" und Eleganteste Art aus Worker-Thread auf Controls zugreifen [generell Kontrollfluss zwischen Threads]. Deshalb nochmal meine Bitte, das Thema Dispatcher nicht weiter zu behandeln, da alles notwendige in der FAQ steht. (Wenn du findest, dass was fehlt, nimm Kontakt zum Team auf.)

Hab ich mir durchgelesen, ist informativ. Hilft mir aber nur bedingt weiter. Werde das Forum am nächsten Wochenende nochmal durchforsten.

Was die Synchronisation bei Linq angeht, hast du ja erkannt, dass (mindestens) ein lock fehlt. Wenn ich es richtig sehe, möchtest du über alle alle Features über alle Teilnehmer iterieren. Dann müsstest du wohl auch alle Sync-Objekte aller Feature-Collections aller Teilnehmer sperren.

Und genau darauf bezieht meine Frage. Wie lassen sich die Sync-Objekte der Feature-Collections jeweils sperren?

So etwas in der Art meine ich:

var features =  linqLock(teilnehmer.SyncRoot)
                from t in teilnehmer
                linqLock(t.Features.SyncRoot)
                from f in t.Features
                select f;

foreach (Feature f in features)
{
    //Mach was...
}

Ich hab mal vorsichtshalber mit lock(...), sondern linqLock(...) geschrieben, damit man das nicht fehlinterpretiert.
Ich habe den Eindruck, dass LINQ für so etwas nicht ausgelegt ist und man doch wieder auf for/foreach zurückgreifen muss.

Oder eben doch mit Kopieren der Collections arbeiten.

Finde ich eine Notlösung, die ich so nicht akzeptieren kann/will (u.a. wegen Observer-Pattern).

Viele Grüße
Thomas

06.01.2013 - 18:02 Uhr

Hallo,

ich wollte hiermit vom aktuellen Status dieses genannten Problems berichten, falls jemand das Thema verfolgt:

Obwohl alles über Dispatcher.Invoke geht, konnte ich beim Öffnen von Fenstern mehrfach erkennen, das z.B. 3 Elemente in der ListView angegezeigt werden, obwohl in der Collection definitiv nur 2 vorkommen (ein Objekt wurde in der ListView mehrfach eingefügt, lässt sich auch an der doppelten blauen Selektierung erkennen).

Ich vermute das Problem gefunden zu haben. In meiner SyncObservableCollection<T> hole ich mir den Dispatcher mit Dispatcher.CurrentDispatcher:

public class SyncObservableCollection<T> : ObservableCollection<T>
{
    private Object syncRoot;
    private Dispatcher dispatcher;    

    public SyncObservableCollection()
    {
        syncRoot = new Object();
        dispatcher = Dispatcher.CurrentDispatcher;
    }

    //...

In der Model-Schicht habe ich so eine Art Hauptklasse (Singleton). Beim Starten der Anwendung wird alles vom UI-Thread initialisiert und auch diverse SyncObservableCollections werden angelegt. (Erst später greifen darauf andere Threads zu)
Es gibt natürlich teils auch Klassen, die wiederum eine bzw. mehrere SyncObservableCollections nutzen. Neue Instanzen hatte bisher aber immer der Benutzer über die Benutzeroberfläche (UI-Thread) erzeugt. D.h. auch der Konstruktor der SyncObservableCollection wurde vom UI-Thread aufgerufen.

Bei meinem ersten Beitrag hatte ich erwähnt:

Ein Gerät hat eine Liste aus Teilnehmer. Ein Teilnehmer hat eine Liste aus Features.

Genau hier scheint das Problem zu liegen, denn neue Teilnehmer werden von einem Task/Thread eingefügt, der mit der Hardware kommuniziert. Diese Teilnehmer-Klasse beinhaltet eine SyncObservableCollection mit Features, aber diese Collection wird dann nicht vom UI-Thread angelegt. Damit holt sich Dispatcher.CurrentDispatcher leider nicht den Dispatcher vom UI-Thread, was man an unterschiedlichen Thread-Ids "dispatcher.Thread.ManagedThreadId" erkennt.

Um das Problem zu beheben, hatte ich zunächst folgendes getestet:

public class SyncObservableCollection<T> : ObservableCollection<T>
{
    private Object syncRoot;
    private static Dispatcher dispatcher;    

    public SyncObservableCollection()
    {
        syncRoot = new Object();
        
        if(dispatcher == null)
            dispatcher = Dispatcher.CurrentDispatcher;
    }

    //...

Da ich mir sicher bin, dass der UI-Thread als erstes diesen Konstruktor aufruft, sollte der dispatcher somit danach immer richtig gesetzt sein.
Leider habe ich nicht beachtet, dass das eine generische Klasse ist und es mit der Klassenvariable so nicht funktioniert, denn die ist bei jedem Typ trotzdem anders. Ich habe mir nur mal als Workaround folgendes zusammengeschustert:

public static class SyncObservableCollectionDispatcherHelper
{
    private static Dispatcher dispatcher;
    public static Dispatcher Dispatcher
    {
        get
        {
            if (dispatcher == null)
                dispatcher = Dispatcher.CurrentDispatcher;

            return dispatcher;
        }
    }
}

public class SyncObservableCollection<T> : ObservableCollection<T>
{
    private Dispatcher dispatcher;
    private Object syncRoot = new Object();
        
    public SyncObservableCollection()
    {
        syncRoot = new Object();
        dispatcher = SyncObservableCollectionDispatcherHelper.Dispatcher;
    }

    //...

Zumindest ist jetzt der Dispatcher immer der des UI-Threads, vorausgesetzt man sorgt dafür, dass der Konstruktor der Collection beim ersten Mal vom UI-Thread aufgerufen wird.
Die Lösung ist deshalb eine gefährliche Sache.

Am liebsten wäre mir natürlich sowas in der Art:

public class SyncObservableCollection<T> : ObservableCollection<T>
{
    private Dispatcher dispatcher;
    private Object syncRoot = new Object();

    public SyncObservableCollection()
    {
        syncRoot = new Object();

        Thread uiThread = Thread.GetMainThread(); //pseudo
        dispatcher = Dispatcher.FromThread(uiThread);
    }

    //...

Leider hab ich noch keine passenden Hinweise im WWW gefunden, ob es eine einfache Möglichkeit gibt an den Hauptthread der Anwendung zu kommen.

Bzgl. LINQ wollte ich auch nochmal nachhaken:
Ich will einfach nicht überall in den Properties Kopien der Collections erstellen, sondern mit den "originalen" Collections (observable) arbeiten, um eventuell auch an manchen Stellen das CollectionChanged-Event zu registrieren.

Folgender schnell eingetippter Code als Beispiel:

public class Teilnehmer
{
    private SyncObservableCollection<Feature> features = new SyncObservableCollection<Feature>();
    public SyncObservableCollection<Feature> Features
    {
        get { return features; }
    }
}

public class Feature
{
    public String Name { get; set; }
}

public class Test
{
    private SyncObservableCollection<Teilnehmer> teilnehmer = new SyncObservableCollection<Teilnehmer>();

    public Test()
    {
        Task.Factory.StartNew(() => { TeilnehmerUndFeatureErzeugung(); });
        Task.Factory.StartNew(() => { MachWasMitForeach(); });  
        Task.Factory.StartNew(() => { MachWasMitLinq(); });
    }

    public void TeilnehmerUndFeatureErzeugung()
    {
        Random random = new Random();

        while(true)
        {
            Teilnehmer t = new Teilnehmer();
            teilnehmer.Add(t);

            for (int i = 0; i < random.Next(10); i++)
                t.Features.Add(new Feature { Name = String.Format("Feature {0}", i) });
        }
    }

    public void MachWasMitForeach()
    {
        while(true)
        {
            lock (teilnehmer.SyncRoot)
            {
                foreach (Teilnehmer t in teilnehmer)
                {
                    lock (t.Features.SyncRoot)
                    {
                        foreach (Feature f in t.Features)
                        {
                            //Mach was...
                        }
                    }
                }
            }
        }
    }

    public void MachWasMitLinq()
    {
        while (true)
        {
            var features = from t in teilnehmer
                            from f in t.Features      // where... 
                            select f;

            foreach (Feature f in features) //Das führt natürlich zu einer InvalidOperationException
            {
                //Mach was...
            }
        }
    }
}

Der Code-Teil mit dem foreach macht keinerlei Probleme. Den finde ich allerdings etwas unleserlich, besonders wenn man noch diverse if-Bedingungen einbaut.

Für solche Fälle hat mir LINQ schon immer ganz gut gefallen. Aber hier hat man es leider mit mehreren Threads zutun. Man könnte den Code-Teil mit einem lock(teilnehmer.SyncRoot) umschließen, jedoch fehlt dann noch ein lock von der Features-Collection.
Gibt es in LINQ keine Möglichkeit das thread safe zu gestalten?

Bitte berichtigt mich, wenn ich hier irgendwas falsch interpretiere. Vielleicht hat ja der ein oder andere noch ein paar Tipps über die ich ich mich sehr freuen würde.

Viele Grüße
Thomas

04.01.2013 - 17:37 Uhr

Guten Tag,

mag sein, dass in den Büchern nicht direkt steht, ob die laufende Verarbeitung unterbrochen wird, aber in den Büchern und im Netz steht genug, um sich die Abläufe selber zu erklären. Deshalb gehe ich hier nicht näher darauf ein, sondern verweise dich auf [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke). Da das GUI single-thtreaded arbeitet und demzufolge die Nachrichten sequentiell verarbeitet und Dispatcher.Invoke einfach eine Nachricht schickt, ergibt sich die Antwort in einem Schritt. Deshalb bitte keine weiteren Fragen zu diesem Aspekt.

Leider kann man sich nicht alle Aspekte selber erklären. Wenn das so wäre, bräuchte man sicherlich auch kein Forum. Vielleicht bin ich aber auch einfach nicht so schlau wie ihr.
Diese sequentielle Verarbeitung hatte ich mir bisher immer schon so gedacht, aber nach den Effekten die ich zuletzt erlebt habe, hat sich mein Verständnis in Luft aufgelöst.
Obwohl alles über Dispatcher.Invoke geht, konnte ich beim Öffnen von Fenstern mehrfach erkennen, das z.B. 3 Elemente in der ListView angegezeigt werden, obwohl in der Collection definitiv nur 2 vorkommen (ein Objekt wurde in der ListView mehrfach eingefügt, lässt sich auch an der doppelten blauen Selektierung erkennen).
Aber egal, es muss dann andere Gründe haben, denen ich nachgehen werde. Habe heute mit einem .NET-Entwickler über den UI-Thread und Dispatcher geredet. Daran sollte es ja eigentlich nicht liegen. Muss mal suchen, wo da der Wurm drin ist.

Ich frag mich aber: wenn Du Dir doch so unsicher in Deine Implementierung bist, wieso schaust Du Dir nicht eine der vielen vielen Implementierungen diesbezüglich an, die bereits existieren?

Hatte ich eigentlich schon im ersten Beitrag geschrieben, dass ich vor ca. 2 Jahren schon ein paar Implementierungen getestet hatte. Die meisten hatten das Problem, dass threadübergreifende Vorgänge problematisch waren, oder es nach außen kein SyncRoot-Object gab, das man für das Iterieren durch die Collection locken könnte. Auch hatte ich damals eine kleine Anwendung in WPF geschrieben, bei der ca. 20 Threads Listenoperationen durchführten. Ich kann mich nicht erinnern, dass das länger als ein paar Sekunden/Minuten gut ging und hatte dann selber noch etwas dran rumgebaut. Auch wenn ich selber nicht zufrieden war/bin, hat es mit der Collection bisher im 8h Betrieb keinerlei Probleme gegeben, wobei da die Listenänderungen meist vom Benutzer (UI-Thread) ausgegangen sind und die anderen Tasks größtenteils nur die Liste durchlaufen oder eventuell beim CollectionChanged-Event bestimmte Aktionen ausgeführt haben. Diese Konstellation hat sich etwas geändert und deshalb sind mir bei "Belastungstests" Deadlocks aufgefallen.
Daher dachte ich, dass es vielleicht Sinn macht hier mal im Forum nachzufragen, wie eine Implementierung solch einer Collection am sinnvollsten ist.

Gruß
Thomas

03.01.2013 - 19:26 Uhr

Hallo,

zunächst Danke für die Antworten.

Das ist aber falsch. Die Logik steckt bei MVC im Controller und nicht in den Modellen.

Ich kann nur jedem Entwickler raten, die eigentliche Logik der Software nicht in die Controller-Schicht zu packen.

gerade bei der Synchronisation von nebenläufigen Vorgängen und wegen mögliche Race Coditions erhält man nur eine sehr begrenzte Aussage. Auf einem anderen Rechner oder auch nach einen Betriebsystem-Update kann es ganz anders aussehen. Wo die Race Coditions vorher möglicherweise alle in die eine Richtung ausgegangen sind gehen sie dann möglicherweise öfter in die andere Richtung aus. Wo es vorher 8 Stunden ohne Probleme ging, knallt es nachher vielleicht alle fünf Minuten. Bei Synchronisierung sollte man wissen, was man tut. Einfach etwas auf Verdacht zu programmieren und nachher zu testen, ist keine gute Option. Siehe z.B. SyncQueue <T> - Eine praktische Job-Queue einen Fall, der sich durch einen einfachen Dauertest kaum finden lässt.

Jedenfalls ist es immer wichtig dass man sich genau überlegt was man macht. Und sich nicht nur auf Dauertests verlässt.

Ich gebe euch beiden vollkommen Recht. Mir wäre auch eine anständige Lösung am liebsten, wenn ich die kennen würde.
Ich sehe aktuell nur die hier genannten Ansätze über den Dispatcher, was sicherlich auch noch Ärger macht oder aber den genannte Kompromiss in .NET 4.5, den ich auch noch nicht komplett durchschaue.

Ich muss zu meiner Schande gestehen, dass mir meine ältere zusammengebastelte Lösung über den Dispatcher auch schon Kopfschmerzen bereitet hat, da ich nicht so wirklich weiß, wie der Dispatcher intern arbeitet.

Mal so rein theoretisch angenommen:
Wir haben eine Art ObservableCollection, deren Operationen über den Dispatcher laufen und die Collection zudem ein SyncRoot-Objekt (public property) zur Verfügung stellt, um auch von außerhalb ein lock(list.SyncRoot) durchführen zu können.
Es gibt ein MainWindow (WPF), mit einem Button. Dort kann der Benutzer weitere Fenster öffnen, das jeweils eine ListView beinhaltet. Die Collection wird an die ItemsSource-Eigenschaft übergeben (bzw. Binding).

Über den internen Ablauf der Steuerelemente habe ich bisher leider nicht so viele nützliche Hinweise finden können. Sicherlich muss durch die Collection iteriert werden, um die Items anzuzeigen. Das CollectionChanged-Event wird auch registriert, um spätere Änderungen mitzubekommen. Doch was passiert, wenn genau während dieses Vorgangs ein Thread beispielsweise Add aufruft. Es wird dann durch den Dispatcher an den UI-Thread weitergereicht.
Was macht dann der UI-Thread? Unterbricht er eventuell sogar die Iteration durch die Collection, was dann zu einer InvalidOperationException (Collection was modified) führt, oder registriert er das Event womöglich zu früh bzw. zu spät?
Sorry für diese Frage, aber ich konnte das in meinen Büchern bzw. Tutorials nirgends nachlesen und oftmals ist ja sogar noch eine ListCollectionView beteiligt.

Viele Grüße
Thomas
@Michael (michlG): Der Solar Inspector sieht interessant aus 👍

02.01.2013 - 21:21 Uhr

Guten Abend,

ich muss gestehen, dass ich die Antwort von Abt nicht so recht verstanden habe. Ich verwende in meinen meisten Anwendungen diverse Entwurfsmuster. Bezogen auf WPF setzte ich eine Art MVC ein, da ich mich mit MVVM bisher nicht recht anfreunden konnte. Business-Logik /DAL-Schicht zähle ich hier mit zur Model-Schicht.

Du erreichst mit Deiner Implementierung die Thread-Sicherheit auf den Typ T
Hat dieser Typ eine Property mit einer Collection und Du benötigst hier ebenfalls eine Thread-Sicherheit, so musst Du diese dort ebenfalls implementieren.

Ist mir nicht ganz klar, wie das gemeint ist. Man könnte natürlich in einer Property (teilnehmer.Features) für Thread-Sicherheit sorgen, indem man nur von dort auf eine interne Liste zugreift, diese lockt und nach außen nur eine Kopie (toList(), toArray(),...) zurückgibt. Damit dürfte das sogar mit LINQ threadsicher sein. Damit bekommt man Änderungen (CollectionChanged-Event) aber dann an anderen Stellen nicht mehr mit.

man muss man zwischen Threadsicherheit und der Vermeidung von unzulässigen threadübergreifenden Vorgängen unterscheiden. Das erste bedeutet, dass man aus mehreren Threads gleichzeitig auf gemeinsame Daten zugreifen darf, ohne dass dadurch Probleme entstehen. Das kann man z.B. durch lock o.ä. erreichen. Das zweite bedeutet, dass alle Zugriffe aus einem bestimmten Thread erfolgen müssen, damit es keine Probleme gibt, unabhängig davon, ob die Zugriffe gleichzeitig erfolgen würden oder nicht. Das kann man z.B. durch Dispatcher.Invoke erreichen.

So wie ich es verstehe, musst du beides sicherstellen.

Danke für die Erklärungen, wieder was gelernt. 👍
Ich denke, dass es in meinem Fall somit hauptsächlich threadübergreifende Vorgänge sind.

Ich habe in der Anwendung diverse Listen und da können zahlreiche Threads drauf zugreifen. Bisher war es oft so, dass Listenoperationen (Add, Remove,...) meist vom Benutzer (UI-Thread) ausgegangen sind und die Threads nur "gelesen" haben.
Diese Anforderung ändern sich jedoch etwas und besonders bei der Kommunikation mit der Hardware kann es vorkommen, dass Threads bzw. Events (z.B. SerialPort.DataReceived) in den Listen Änderungen vornehmen. Deshalb ist mir das mit den Deadlocks auch jetzt erst nach einigen Erweiterungen aufgefallen.

Hinzu kommt noch, dass bei Listenänderungen (CollectionChanged-Event) bestimmte Aktionen in der Model-Schicht von anderen Threads ausführt werden sollen. Deshalb dachte ich, dass eine ObservableCollection<T> da relativ gut passt. Manchmal kommt es halt vor, dass ich solch eine Liste auch mal in der View darstellen will. Besonders schön bei WPF sind natürlich die Bindings z.B. direkt an ItemsSource. Leider kommt WPF jedoch nicht damit klar, wenn das CollectionChanged-Event nicht vom UI-Thread gefeuert wird. Nur deshalb hatte ich damals den Kompromiss mit dem Dispatcher gemacht und dachte nun bei .NET 4.5, dass bei BindingOperations.EnableCollectionSynchronization diese Probleme der Vergangenheit angehören. Jedoch steige ich noch nicht so recht durch, was da genau passiert. Ich wollte mir nicht die Arbeit machen und den .NET Code studieren bzw. eigene Datenstrukturen überlegen. Für ein Privatprojekt ist mir das einfach zu viel Aufwand.

Eine echte threadsichere Liste (observable) kenne ich nicht. Die in .NET 4 eingeführten Thread-safe Collections sind zwar oftmals hilfreich, aber eher als Puffer zu gebrauchen (FIFO, LIFO).

Ich werde eventuell doch mal mit meiner o.g. TestObservableCollection<T> einen Dauertest durchführen. Wenn das ca. 8h ohne Absturz läuft, dann wäre ich schon zufrieden.

Gruß
Thomas

01.01.2013 - 14:29 Uhr

Hallo C#-Profis,

zunächst wünsche ich hiermit ein gesundes und erfolgreiches neues Jahr 2013.

Ich habe schon länger ein paar Verständnisprobleme mit Threads, Tasks, ObservableCollections (auch in Verbindung mit WPF Bindings), LINQ und wollte deshalb hier im Forum mal bei euch nachfragen.

Ich habe eine Software, die viel asynchron mit der Hardware (Geräten) kommuniziert (z.B. RS232, USB, Ethernet, CAN,...).
Ich verwendet u.a. Threads, BackgroundWorker und mittlerweile am liebsten Tasks. Die Kommunikation selber ist kein Problem, da das meist nach dem Schema Producer-Consumer abläuft.
Ich hatte dafür früher die SyncQueue<T> von herbivore genutzt. Aktuell verwende ich in .NET 4 die BlockingCollection<T>.

Es kommt jetzt jedoch noch ein anderer Bereich hinzu, den ich hier als Beispiel nur vereinfacht darstelle:
Bei der Kommunikation mit den Geräten werden bestimmte Teilnehmer gemeldet und jeder Teilnehmer kann bestimmte Features aufweisen, die er unterstützt.
Kurz: Ein Gerät hat eine Liste aus Teilnehmer. Ein Teilnehmer hat eine Liste aus Features.

Während das Gerät verbunden ist, können jederzeit Teilnehmer oder sogar deren Features wegfallen oder neu hinzukommen.
Solche Änderungen müssen in der Software andere Einheiten mitbekommen und auch der Benutzer, der sich vielleicht gerade die Features eines Teilnehmers ansieht. Deshalb brauche ich dafür ObservableCollections die threadsicher sind.
Vor ca. 2 Jahren hatte ich mit SynchronizedObservableCollection, SafeObservableCollection, ThreadSafeObservableCollection Tests gemacht und letztlich mir einen Verschnitt daraus zusammengebastelt, wobei alle Listenoperationen mittels Dispatcher.Invoke an den UI-Thread weitergereicht werden, sodass auch das NotifyCollectionChanged-Event keine Probleme bei Bindings und WPF macht. Zudem wird intern ein syncRoot-Object gelockt. Auf dies kann man von außen (Property) zugreifen, z.B. wenn man durch die Liste iterieren will (foreach etc.).
Bisher funktionierte das glücklicherweise im Dauerbetrieb (ca. 8h). Neulich kamen aber noch ein paar Tasks dazu und ich müsste dann lange auf Fehlersuche gehen, da es immer mal wieder zu Deadlocks kam.

Der Grund so nebenbei: Ein Task sollte ein Feature durch ein anderes ersetzen und hat zunächst die Liste gelockt, mit Contains nach dem Feature gesucht, es mit Remove gelöscht und eine anderes mit Add hinzugefügt. Da die Listeoperationen jedoch intern mit Dispatcher.Invoke verarbeitet werden, versuchte der UI-Thread ebenfalls die Liste zu locken. Task und UI-Thread haben sich gegenseitig blockiert. Das asynchrone Dispatcher.BeginInvoke führte leider zu anderen Problemen.

Mir wäre es lieber, wenn die Listenoperationen direkt vom jeweiligen Thread/Task ausgeführt werden und nicht über den Dispatcher laufen.
Ich will eventuell auf .NET 4.5 umsteigen und mache deshalb gerade Tests mit der ObservableCollection<T> und BindingOperations.EnableCollectionSynchronization.

Habe mir jetzt mal eine total einfache TestObservableCollection<T> erstellt:

public class TestObservableCollection<T> : ObservableCollection<T>
{
	private Object syncRoot;

	public TestObservableCollection() : base()
	{
		syncRoot = ((ICollection)this).SyncRoot;
		BindingOperations.EnableCollectionSynchronization(this, syncRoot);
	}

	public Object SyncRoot
	{
		get { return syncRoot; }
	}
}

In WPF habe ich eine ListView und DataGrid (CanUserAddRows=True) verwendet und daran die Listen gebunden (zudem lassen sich mehrere Fenster öffnen). Es laufen mehrere Tasks, die Listenoperationen durchführen bzw. durch die Liste iterieren, wobei natürlich das SyncRoot-Objekt gelockt wird.
Bisher lief das reibungslos, aber ich wollte trotzdem mal nachfragen, ob das so Sinn macht, bevor ich das in eine Anwendung einbaue?

Es gibts dann noch so eine Sache mit LINQ. Ich habe LINQ wirklich schätzen gelernt und will das eigentlich nicht mehr missen, aber mir ist das mit der Threadsicherheit nicht klar.
Pseudocode:

public void MachWas(Gerät gerät)
{
	var featureNames = from teilnehmer in gerät.Teilnehmer
			           from feature in teilnehmer.Features
			           where feature.Id < 10 && feature.Status == FeatureStatus.On
			           select feature.Name;
			   
	foreach(String featureName in featureNames)
		//mach was...
}

Wie ich das mit zwei foreach-Schleifen threadsicher machen könnte, ist mir klar. Aber wie funktioniert das in LINQ? Wo baut man dort die lock-Anweisungen ein? Leider spuckt das WWW dazu nichts aus oder ich verwende die falschen Suchbegriffe.

Ich würde mich über eure Tipps freuen!

Viele Grüße
Thomas

14.08.2012 - 19:50 Uhr

Hallo,

das DataReceived-Event wird nicht vom UI-Thread ausgelöst. Somit macht dein Zugriff auf die textBox1 Probleme.

Ich bin zwar kein Windows.Forms Profi, aber Suche mal nach den Stichworten Invoke, BeginInvoke.

21.03.2012 - 22:07 Uhr

In meinen Anwendungen nutze ich auch schon lange die SerialPort-Klasse für diverse Hardwareansteuerungen (teils 8 Stunden Dauerbetrieb). Bisher hatte ich mit dem DataReceived-Event noch keine Probleme und alle Bytes sind angekommen.
Da die Bytes blockweise ankommen, lese ich diese aus und füge sie in eine Liste/Queue (Instanzvariable) hinzu. Danach mache ich die Auswertung, sodass die ankommenden Daten als Nachricht interpretiert werden.

Entscheidend ist natürlich auch das Protokoll/die Befehlsbeschreibung. Meist hatte ich das Glück, dass ein eindeutiger Delimiter/Separator (z.B. 0xFF) definiert ist und z.B. das nachfolgende Byte die Länge der Datenbytes angibt. So geht die Auswertung wunderbar, selbst wenn von einer Nachricht erstmal nur ein paar Bytes ankommen.

Auf eine bestimmte Länge warten. Hmmm... sowas ähnliches hatte ich neulich auch mal.
Da war das Protokoll sehr simpel aufgebaut. Es legte nur fest, dass eine Nachricht 13 Bytes lang ist. D.h. also im DataReceived-Event wiederum mit einer Liste/Queue (Instanzvariable) arbeiten. Bei der Auswertung dann jeweils 13 Bytes als Nachricht interpretieren, solange die Listenlänge ≥13 ist.
Das war auch ein Gerät, das ungefragt Nachrichten an den PC sendet (also kein Polling bzw. Request/Response).

Eine Sorge habe ich allerdings noch heute: Bei Geräten (RS232 bzw. USB via VCP) sind vermutlich diverse Puffer im Hintergrund.
Solch eine Auswertung mit dem Zählen von Bytes würde total ins Hemd gehen, wenn nach serialPort.Open() im Empfangspuffer noch "Müll-Bytes" z.B. von der letzten Verbindung bestehen. Dann kommt das Zählen total durcheinander. Mir ist das noch nicht passiert und auch andere Kollegen konnten mir keine Auskunft darüber geben.
Jemand hat mir mal empfohlen nach dem serialPort.Open() einfach mit Read() den Empfangspuffer zu leeren. Meiner Ansicht nach, kann das aber auch nach hinten losgehen, wenn da das Gerät schon mit dem Senden angefangen hat und man womöglich relevante Bytes ins Nirwana wirft.

28.02.2012 - 21:42 Uhr

Ich möchte ein Programm entwickeln welches sich unten Rechts in der Taskbar minimiert und im Hintergrund läuft. Es soll sich automatisch zu einem Webserver verbinden und dort den Text der in einer .txt oder ähnlichem steht auf dem PC anzeigen. Meine Frage ist nun... wo fange ich an?

Jeder Programmierer wird sich da seine eigenen Gedanken drüber machen, wie er die gestellte Aufgabe am besten mit seinem aktuellen Wissensstand löst. Ansonsten muss man sich weiteres Wissen aneignen (Bücher, WWW, ...) 🤔

Spontane Idee: WPF-Anwendung, ShowInTaskbar="False", NotifyIcon, WebClient...

28.02.2012 - 21:17 Uhr

Studienkollege wollte bei Projekt auf Nummer sicher gehen. Bei bool weiß man ja nie 😁

bool isAktiv = //....
if (isAktiv)
{
    //Mach was
}
else if (!isAktiv)
{
    //Mach was anderes
}
28.02.2012 - 20:40 Uhr

Hallo Jürgen,

ich würde aus Sicherheit trotzdem mal nachschauen, eventuell wurde das doch verstellt.

Habe mal kurz im Internet recherchiert. Drück doch mal:
[WINDOWS-LOGO] + [R]

Und dann das hier ausführen:
shell:::{80F3F1D5-FECA-45F3-BC32-752C152E456E}

Habs gerade selber getestet und ein Bild angehängt, wie das eingestellt sein muss. Wenn das nicht hilft, weiß ich leider auch nicht weiter.

Gruß
Thomas

27.02.2012 - 15:47 Uhr

Guddn Tach,

ich habe nicht gesagt, dass man sich die Zeit als DateTime merken soll. 🙂 Man muss das natürlich in geeigneter Weise tun. Vermutlich bekommt man es mit Environment.TickCount hin.

Finde ich für meinen Fall zu kompliziert und da stellen sich mir gleich wieder neue Fragen. Ich bleibe lieber in dieser Anwendung beim KISS-Prinzip.

Der optimale Wert hängt von der Granularität Deiner Aktionszeitpunkte und von der gewünschten Genauigkeit ab.

Genau auch aus diesem Grund bleibe ich erstmal bei der Lösung von winSharp93. So genau muss das nicht sein, nur ungefähr. Wichtig ist nur, dass der SchalteAus-Befehl gesendet wird, sonst brennt u. U. eine Spule durch. Ein paar ms hin oder her sind egal und Realtime wird das mit C# und Managed Code eh nie werden.

[Ironie]Wenn ich mal viiiiiiiiiiiieeeeeeeeel Zeit habe, setze ich ein Gentoo mit Linux Kernel 3 und RT- bzw. RTAI-Patch auf. Dann übergebe ich die Aufgabe per XML/JSON übers Netzwerk an ein Embedded System und dort wird das An- und Abschalten genaustens mittels Realtime Clock ausgelöst.[/Ironie] 😉

Außerdem muss man die Genauigkeit der Timer selbst (~55ms, außer beim Multimediatimer) berücksichtigen. Und die Dauer der Aktionen ist auch nicht ganz unwichtig, weil z. B. System.Threading.Timer auch auslöst, wenn der letzte noch nicht fertig ist.

Hört sich interessant an, werde ich mir mal mit dem Multimediatimer merken. Eventuell braucht man das mal bei einer anderen Anwendung.

Gruß
Thomas

26.02.2012 - 21:22 Uhr

Ahh, das ist bei mir auch der Fall 🙂 Hab mein Tablet schon so lange das ichs gar nicht mehr anders kenne, daher habe ich auch angenommen es ist das Standardverhalten.

Aber ein Grund mehr das nicht zu ändern. Die gewünschten Einstellungen des Users sollte man schon berücksichtigen.

hehe... Also ich bin am Anfang erschrocken, so ungefähr "Shit, wie stellt man den Käse wieder um". 😁
Den MultiTouch-Rahmen hatte ich nur für Testzwecke angeschlossen. Bei mir ist es auch nur ein Desktop-Rechner, den ich zu 99% mit der Maus bediene.

26.02.2012 - 21:03 Uhr

Hallo Jürgen,

eventuell verstehe ich dein Problem auch falsch, aber ist das wirklich nur in deiner Anwendung?

Ich habe aus Testzwecken einen MultiTouch-Rahmen. Als ich die Treiber vor einiger Zeit installiert hatte, gingen nach Neustart alle Menüs so auf, wie in deinem Bild.
Das lag daran, dass das für Rechtshänder eingestellt wird, sodass man besser mit dem Stift die Menüs auswählen kann.

Wenn der TouchScreen eingesteckt ist, kann man das in der Systemsteuerung unter "Tablet PC-Einstellungen" wieder auf "Linkshändig" einstellen und dann ist alles wieder normal.
Es gibt aber auch einen Registry-Schlüssel, wenn ich mich richtig erinnere.

Gruß
Thomas

26.02.2012 - 20:37 Uhr

Der Code würde nicht mal kompilieren, da Du in button2_Click auf ein Timer-Objekt zugreifen willst, das es gar nicht gibt.

Ich habe zwar nicht viel Ahnung von Windows.Forms, aber der Code kompiliert bei ihm bestimmt. Wie es Chris360 richtig erwähnt, wurde da sehr wahrscheinlich ein Timer im Designer reingezogen. Der nennt sich dann timer1 und ist vermutlich auch noch auf Enabled gesetzt.

Der timer1 in button1_Click bringt in der Tat gar nichts 😉

trip2137 mach dir halt einfach mal eine Consolen/Debug-Ausgabe in timer1_Tick mit Zeitausgabe rein. Der eine Timer läuft bestimmt sofort los, wenn du die Anwendung startest.

26.02.2012 - 15:49 Uhr
  
Timer timer = null;  
timer = new //...  
  

und schon kannst du in der anonymen Methode auf den Timer zugreifen.

Ok, super! Stimmt, darum hat es bei meinem Test vorhin nicht funktioniert, da ich es nicht auf null gesetzt habe.

Funktioniert auch wunderbar, "Schalte aus" ist bei impuls > 0 jetzt immer brav da.
Vielen Dank! Dieses Thema ist auf meiner TODO-Liste somit erstmal erledigt 👍

Man kann aber keine Threads "entziehen".
Etwas in die Richtung ist nur mit .ContinueWith bzw. .ContinueWhenAll möglich (in diesem Kontext aber nicht sinnvoll einsetzbar). Aber auch da verzichtet der Code quasi frewillig auf den Thread.

Interessant. Ich nutze statt eines Background-Threads, BackgroundWorker und co neuerdings gerne mal einen Task, u.a. auch wegen .ContinueWith, CancellationTokenSource und der einfacheren Handhabung. Bei .ContinueWith ist mir nur mal aufgefallen, dass dies oftmals auch ein anderer Thread aufruft (Ausgabe von Thread.CurrentThread.ManagedThreadId) und manchmal auch der Gleiche.

26.02.2012 - 12:08 Uhr

Hallo,

danke erstmal an herbivore für die Erklärung der Task, klingt plausibel.

Ich habe eigentlich vermutet, dass die TPL so intelligent ist, dass einem Task, der längere Zeit durch Thread.Sleep, WaitHandle (ManualResetEvent, AutoResetEvent) blockiert werden, der Thread entzogen wird. Der Thread könnte dann für andere Aufgaben/Tasks genutzt werden.
Leider steht in meinen älteren C#-Büchern nichts drin. Im Internet findet man zwar viel über die Programmierung, aber was im Hintergrund passiert, bleibt offen. Im Openbook Visual C# 2010 Kapitel 11.3 ist es auch nur allgemein formuliert.

Die Lösung von winSharp93 verstehe ich ungefähr so:


private List<Timer> timerList = new List<Timer>();
public void Aktiviere(int impuls)
{
    SchalteAn();

    if (impuls > 0)
    {
        Timer timer = new System.Threading.Timer(t => 
        { 
            SchalteAus();

            //lock (((ICollection)timerList).SyncRoot)
            //    timerList.Remove(timer);

            //timer.Dispose();
        }, null, Timeout.Infinite, Timeout.Infinite);
                
        lock(((ICollection)timerList).SyncRoot)
            timerList.Add(timer);
                
        timer.Change(impuls, Timeout.Infinite); //Manueller Timer-Start
    }
}

Wobei das mit dem Löschen und Dispose noch so eine Sache ist (auskommentiert). Bin gerade überfragt, wie ich einen Verweis auf den Timer am einfachsten übergeben kann.

Zur Lösung mit nur einem Timer von ujr und herbivore:
Verstehe ich euch da richtig? Einen Timer laufen lassen, der z.B. jede 10ms eine Methode aufruft. Die überprüft dann, ob es in einer Liste Einträge gibt (z.B. Aus-Befehl mit TimeStamp), deren Zeit abgelaufen ist. Ist das der Fall wird ein entsprechender Aktion ausgeführt und der Aus-Befehl aus der Liste gelöscht.
Nur in dem Fall frage ich mich was der optimale Wert für das Timer-Intervall ist.

Einfache Dinge können so kompliziert sein... uff...
Mein innerer Timer sagt jetzt erstmal Essenszeit 🙂

Gruß
Thomas

25.02.2012 - 21:28 Uhr

Guten Abend,

Ist aber eine ganz schlechte Idee, da es ziemlich viele Resourcen zieht, lauter sleepende Threads in der eigenen Anwendung zu haben.
Evtl. führt das letztlich sogar dazu, dass dein kompletter TaskScheduler blockiert und letztlich die Wartezeiten überhaupt nicht mehr stimmen.

Ich muss gestehen, dass ich mir darüber noch keine Gedanken gemacht habe. Gut dieses Problem hatte ich auch noch nie. Normalerweise nutze ich einen Timer, um Dinge zyklisch auszuführen und da hat man dann natürlich einen Verweis drauf.

Theoretisch angenommen man hat 1000 Tasks, die mittels Thread.Sleep, WaitHandle (ManualResetEvent, AutoResetEvent) blockiert werden. Heißt das jetzt, dass auch tatsächlich 1000 Threads blockieren?

Aber gut - eine Liste wäre wohl zu einfach.

Um das gehts mir eigentlich gar nicht. Ich versuche nur von vornherein eine Software so zu programmieren, dass die verwendeten Listen auch einen Sinn für die eigentliche Anwendung ergeben. In diesem Fall pfeift aber irgendwie mein innerer Schweinehund, eventuell mal wieder unbegründet. 😁
Aber wenn das mit einem Task wirklich nicht vertretbar ist und es keine andere Möglichkeit gibt, dass man eine Methode verzögert ausführen kann, dann werde ich das so machen müssen.

Gruß
Thomas

25.02.2012 - 13:08 Uhr

hmmm... irgendwie hatte ich es geahnt, dass das mit der Liste kommt. Nagut, werde ich mir mal überlegen. Eventuell lasse ich auch den Task.
Trotzdem Danke! 😉

25.02.2012 - 12:01 Uhr

Hallo winSharp93,

du musst den Timer in einer Instanzvariable speichern, da er sonst vom GC entsorgt wird.

Die Instanzvariable würde dann aber bei jedem Aufruf von Aktiviere(200) wieder überschrieben werden. D.h. nur auf den letzten erzeugten Timer hat man dann durch die Instanzvariable einen Verweis.
Wenn ich das richtig verstehe, wäre das sogar noch schlimmer, da so ein Fehler noch schwieriger zu finden ist.

Vielleicht gibt es ja noch andere Ideen.

Gruß
Thomas

25.02.2012 - 11:30 Uhr

Hallo C#ler,

ich habe eine Methode, die von diversen anderen Threads aufgerufen werden kann. Diese soll etwas anschalten. Ist eine Impulsdauer angegeben, soll es automatisch wieder ausgeschaltet werden. Der Aufrufer darf aber nicht blockiert werden (z.B. durch Thread.Sleep(impuls)).

Das Problem habe ich mal in eine separate Testanwendung gepackt:


/// <summary>
/// Aktiviert irgendwas
/// </summary>
/// <param name="impuls">Impuls in ms für automatische Abschaltung</param>
public void Aktiviere(int impuls)
{
    SchalteAn();

    if (impuls > 0)
        new System.Threading.Timer(t => { SchalteAus(); }, null, impuls, Timeout.Infinite);
}

private void SchalteAn()
{
    Debug.WriteLine(DateTime.Now.ToString("mm:ss.fff") + " Schalte an");
}

private void SchalteAus()
{
    Debug.WriteLine(DateTime.Now.ToString("mm:ss.fff") + " Schalte aus");
}

Ich habe jetzt mehrfach in der Testanwendung Aktiviere(200) aufgerufen und festgestellt, dass nicht immer die Ausgabe "Schalte aus" erscheint.
Meine Vermutung ist, dass mir da der Garbage Collector einen Strich durch die Rechnung macht und meinen Timer freigibt, da kein Verweis darauf vorhanden ist. Laut MSDN scheint meine Vermutung zu stimmen.

Ersetzt man den Timer durch einen Task, funktioniert es scheinbar immer:


Task.Factory.StartNew(() =>
{
    Thread.Sleep(impuls);
    SchalteAus();
});

Macht das mit dem Task Sinn, oder gibt es beim Timer irgendeinen Trick, dass das immer funktioniert?
Gibt es eventuell eine andere/bessere Lösung, eine Methode verzögert auszuführen?

Gruß
Thomas

Edit: [erledigt]

10.02.2012 - 16:51 Uhr

Hallo,

hat wirklich niemand einen Tipp für mich ?(

Eventuell einfachere Frage:
Gibt es irgendeine Möglichkeit einmal eine Methode aufrufen zulassen, wenn der Benutzer X, Alt+F4, Datei->Beenden oder in Taskleiste "Fenster schließen" aufruft (abgesehen von Prozess beenden)?
ApplicationCommands.Exit wird leider auch meist nicht ausgelöst.

Gruß
Thomas

06.02.2012 - 17:36 Uhr

Hallo,

das mit dem Singleton habe ich sogar schon für was anderes drin. 😉
Eine weitere Collection ist natürlich kein Problem, in der ich dann selber die geöffneten Hauptfenster verwalte.
Im MainWindow-Konstruktor könnte ich dann folgendes einbauen:

View.Instance.MainWindowList.Add(this);

In OnClosing oder OnClosed zudem:

View.Instance.MainWindowList.Remove(this);

Ich will jetzt nichts falsches sagen, aber ich glaube das kommt aufs gleiche hinaus, wie bei Application.Current.Windows. Wenn die OnExit-Methode in der App.xaml.cs aufgerufen wird, sind die einzelnen OnClosing der Hauptfenster bereits ausgeführt worden. Somit ist die MainWindowList wieder leer.

Hatte schon ein paar andere Versuche, bisher hat mir aber kein Ansatz gefallen. Ich will eigentlich nur, dass die Eigenschaften (Position, Fensterstatus,...) mehrerer Hauptfenster gespeichert werden, sodass beim Start wieder genau so viele Hauptfenster geöffnet werden (Multi-Head), wie der Benutzer zuletzt geöffnet hatte.

Das sollte auch unabhängig davon sein, was der Benutzer verwendet (X, Alt+F4, Datei->Beenden). Bei mehr als einem Hauptfenster würde ich noch gerne einen Dialog haben, damit man nur das Fenster oder die ganze Anwendung schließen kann:

//Ungetestet
if (View.Instance.MainWindowList.Count > 1)
{
    MessageBoxResult result = MessageBox.Show("Soll die gesamte Anwendung beendet werden?", "", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);

    switch (result)
    {
        case MessageBoxResult.Yes: //Anwendung beenden
            Application.Current.Shutdown();
            break;
        case MessageBoxResult.No: //Nur dieses Fenster schließen
            this.Close();
            break;
        case MessageBoxResult.Cancel:
            Debug.WriteLine("Der Benutzer will doch nichts schließen");
            break;
    }
}
else 
{
    //Wenn nur dieses Fenster offen, einfach schließen. (ShutdownMode==OnLastWindowClose)
    this.Close();
}

Problem ist nur, wer das aufrufen soll. Bei Datei->Beenden kein Problem. Aber für X, Alt+F4 finde ich nur das Abfangen mit OnClosing. Das würde dann aber für jedes Fenster aufgerufen werden und nicht einmal.

Ich sehe momentan keine einfache Lösung.

Gruß
Thomas

06.02.2012 - 15:16 Uhr

Hallo C#ler,

bisher hatte ich immer in den WPF-Anwendungen ein Hauptfenster. In der überschriebenen Methode OnClosing habe ich die relevanten Eigenschaften in den Settings gesichert, um u.a. die Fensterposition beim nächsten Start wiederherzustellen.

In einer anderen WPF-Anwendung erlaube ich dem Benutzer mehrere Hauptfenster zu öffnen.
Ist nur ein Fenster aktiv und man drückt X, Alt+F4 oder Datei->Beenden so ist das mit OnClosing kein Problem.

Bei mehreren geöffneten Hauptfenstern wäre es gut, wenn ein Dialog erscheint, was man machen möchte (Nur dieses Fenster schließen oder gesamte Anwendung beenden).
Möchte man die gesamte Anwendung beenden, so dachte ich, dass ich die OnExit-Methode in der App.xaml.cs überschreibe und darin die relevanten Werte aller Fenster sichere. Leider ist dort dann aber die Anzahl der Fenster schon 0:

Application.Current.Windows.OfType<MainWindow>()

OnClosing der einzelnen Windows bringt mir auch nichts, da die unabhängig voneinander sind.
Eine Idee ist es nach dem Aufrufen von X, Alt+F4 oder Datei->Beenden eine eigene Methode (z.B. Close) zum Beenden in der App.xaml.cs aufzurufen. Darin wird erstmal alles gesichert und erst danach erfolgt der Shutdown-Befehl.
Bei Datei->Beenden ist es kein großes Problem, dort dann aufzurufen:

((App)Application.Current).Close();

Das wird dann genau einmal aufgerufen. Danach kann dann alles beendet werden. OnClosing der einzelnen Fenster sind somit irrelevant.

Aber wie sieht es bei X und Alt+F4 aus? Gibt es da eine Möglichkeit das einfach abzufangen und einmal die Close-Methode aufzurufen?

Gruß
Thomas

09.10.2011 - 15:04 Uhr

Hallo,

erstmal vielen Dank für eure Hilfe.

Also ich stelle mal die behauptung auf dass es in nem Thread zu einer Exception kommt.
Kannst ja mal versuchen das
>
(WPF) oder
>
(WinForms) zu abonnieren.

Das es evt. in einem Thread zu einer Exception kommt, habe ich auch schon in Erwägung gezogen. AppDomain.UnhandledException kannte ich noch gar nicht, hatte bisher in WPF-Anwendungen nur DispatcherUnhandledException abgefangen (App.xaml.cs).

protected override void OnStartup(StartupEventArgs e)
{
	this.DispatcherUnhandledException += new App_DispatcherUnhandledException;
	AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}

void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
	Debug.WriteLine("App_DispatcherUnhandledException: " + e.Exception.Message);
}

void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
	Exception ex = (Exception)e.ExceptionObject;
	Debug.WriteLine("CurrentDomain_UnhandledException: " + ex.Message);
	Debug.WriteLine(ex.StackTrace);
}

CurrentDomain.UnhandledException werde ich mir in Zukunft merken, um Fehler schneller zu finden. 👍

aktiviere mal in VS alle Exceptionbreakpoints (via Debug-Exception); also auch die für C++ und COM-Exceptions.
Dann solltest du sehen, wo genau die Exception auftritt bzw. um welche es sich handelt (und wie diese weitergereicht wird).

Wieder was gelernt. Ich habe immer in "Debuggen->Optionen und Einstellungen..." nach Einstellungen gesucht. In "Debuggen->Ausnahmen..." habe ich jetzt mal alles aktiviert und siehe da, der Debugger war gleich viel gesprächiger. 😉

Der Fehler tritt anscheinend wirklich nur auf, wenn die Bluetooth Verbindung abreißt und man einen Befehl von PC an die Wiimote sendet. Danach wird an einer anderen Stelle beim asynchronen Lesen eines Streams eine IOException geworfen, jedoch nirgends angefangen.

Das müsste dann sogar der Fehler http://wiimotelib.codeplex.com/workitem/7980 hier sein.
Nur die Lösung gefällt mir nicht... Einfach nur ein catch-Block mit Debug-Ausgabe macht wenig Sinn. Am schönsten wäre natürlich, wenn bei wm.SetRumble(isChecked) die IOException geworfen wird. Wird vermutlich durch den asynchronen Lesevorgang schwierig.

Habe sowas in diesem Zusammenhang noch nie selber programmiert, aber evt. bietet sich hier ein Exception-Event in der Klasse Wiimote an (z.B. wm.ErrorReceived), so nach dem Schema wie bei serialport.ErrorReceived!?

Gruß
Thomas

06.10.2011 - 19:00 Uhr

Hallo,

erstmal danke für die schnelle Antwort.

Das die Exception bis nach außen, also aus der Bibliothek heraus geworfen wird ist so auch vollkommen richtig. 🙂 - Schließlich bist du der, der darauf reagieren muss durch z.B. erneuten Verbindungsaufbau.

Das ist leider das Problem. Es gibt keine Exception. Der (Debug)Prozess raucht einfach ab und somit ist die Anwendung weg.

try
{
    bool isChecked = (bool)this.RumbleToggleButton.IsChecked;
    wm.SetRumble(isChecked);
}
catch(Exception ex)
{
    MessageBox.Show(ex.Message, "Wiimote error", MessageBoxButton.OK, MessageBoxImage.Error);
}

Ich komme leider NIE in den catch-Block. Warum auch immer.

Ich bin sogar bei einem anderen Test noch weiter gegangen und habe direkt in der Lib um den Aufruf von HidD_SetOutputReport eine try-catch-Anweisung gemacht.

try
{
    HIDImports.HidD_SetOutputReport(this.mHandle.DangerousGetHandle(), buff, (uint)buff.Length);
}
catch (Exception ex)
{
    Debug.WriteLine("Fehler wurde abgefangen: " + ex.Message);
}

Doch selbst da landet man leider NIE im catch-Block.

Meiner Ansicht nach wird in diesem Fall keine Exception aus dem nativen Code geworfen, sondern der reißt meinen Prozess der Anwendung mit ins Grab.
Ich würde JUHU schreiben wenn ich mal eine Exception abfangen könnte, denn dann könnte ich das behandeln 😉.

Über weitere Tipps würde ich mich freuen!

Gruß
Thomas

06.10.2011 - 18:36 Uhr

Hallo C#ler,

ich nutze in meiner Software die WiimoteLib, um diverse Funktionen per Wiimote zu steuern.
Das klappt auch sehr gut und funktioniert stundenlang ohne Probleme.

Wenn die Bluetooth Verbindung jedoch abreißt, dann kommt es zu Problemen, falls Daten vom PC an die Wiimote gesendet werden sollen.
Erst dachte ich, dass der Fehler in meiner Software (WPF-Anwendung, .NET 4) liegt und darum habe ich eine kleine WPF-Testanwendung erstellt.
Diese ist recht einfach aufgebaut und zeigt bei einer verbundenen Wiimote einen ToggleButton an, mit dem man den Vibrationseffekt an und ausschalten kann.

private void RumbleToggleButton_Click(object sender, RoutedEventArgs e)
{
    Wiimote wm = this.DataContext as Wiimote;

    if (wm != null)
    {
        try
        {
            bool isChecked = (bool)this.RumbleToggleButton.IsChecked;
            wm.SetRumble(isChecked);
        }
        catch(Exception ex)
        {
            MessageBox.Show(ex.Message, "Wiimote Error", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }
}

Anfangs hatte ich keine try-catch-Anweisung. Reißt die Verbindung ab (z.B. Akkus raus) und man klickt erneut auf den Button, so schmiert der Prozess einfach ab.
Auch die nachher eingebaute try-catch-Anweisung bringt keine Änderung. Der Prozess der Anwendung ist einfach weg. Selbst im Debug-Modus von Visual Studio 2010 keinerlei Exception.

Da ich bisher vom Entwickler der Lib keine Lösung habe, bin ich selber mal den Quellcode durch und nach vielen Versuchen der Auffassung, dass die entscheidende Zeile folgendermaßen lautet:

HIDImports.HidD_SetOutputReport(this.mHandle.DangerousGetHandle(), buff, (uint)buff.Length);

Wobei HidD_SetOutputReport ein DLL-Import ist

[DllImport("hid.dll")]
internal extern static bool HidD_SetOutputReport(IntPtr HidDeviceObject, byte[] lpReportBuffer, uint ReportBufferLength);

Leider habe ich noch nicht so die Erfahrung mit nativen Codeaufrufen bzgl. Exception Handling im Managed Code. Jemand meinte das es vielleicht an der CLR liegt, da die WiimoteLib mit .Net Framework 2.0 kompiliert ist.
Aus Sicherheit habe ich die Lib auch schon mit .Net Framework 4 Client Profile kompiliert, kam jedoch zum selben Ergebnis.

Es muss sichergestellt sein, dass die Anwendung weiter läuft, auch wenn mal eine Wiimote streikt.

Somit stellt sich mir die Frage, was man in diesem Fall machen kann?
Würde mich sehr über Tipps freuen!

Gruß
Thomas

21.07.2011 - 17:59 Uhr

Hallo WPFler,

mein Problem hat sich erledigt. Ich nutze nun doch die 2. Variante und lege die Popups/ContextMenus in den Resources an. Die Bindings konnte ich so abändern, dass alles wieder funktioniert.

So werden jedenfalls die Transformationen der Parents nicht berücksichtigt und die Popups/ContextMenus sehen "normal" aus.

Gruß
Thomas

17.07.2011 - 17:07 Uhr

Hallo WPFler,

in einer Canvas nutze ich Transformationen für Zoom (ScaleTransform) und Rotation (RotateTransform).
Des Weiteren werden darin verschiedene UserControls gezeichnet, die gedreht werden können (RotateTransform).

Wenn ich in diesen UserControls Kontextmenüs verwende, dann werden die wie gewöhnlich dargestellt, wenn man die mit der rechten Maustaste öffnet.
Jedoch hätte ich auch gerne mit der linken Maustaste eine Art zweites ContextMenu, was mir bisher nicht gelungen ist.

Deshalb nutzte ich momentan Popups, jedoch macht das mit den Transformationen der Canvas und UserControls Ärger.
Die Popups sind teilweise skaliert/verdreht und somit ist das mit der Bedienbarkeit so eine Sache, wenn man erst den Kopf und 123,45 Grad drehen muss bzw. ein Mikroscop am Bildschirm braucht. 8o

Folgende Möglichkeiten fallen mir ein

  1. Einen Converter schreiben, der den VisualTreeHelper nutzt und alle Tranformationen von UserControl und Canvas in umgekehrter Richtung für ScaleTransform, RotateTransform,.. vornimmt. -> Sehr aufwändig
  2. Popup in UserControl.Resources auslagern, damit das Popup nicht mehr im Visual Tree ist. -> Zugriff auf das Popup sowie Bindings werden komplizierter

Gibt es eine simple Lösung für das Problem? Vielleicht eine Eigenschaft, die das Popup unabhängig von den Transformationen der Parents macht?

Gruß
Thomas

02.02.2011 - 09:55 Uhr

Hallo C#ler,

ich habe eine Software geschrieben (.Net 4 mit WPF) mit einem Art MVC-Ansatz. Die Model-Schicht ist also sauber von der View getrennt, steckt allerdings zurzeit noch alles in einem Projekt mit Strukturierung über Namespaces.

Die Software möchte ich jedoch aufteilen in ein Kern-Projekt (Klassenbibliothek mit Model-Schicht) und ein View-Projekt (WPF-Anwendung). Das ganze Programm soll in diesem Zuge auch auf Netzwerk ausgelegt werden. D.h. ein Benutzer startet über eine View die Kern-Komponente als Server (Als Server starten...). Weitere Views sollen sich über TCP damit verbinden können (Mit Server X verbinden...) und somit auf der gleichen Model-Schicht arbeiten.

In Java habe ich bereits etwas Erfahrungen mit Sockets und habe mir da jetzt in letzter Zeit etwas in C# eingearbeitet (TCPListener, TCPClient,...).
Allerdings fehlt mir noch ein Konzept bzw. Design Pattern für solch ein Vorhaben. Momentan habe ich mir überlegt das mit LINQ to XML zu machen (XElement) und das dann über den NetworkStream zu jagen.

<MyObject Guid="{C053889D-54A6-46B1-A5C1-222E8A2DD2D0}" MyProperty="true" />\r\n

D.h. bei Property-Änderungen bzw. Methodenaufrufe muss alles übers Netzwerk gehen. Da die Model-Schicht mit Hardware (USB, SerialPort) kommuniziert ist dort auch ein Observer-Pattern, d.h. bei Änderungen müssen die auch in allen Views dargestellt werden.

Das Protokoll sollte auch plattformunabhängig sein, da ich später mobile Clients (Android, iOS, WP7) erstellen möchte.

Jetzt stellt sich mir die Frage, ob mein Gedankengang mit einem offenen XML-Protokoll und per Socket-Kommunikation prinzipiell der richtige Ansatz ist, oder ob das für diese Zwecke viel zu aufwändig wird?

WCF gibt es zwar auch, jedoch habe ich da noch nicht so die Erfahrung, ob das überhaupt ansatzweise für mein Vorhaben geeignet ist. Request/Response-Prinzip bringt mir nichts und Polling will ich vermeiden. Viele Beispiele von WCF driften immer in den Bereich Web bzw. Web Services ab mittels SOAP bzw. JSON.

Momentan habe ich mal einen kleinen Test-Client mithilfe der TCPClient-Klasse geschrieben.

private TcpClient server; //Initialisierung vernachlässigt

public void Run() //Läuft in einem eigenen Thread
{
	Debug.WriteLine("+++ Client verbunden +++");
	StreamReader streamReader = new StreamReader(this.server.GetStream());
   
	while (true)
	{
		try
		{
			String receivedData = streamReader.ReadLine();
			if (receivedData == null)
				break;
			
			Debug.WriteLine(receivedData); //Hier dann später Auswertung
		}
		catch (Exception ex)
		{
			Debug.WriteLine("Client Read-Exception: " + ex.Message);
			break;
		}
	}

	Debug.WriteLine("--- Client getrennt ---");
}

public void Write(String s) //Soll durch andere Threads aufgerufen werden können
{
	try
	{
		StreamWriter streamWriter = new StreamWriter(this.server.GetStream());
		streamWriter.WriteLine(s);
		streamWriter.Flush();
	}
	catch (Exception ex)
	{
		Debug.WriteLine("Client Write-Exception: " + ex.Message);
	}
}

In der Schleife des Client-Threads könnten ankommende Daten verarbeitet werden. Bei der Write-Methode will ich dann Daten an den Server senden, wobei die Methode durch andere Threads aufgerufen werden könnte. Vermutlich ist das nicht Thread-safe? Muss ich beim Nutzen von StreamReader/StreamWriter ein Lock-Objekt verwenden, sodass ich keinen Datenmüll im NetworkStream produziere?

Gruß
Thomas
P.S. Bin für Ratschläge, Kritik, Buchempfehlungen,... sehr dankbar.

05.04.2010 - 17:02 Uhr

Hallo Blackhawk50000,

im Grunde wird im DataTrigger auch nichts anderes gemacht, als die Eigenschaften vom Window zu ändern.
Komisch finde ich daran nur, wenn man die gleichen Eigenschaften in der Code-behind bzw. einer abgeleiteten Window Klasse verändert, so funktioniert es ohne Probleme. Stellt sich nur die Frage warum, denn ich sehe in dem unteren Code eigentlich keinen Fehler.

Gruß
Thomas

26.03.2010 - 15:45 Uhr

Hallo,

ViewModel (MainWindowViewModel):


private bool isFullScreen;
public bool IsFullScreen
{
    get { return this.isFullScreen; }
    set
    {
        if (value != this.isFullScreen)
        {
            this.isFullScreen = value;
            OnPropertyChanged("IsFullScreen");
        }
    }
}

ResourceDictionary:


<Style x:Key="FullScreenStyle" TargetType="{x:Type Window}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=IsFullScreen}" Value="True">
            <Setter Property="WindowStyle" Value="None" />
            <Setter Property="WindowState" Value="Maximized" />
            <Setter Property="Topmost" Value="True" />
        </DataTrigger>
    </Style.Triggers>
</Style>

View (MainWindow):


<Window ... Style="{StaticResource FullScreenStyle}" ...

Vollbild funktioniert auch, wenn ich das Programm starte und durch irgendwelche Commands das Property IsFullScreen ändere. Sobald ich allerdings per Maus das Fenster maximiere, wieder verkleinere und dann nochmal Vollbild mache, dann passt es nicht mehr. Es fehlt dann die komplette Titelleiste und es maximiert sich auch nicht mehr. Momentan nur unter Windows XP getestet. Was mache ich falsch?

Gruß
Thomas

25.03.2010 - 18:08 Uhr

Danke CSL,

mit deinem Beispiel hab ich es hinbekommen. Hab mir eine MyMouseEventArgs-Klasse geschrieben, da kann ich alles reinpacken und das dann beim Converter zurückgeben.

while (parent != null || !(parent is GeradeControl))
while (parent != null && !(parent is GeradeControl))
😁

Gruß
Thomas

24.03.2010 - 19:47 Uhr

Hallo,

MA-Tec meint sicherlich diese Diskussion: Paar Fragen zu MVVM
Also ich muss gestehen, dass ich zurzeit auch im Model einfach INotifyPropertyChanged einsetze. Sobald sich was im Model ändert, so soll das in der View dargestellt werden und das betrifft fast alle Properties.
Das funktioniert auch ganz gut, erst richtig aufwändig wird es bei Listen im Model, die synchron mit den Listen im ViewModel gehalten werden müssen. Ich sehe da keine andere Möglichkeit als (Enhanced)ObservableCollection auch im Model zu nutzen und im ViewModel dann CollectionChanged auf die Liste des Models zu registrieren.

Hier mal ein Ausschnitt aus dem ViewModel meines Logbuches.


public LogViewModel(Log log)
{
    this.log = log;

    this.logeintraege.AddRange(this.log.Logeintraege.Select(l => new LogeintragViewModel(l)));
    this.log.Logeintraege.CollectionChanged +=new NotifyCollectionChangedEventHandler(Logeintraege_CollectionChanged);
}

void Logeintraege_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    switch (e.Action)
    {
        case NotifyCollectionChangedAction.Add:
            foreach (Logeintrag logeintrag in e.NewItems)
                this.logeintraege.Add(new LogeintragViewModel(logeintrag));
            break;
        case NotifyCollectionChangedAction.Remove:
            foreach (Logeintrag logeintrag in e.OldItems)
                this.logeintraege.RemoveAll(l => l.Logeintrag == logeintrag);
            break;
        case NotifyCollectionChangedAction.Reset:
            this.logeintraege.Clear();
            this.logeintraege.AddRange(this.log.Logeintraege.Select(l => new LogeintragViewModel(l)));
            break;
    }
}

private EnhancedObservableCollection<LogeintragViewModel> logeintraege = new EnhancedObservableCollection<LogeintragViewModel>();
public EnhancedObservableCollection<LogeintragViewModel> Logeintraege
{
    get { return this.logeintraege; }
}

Und das bei jeder Liste. Wenn es eine andere Möglichkeit mit Listen geben würde, dann wäre ich glücklich. Bin für jede Kritik offen, will schließlich was lernen, bitte um Meinungen. 😉

Gruß
Thomas

19.03.2010 - 13:01 Uhr

Hallo Quaneu,

das gleiche Problem hatte ich auch schon. Mit TargetName bekomme ich die gleiche Fehlermeldung. Interessant wäre es schon, ob man mit einem Trigger auf ein anderes Element zugreifen kann.

Die Funktionalität die du willst, die bekommst du ja mit RadioButtons:


...
<Style x:Key="StyleRadioButtonToogle" BasedOn="{StaticResource {x:Type ToggleButton}}" TargetType="{x:Type RadioButton}">
    <Style.Triggers>
        <Trigger Property="IsChecked" Value="true">
            <Setter Property="Background" Value="LightBlue"/>
        </Trigger>
    </Style.Triggers>
</Style>

...

<RadioButton GroupName="group" Style="{StaticResource StyleRadioButtonToogle}" Content="test1" />
<RadioButton GroupName="group" Style="{StaticResource StyleRadioButtonToogle}" Content="test2" /> 

Gruß
Thomas

19.03.2010 - 10:25 Uhr

Hallo,

vielen Dank CSL. Also langsam wird der Durchblick größer. Ich hab nun mal die Canvas, in der ich Grafikobjekte (eigene UserControls) zeichne komplett umgebaut:

Alt:


<ScrollViewer  x:Name="myScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" MouseWheel="onMouseWheel">
    <Canvas x:Name="myCanvas" Width="{Binding Path=Width}" Height="{Binding Path=Height}" MouseLeftButtonDown="mouseLeftButtonDown">
        <Canvas.LayoutTransform>
            <TransformGroup>
                <RotateTransform Angle="{Binding Angle}" />
                <ScaleTransform ScaleX="{Binding Zoom}" ScaleY="{Binding Zoom}" />
            </TransformGroup>
        </Canvas.LayoutTransform>
    </Canvas>
</ScrollViewer>

In den einzelnen UserControls hatte ich dann jeweils noch die Position gebunden:


<UserControl
    x:Class="MyApp.GeradeControl"
    ...
    Height="50" Width="50"
    Canvas.Left="{Binding Path=X}" Canvas.Top="{Binding Path=Y}">

In der Canvas konnte ich den sender bestimmen.


private void mouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Console.WriteLine("sender: " + sender.GetType());
    Console.WriteLine("e.Source: " + e.Source);
}

Ausgabe wenn man auf eine leere Stelle der Canvas klickt:
sender: System.Windows.Controls.Canvas
e.Source: System.Windows.Controls.Canvas

Ausgabe wenn man auf ein Grafikobjekt klickt (z.B. GeradeControl):
sender: System.Windows.Controls.Canvas
e.Source: MyApp.GeradeControl

=> Somit hatte ich als sender immer die Canvas und als e.Source das Grafikobjekt bzw. auch die Canvas. Konnte ich so wunderbar auswerten.

Neu mit MVVM:


<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Events:EventBinding.EventBindings="{Events:EventBindingExtension Events=1:PreviewMouseWheel, Commands=1:MouseWheelCommand}">
    <ItemsControl Background="Transparent" Width="{Binding Path=Width}" Height="{Binding Path=Height}" ItemsSource="{Binding Grafikobjekte}" Events:EventBinding.EventBindings="{Events:EventBindingExtension Events=1:MouseLeftButtonDown, Commands=1:MouseLeftButtonDownCommand}">
        
        <ItemsControl.LayoutTransform>
            <TransformGroup>
                <RotateTransform Angle="{Binding Path=Angle}" />
                <ScaleTransform ScaleX="{Binding Path=Zoom}" ScaleY="{Binding Path=Zoom}" />
            </TransformGroup>
        </ItemsControl.LayoutTransform>
        
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        
        <ItemsControl.ItemContainerStyle>
            <Style>
                <Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
                <Setter Property="Canvas.Left" Value="{Binding Path=X}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>
</ScrollViewer>

Canvas.Left bzw. Canvas.Right ist zudem aus den einzelnen UserControls rausgeflogen.
Hier binde ich jetzt die Grafikobjekte vom ViewModel an das ItemsControl:


EnhancedObservableCollection<GrafikobjektViewModel> grafikobjekte = new EnhancedObservableCollection<GrafikobjektViewModel>();
public EnhancedObservableCollection<GrafikobjektViewModel> Grafikobjekte
{
    get { return this.grafikobjekte; }
}

private ICommand mouseLeftButtonDownCommand;
public ICommand MouseLeftButtonDownCommand
{
    get
    {
        if (this.mouseLeftButtonDownCommand == null)
            this.mouseLeftButtonDownCommand = new DelegateCommand(p => this.mouseLeftButtonDown(p));
        return this.mouseLeftButtonDownCommand;
    }
}

private void mouseLeftButtonDown(object param)
{
    MouseButtonEventArgs e = param as MouseButtonEventArgs;

    Console.WriteLine("e.Source: " + e.Source);
    Console.WriteLine("e.OriginalSource: " + e.OriginalSource);
}

Jedes Grafikobjekt (Gerade) hat ein ViewModel (GeradeViewModel), dass von GrafikobjektViewModel abgeleitet ist. Zudem ist für jedes ViewModel ein DataTemplate definiert:


<DataTemplate DataType="{x:Type vm:GeradeViewModel}">
    <v:GeradeControl />
</DataTemplate>

Füge ich nun bei der Liste Grafikobjekte im ViewModel z.B. eine neue GeradeViewModel ein, wirds auch sofort dargestellt... 8)

Ich hoffe ich habe es soweit erstmal richtig verstanden und umgesetzt.

Problem:
Mit den Parametern der Commands komme ich immer noch nicht ganz klar.

Ausgabe wenn man auf eine leere Stelle des ItemsControl klickt:
e.Source: System.Windows.Controls.ItemsControl Items.Count:5
e.OriginalSource: System.Windows.Controls.Border

Ausgabe wenn man auf ein Grafikobjekt klickt (z.B. GeradeControl):
e.Source: System.Windows.Controls.ItemsControl Items.Count:5
e.OriginalSource: System.Windows.Shapes.Path

An das UserControl, dass gedrückt wurde, komme ich jedoch nicht ran. Am Besten wäre natürlich, wenn ich irgendwie direkt raus bekommen würde, ob es sich z.B. um ein GeradeControlViewModel handelt.

  1. Du gibst direkt die View als CommandParameter mit {Binding ElementName=window}, du brauchst eventuell "me" gar nicht, für die Position der Maus kannst du auch das statische Mouse.GetPosition(x) verwenden.

Habe ich schon getestet, hat mich aber nicht weiter gebracht. An die Mauskoordinaten komme ich folgendermaßen.


private void mouseLeftButtonDown(object param)
{
    MouseButtonEventArgs e = param as MouseButtonEventArgs;
    ItemsControl itemscontrol = e.Source as ItemsControl;
    Point mousePosition = Mouse.GetPosition(itemscontrol);
    ...
}

Wie gesagt sender bzw. gleich das ViewModel des UserControls, das dies auslöst, bräuchte ich.

  1. Du schaltest einen Converter dazwischen der den Parameter entsprechend ändert. Auf meiner Seite siehst du ein Beispiel dafür.
    Da kannst du jedes beliebige Objekt erstellen und als Parameter weiter geben, z.B. ein erweitertes MouseButtonEventArgs das die Position gleich mit liefert 😄

Das verstehe ich noch nicht ganz. Ich habe zwar schon einige Converter geschrieben, jedoch verstehe ich das in Zusammenhang mit MouseButtonEventArgs und sender, MouseButtonEventArgs nicht?

Gruß
Thomas

14.03.2010 - 17:36 Uhr

Hallo zero_x und CSL,

erstmal Danke für eure Hilfe. Ich muss mich noch intensiver damit befassen, denn an manchen vielen Stellen fehlt mir einfach das Wissen.

Das mit dem EventBinding funktioniert nun:

View:

<Canvas Background="LightGray" Events:EventBinding.EventBindings="{Events:EventBindingExtension Events=1:PreviewMouseLeftButtonDown;2:PreviewMouseMove, Commands=1:CanvasClickCommand;2:MouseMoveCommand}" />

ViewModel:


private ICommand mouseMoveCommand;
public ICommand MouseMoveCommand
{
    get
    {
        if (this.mouseMoveCommand == null)
            this.mouseMoveCommand = new DelegateCommand(p => this.mouseMove(p));
        return this.mouseMoveCommand;
    }
}

private void mouseMove(object param)
{
    MouseButtonEventArgs me = param as MouseButtonEventArgs;
    Debug.WriteLine("X: " + me.GetPosition(???).X); //Kein Zugriff auf View
}

private ICommand canvasClickCommand;
...

Nur beim Auswerten der GetPosition bräuchte man ja wieder Zugriff auf das Control der View. Aber eventuell begehe ich hier schon wieder einen Denkfehler.

Ich hatte folgendes vor, was ich ohne MVVM Einsatz schon implementiert hatte (Canvas - Spalt zwischen Grafikobjekten). In einer Canvas konnte man Grafikobjekte zeichnen. Später wollte ich noch verschieben, drehen, ... einbauen. Hat man auf das Canvas geklickt und war ein bestimmter RadioButton ausgewählt, so wird ein dementsprechendes Grafikobjekt (UserControl) in der Canvas gezeichnet (Koordinaten der Maus werden in ein bestimmtes Raster umgerechnet).

Das würde ich gerne in MVVM umsetzen, doch ich scheitere ja schon an diesem simplen Logbuch 😭

Benutzereingaben:
Das mit den Benutzereingaben bekomme ich auch nicht hin. Der Converter ist ja noch verständlich. Gibt so von Microsoft und der Einsatz ist auch klar:


[ValueConversion(typeof(DateTime), typeof(String))]
public class DateTimeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        DateTime date = (DateTime)value;
        return date.ToShortDateString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string strValue = value.ToString();
        DateTime resultDateTime;
        if (DateTime.TryParse(strValue, out resultDateTime))
        {
            return resultDateTime;
        }
        return value;
    }
}

Über den Parameter könnte man eventuell noch das Format einstellbar machen.

View


...
<UserControl.Resources>
    <converters:DateTimeConverter x:Key="dateTimeConverter"/>
</UserControl.Resources>
...
<TextBox Text="{Binding Path=Zeit, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource dateTimeConverter}}" />
...

Jetzt dachte ich wenn ValidatesOnDataErrors=True und eine Exception geworfen wird, so kann ich das über einen Style per Trigger kenntlich machen. Habe das hiermit versucht:


<Style TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="BorderBrush" Value="Red" />
            <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
        </Trigger>
    </Style.Triggers>
</Style>

Ich sehe zwar in VS in der Ausgabe die Exceptions, wenn ein Fehler eingegeben wurde, jedoch in der View passiert nichts. Bestimmt beachte ich mal wieder etwas nicht? X(

Gruß
Thomas

14.03.2010 - 16:07 Uhr

Hallo,

jetzt hab ich verstanden, wie du das herbivore damals mit dem Abbruchflag in Kombination mit der Quit-Nachricht gemeint hat.
Aber da es bei mir immer nur einen Consumer geben wird, so lasse ich das erstmal so.

Gruß
Thomas

13.03.2010 - 17:59 Uhr

Hallo herbivore,

ich habe nur einen Consumer. Aber du hast Recht, wenn man mehrere Consumer hat, so schnappt man durch das clear den anderen die Quit-Nachricht weg.

Gruß
Thomas

13.03.2010 - 15:22 Uhr

Hallo,

ich wollte das Thema nochmal aufgreifen, da ich mir noch ein paar Gedanken dazu gemacht habe und ihr schon Recht hattet, dass das mit Thread.Abort sehr schlecht ist. Eine schöne Erklärung findet sich auch hier.
Das mit der SyncQueue funktioniert schon mal bestens. Mittlerweile finde ich die Lösung mit der Quit-Nachricht gar nicht so schlecht, gibt ja kaum eine andere Möglichkeit. Hier mal der Codeausschnitt fürs Beenden:


private SyncQueue<Byte[]> sendePuffer = new SyncQueue<Byte[]>();

public void stop()
{
    this.sendePuffer.Enqueue(null); //Quit-Nachricht
}

public void sende() //Wird im sendeThread ausgeführt
{
    while (true)
    {
        Byte[] bytes = this.sendePuffer.Dequeue();      //Hole Befehl aus der Schlange (wartet automatisch)
        if (bytes == null) //Quit-Nachricht
        {
            Debug.WriteLine("sendeThread endet hier");
            if (this.serialPort.IsOpen)
                this.serialPort.Close();              
            return;
        }

        this.serialPort.Write(bytes, 0, bytes.Length);  //Sende den Befehl
        this.quittierung.WaitOne(200);                  //Warte auf Quittierung oder Timeout (200ms)
    }
}

Hab ich das so richtig verstanden? Beim stop schreibe ich eine Quit-Nachricht (null) in die Schlange und sobald die Nachricht ausgelesen wird gehe ich mit return einfach raus?

Nur eine Kleinigkeit noch. Könnte ja sein, dass in der Schlange viele Befehle drin stehen und die Quit-Nachricht ziemlich am Schluss kommt. Bevor ich die Quit-Nachricht in die Schlange schreibe, könnte man ja ein Clear() machen. Wenn ich in der SyncQueue noch folgendes einfüge:


public void Clear()
{
    lock (this)
    {
        this.queue.Clear();
        Debug.WriteLine(Thread.CurrentThread.Name + " Clear ==> " + this.queue.Count);
    }
}

Bei stop() rufe ich dann this.sendePuffer.Clear() auf und danach sende ich die Quit-Nachricht.
Ich bin mir nur nicht sicher, ob ich das so richtig mache, oder mir gerade die SyncQueue zerschieße.

Gruß
Thomas

12.03.2010 - 15:00 Uhr

Hallo David,

vielen Dank für deine Anmerkungen. Dein Blog gefällt mir gut, wenn ich so viel Ahnung hätte, dann wäre ich =).

Stichwort:
>

Das mit KeyDown und KeyUp sind vermutlich noch die einfacheren Events. Wenn ich MouseMove(object sender, MouseEventArgs e), MouseLeftButtonDown(object sender, MouseButtonEventArgs e), onMouseWheel(object sender, MouseWheelEventArgs e) verwende, so kann ich z.B. auf e.GetPosition(...), e.Delta,.. zugreifen. Wenn ich nun aber mit EventBindings arbeite, wie komme ich dann an diese Argumente?


>

Werde ich testen.

a. Kein Designer verwenden (Ich persönlich verwende nie Designer)
b. DynamicResource verwenden
c. Das ResourceDictionary temporär zu den Window Resourcen hinzufügen.

Eigentlich mag ich auch keine Designer, verwende ich in anderen Bereichen schon lange nicht mehr. Ich nutze den Designer auch nur als Vorschau und tippe XAML selber. Ist halt doch manchmal nützlich, wenn man gleich in der Vorschau sieht, wie das Ergebnis aussieht.

Die PumpeViewModel gehören in eine ObservableCollection, oder noch besser gleich in eine
>

EnhancedObservableCollection hatte ich schon mal entdeckt, verstehe ich doch richtig, dass die um sort, range und remove Methoden erweitert ist und zudem beim Propertyä-Änderungen eines Items ein Event feuert? Spricht da was dagegen die auch im Model einzusetzen und nicht nur im ViewModel?

5. Überprüfung von Benutzereingaben
Im ViewModel:


public DateTime Zeit
{
    get { return this.model.Zeit; }
    set
    {
        if (value != this.model.Zeit)
        {
            model.Zeit = value;
            OnPropertyChanged("Zeit");
        }
    }
}

In der View:

<TextBox Text="{Binding Path=Zeit, StringFormat=\{0:HH:mm:ss\}, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

Textbox in der View zeigt Zeit richtig an. Nun ändere ich etwas, allerdings in ein nicht korrektes Format:
ConvertBack cannot convert value '14:25:58s' (type 'String'). BindingExpression...

Egal ob im ViewModel der Typ des Property ein DateTime, int, ... ist, es geht immer schief, wenn es sich nicht parsen lässt. String hingegen geht immer. Ich versteh nur noch nicht, wo ich dies jetzt abfangen muss und wie ich das in Verbindung mit IDataErrorInfo bzw. ValidatesOnDataErrors=True in die Reihe bekomme?

LogView : LogViewModel zur verwaltung des Log fensters
und
LogEntryViewModel : LogEntry zur anzeige der Log Einträge in einer EnhancedObservableCollection.
Wo ist das Problem? Eventuell ist es ein separater Thread wert, dieser kann sehr lang werden.

Also rein theoretisch kann ichs irgendwie nachvollziehen. Ich hab als Model Log und Logeintrag. Als ViewModel LogViewModel und LogeintragViewModel. Aber in der Praxis scheiterts einfach noch, da ich an vielen Punken mit dem MVVM nicht klar komme. -> Ich gebe zu: PEBKAC X(
Eventuell mach in wirklich einen neuen Thread, will das Forum aber nicht unnötig mit meinen ... belasten.

Gruß
Thomas

12.03.2010 - 11:21 Uhr

Hallo C#ler,

mal wieder beschäftige ich mich mit dem MVVM, doch irgendwie komm ich noch nicht ganz damit klar. Erfahrung habe ich durchs Studium nur mit MVC und Java, jedoch bringt mich das kein Stück weiter. Ich versuche deshalb durch viele Beispiele (z.B. Josh Smith) mir was abzuschauen.

1. Command Bindings
Also die Model- und View-Schicht hab ich einigermaßen verstanden. Dass man bei WPF mit Bindings zwischen View und ViewModel arbeitet ist auch klar. Mit dem RelayCommand und dem Erstellen von Commands im ViewModel, die dann in der View über Bindings verwendet werden, dass scheint ganz gut zu funktionieren.

Die View kennt somit das ViewModel über den DataContext.
Darf das ViewModel auch eine Referenz auf die View besitzen? Was macht man, wenn man Mausrad, Drag and Drop,... verwenden möchte. Was sagt ihr zu sowas:


public class MyViewModel
{
	private MyModel model;
	private MyView view;
	
	public MyViewModel(MyModel model, MyView view)
	{
		this.model = model;
		this.view = view;
		
		this.view.IrgendeinControl.PreviewMouseWheel += new MouseWheelEventHandler(MouseWheel);
	}
	
	void MouseWheel(object sender, MouseWheelEventArgs e)
	{
		//TODO MouseWheel
	}
	
	private ICommand clearCommand;
	public ICommand ClearCommand
	{
		get
		{
			if (clearCommand == null)
			{
				clearCommand = new RelayCommand(param => this.model.clear(), param => this.model.Irgendwas != 0);
			}
			return clearCommand;
		}
	}
}

Command Binding des ClearCommand an Buttons, MenuItems funktioniert. KeyBindings schlagen in der View jedoch fehl, scheint sich aber mit CommandReference aus dem WPF Model-View-ViewModel Toolkit 0.1 zumindest umgehen zu lassen.

2. Radio Buttons:
Habe im Internet Lösungen gesehen, die für jeden RadioButton IsChecked an das jeweilige Property des ViewModel binden. Die Gruppierung muss dann aber über Code im ViewModel erfolgen und nicht über GroupName in der View.
Eine andere Lösung ist eine Enumeration, die über einen Converter an die RadioButtons gebunden wird, z.B. wie bei Lars Hildebrandt. Hatte da allerdings teilweise schon komische Effekte und die RadioButton-Auswahl und die Enumeration im ViewModel waren irgendwie nicht mehr "synchron". Eventuell hat da GroupName rein gespuckt, da dieser gar nicht gesetzt war.
Wenn ich den oberen Code verwenden würde, so könnte man in der View mit GroupName arbeiten und im ViewModel per if-else die RadioButtons per this.view.MyRadioButton.isChecked abfragen.
Welche Lösung strebt ihr an?

3. View in View verwenden:
Im MainWindow habe ich wiederum andere Views, wie z.B. TestView (UserControl). Das füge ich folgendermaßen ein:


<Window x:Class="MyApp.View.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:view="clr-namespace:MyApp.View" ... >
...
	<view:TestView x:Name="testView" />
...
</Window>

Beim Ausführen des Programms klappte alles reibungslos, TestView wird angezeigt. In Visual Studio funktioniert der Designer vom TestView-Control tadellos, allerdings streikt nach dem Kompilieren der Designer vom MainWindow und meldet:
Fehler 1: Die Instanz des TestView-Typs konnte nicht erstellt werden.
Der Hinweis von Microsoft brachte mich auch nicht wesentlich weiter. (Die Instanz des '{0}'-Typs konnte nicht erstellt werden)
Habe in der TestView alle Element raus und dann gings wieder wunderbar. Systematisch habe ich dann nach und nach alles eingefügt. Letztendlich produziert der Style (Style="{StaticResource StyleRadioButtonToogle}") den Fehler. Der Style ist in einem ResourceDictionary angelegt, welches in der App.xaml (ResourceDictionary.MergedDictionaries) includiert ist. Verwende ich stattdessen DynamicResource, so funktioniert der Designer ohne zu murren.

4. Immer aktuelle Daten des Models in der View:
Man hat z.B. im Model eine Liste mit Objekten "Pumpe" mit dem Property "IsOn". In der View-Schicht gibt es eine PumpeView, welche z.B. in einer Canvas dargestellt wird. Den Rest kann man sich denken, ändert sich IsOn, so soll in der View die Pumpe anders dargestellt werden.
Würde ich mit dem MVC Pattern arbeiten, so hätte die PumpeView direkten Zugriff auf die Pumpe und bei Änderung wurde dies dargestellt werden (z.B. Einsatz des Observer-Pattern).
Wenn ich die Forenbeiträge richtig verstanden habe, so ist bei MVVM immer ein ViewModel nötig. D.h. ich muss die Änderung durchs ViewModel durchreichen. Ich habe die Beiträge in Paar Fragen zu MVVM verfolgt, endete schließlich in heißen Diskussionen 😉
Sehe ich es richtig im ViewModel mit INotifyPropertyChanged zu arbeiten und im Model mit eigenen Events, auf das sich das ViewModel registriert und bei Änderungen das Property anpasst, welche dann das PropertyChanged-Event auslöst, worauf die View reagiert?
Was ist aber, wenn man im Model mit List<...> arbeitet und in der View z.B. Comboboxen hat, die Objekte der Liste zur Auswahl darstellen sollen. Somit wieder für jedes Objekt ein ViewModel? Ändert sich etwas in der Liste, so soll das auch in der View dargestellt werden. D.h. im Model dann gleich ObservableCollection verwenden?

**5. Überprüfung von Benutzereingaben **
Da gab es schon heiße Diskussionen in der Hochschule, ob im Model oder im Controller (bzw. hier ViewModel). Schlussendlich hat man sich geeinigt, dass im Model objektrelevante Sachen geprüft werden, wie z.B. Zahlenbereiche, was sonst zu Logikfehlern führen könnte. Im Controller wurde dann nur überprüft, ob der eingegebene String der Textbox sich z.B. in einen Integer parsen lässt bzw. ob gar kein Wert eingegeben wurde.
Hier sehe ich bei den ViewModels schon wieder Probleme. Die meisten Beispiele arbeiten nur mit String Properties, was ist jedoch, wenn im Model z.B. ein DateTime verwendet wird. Ist dann im ViewModel für diese Eigenschaft auch der Typ DateTime zu verwenden, oder generell String? D.h. im set Bereich würde dann erst versucht werden zu parsen. Geht dies schief, so wird in der View ein Fehler angezeigt (über IDataErrorInfo 🤔 )

6. Logbuch mit MVVM
Habe mal als Test ein Logbuch versucht zu realisieren. Wenn ich MVVM einsetze, so verstehe ich das mit der ItemsSource der ListView nicht mehr, da dann für jeden einzelnen Logeintrag ja eigentlich ein ViewModel nötig ist. Momentan binde ich immer noch die original Liste aus dem Model an die ItemsSource. Wenn ihr wollt, so kann ich Codeausschnitte hier mal einstellen.

Gruß
Thomas
P.S. Gibt es im Internet ein größeres Open Source Projekt, wo das MVVM-Pattern angewendet wurde, oder eine Buchempfehlung?

10.02.2010 - 15:54 Uhr

Hallo herbivore,

eiei bin ja doof. Ich hab mir dann in Applikation mit Warteschlange [SyncQueue<T>-Klasse] die weiteren Beiträge angeschaut.

OK aber prinzipiell scheint es gar nicht so viele Alternativen zu geben. Das mit der Quit- bzw. Dummy-Nachricht in Verbindung mit dem Abbruch-Flag ist sicherlich eine Lösung, aber irgendwie hab ich dabei ein ungutes Gefühl (eventuell unbegründet).

Vielen Dank für eure Bemühungen 👍
Thomas

10.02.2010 - 11:28 Uhr

Hallo Lynix,

das habe ich auch irgendwann mal gelesen, dass das mit Thread.Abort nicht schön sei.
Anfangs hatte ich das sogar so:


while(!shutdown)
{
	//1. Warte auf Nachrichten in der sendeQueue
	
	//2. Hole Nachricht aus der Queue
	
	//3. Sende Bytes der Nachricht
	
	//...
}

Beim Beenden wird nun shutdown auf true gesetzt. Der Thread wartet z.B. auf Nachrichten in der sendeQueue (1.) und es kommen keine Neuen, dann würde die Bedingung in der Schleife erstmal nicht überprüft werden und es wird gewartet, gewartet…

Dann dachte ich, ich mach eine Endlosschleife und breche den Thread beim Beenden ab, so wie es momentan auch ist. Wie du bereits gesagt hast, lässt sich die Anwendung dann nicht richtig beenden, da der Thread weiter läuft (musste ich leidvoll erfahren, als ich mal in den TaskManager geschaut habe 8o). Ich habe dann thread.IsBackground = true; gefunden und das mal getestet. Hier wird anscheinend der Thread beenden, wenn die Anwendung geschlossen wird, jedenfalls konnte ich im TaskManager nichts mehr erkennen.

Wenn es noch eine Möglichkeit gibt den Thread kontrolliert zu beenden, dann würde ich mich freuen, wenn mir jemand einen kleinen Denkanstoß geben kann.

Gruß
Thomas

09.02.2010 - 18:42 Uhr

Hallo ujr,

danke. Das ist doch genau was ich brauche. Werde ich testen.

Warum eigentlich benutzt Du this vor sendeQueue, timeoutTimer usw. aber nie vor serialPort?

Haste natürlich Recht. Gehört auch vor serialPort. Hatte serialPort irgendwann mal nur als lokale Variable, habs dann aber wegen der Initialisierung als Instanzvariable gemacht. (Im Projekt hab ichs scho umgestellt, im Forum scheint das Refactoring net funktioniert zu haben 😉)

Gruß
Thomas