Hallo zusammen,
ich arbeite aktuell an einer kleinen Anwendung die über einen Timer Aufgaben erledigt.
Nachdem ich komplett die Hintergrundlogik und die Klassen, einschließlich Timer, implementiert habe möchte ich jetzt an einigen Stellen die GUI ansprechen.
Leider musste ich feststellen, dass der wohl WPF-typische Timer unter System.Timers mir keinen Zugriff auf die Oberfläche gibt, sondern dort eine Threadverletzug meldet (InvalidOperationException - '...Objekt im Besitz eines anderen Thread...').
Ich hatte bereits früher einen solchen Timer im Einsatz, allerdings liefen dort alle Oberflächenänderungen per Binding ab, was auf magische Weiße auch Threadübergreifend funktionieren zu scheint.
In meinem aktuellen Beispiel habe ich nicht die Möglichkeit über Binding zu arbeiten und möchte stattdessen per Events bestimmte Operationen auf der GUI ausführen.
Nach einigem Suchen fand ich heraus, dass nur der Timer unter **System.Windows.Forms ** auf dem UI Thread arbeitet und problemlos Zugriffe ermöglicht. Der andere läuft, wenn ich es richtig verstanden habe, in einem eigenen Thread.
Ich möchte den Forms.Timer allerdings ungern einfach stumpf einsetzen ohne zu Wissen ob und wie ich das selbe mit der anderen Timer-Klasse bewerkstelligen könnte.
Welche Möglichkeiten habe ich UI-Zugriffe mit System.Timers.Timer durchzuführen?
Grüße
Deswegen fand ich den Timer für meine konkrete Anwendung auch absolut geeignet.
Ich wollte lediglich noch zusätzlich eine Art Echtzeitmodus bieten, bei dem die Visualisierung natürlich recht unbrauchbar wird, da die Geschwindigkeit zu hoch wäre. Alle anderen Geschwindigkeitseinstellungen hätte ich per echtem Zeitwert entsprechend verzögert.
Aber diesen "Echtzeit"-Betrieb sollte ich wie bereits erwähnt wirklich separat implementieren.
Generell muss ich mein Wissen über Threading noch erweitern (besonders Synchronisierung). Wie du sicher bemerkt hast war meine Zusammenfassung über die Funktionsweise des AutoResetEvents recht oberflächlich.
Ok, ich werde mich mal über das AutoResetEvent einlesen und einige Beispiele dazu suchen. Sowie ich es jetzt schon kurz überflogen habe, könnte mit dem AutoResetEvent mein Hintergrundthread per WaitOne() belieb lange warten. Ein anderer Thread (in meinem Fall die UI) würde das Fortsetzen über dieses Event einleiten?!
@ujr:
Treten denn große Unterschiede zwischen Threading mit WinForms und WPF auf? Ich habe selber nie WinForms benutzt, bei allem was ich aber gelesen habe, wurde nur die Veränderung mit dem Dispatcher angemerkt.
Meine Frage ist etwas schwierig zu erklären. Mit dem Kommentar meinte ich, dass für genau dieses Beispiel das UI nicht einfriert.
Zur Aktualisierung würde ich gerne eine Variante ohne Zeitangaben herausfinden. Die von dir angesprochenen 60x/s als Beispiel hätten meiner Auffassung nach das Problem, dass es für ein langsames System evtl. schon zuviele Operationen im UI-Thread sind und diese dann blockiert.
Es gilt ja unbedingt zu verhindern, zuviel Arbeit in die UI auszulagern, wenn diese weiterhin ansprechbar sein soll. Darum scheint mir ein fest programmierter Wert mit z.B. "alle 100ms aktualiseren" nicht richtig.
Verstehe ich das überhaupt korrekt?
Ok das dürfte mir eigentlich reichen.
Würde der Timer allerdings mit seinen 250ms auf einem sehr sehr langsamen PC die Oberfläche nicht wieder einfrieren? Es könnte ja sein, dass das Update der GUI länger braucht?
Wirklich unabhängig von Zeiten/unterschiedlichen Ausführungsgeschwindigkeiten bin ich damit ja nie.
Wie würde ich es denn realisieren können, wenn ich wirklich einen Ablauf wie folgt haben möchte:
Der Thread im Hintergrund arbeitet und invoked an einer bestimmten Stelle ein Update des UI. Anschließend arbeitet er solange nicht weiter (triggert keinen neuen GUI-Update) bis vom UI-Thread wirklich eine Meldung zurück kommt, dass seine Update-Operation beendet wurde. (Könnte ja einfach in UpdateGUI() am Ende implementiert werden).
So dass die beiden wirklich eine Kommunikation haben.
Für meinen Fall reicht eine Lösung mit Timer zwar, allerdings würde mich das einfach noch interessieren.
Für den üblichen Einsatz eines Threads, bei dem eine möglichst schnelle Abwicklung des ausgelagerten Jobs im Vordergrund steht und der Benutzer nur kurz informiert wird, ist der erste eigentliche Vorschlag natürlich das beste.
Die Lösung mit dem Timer hört sich im Prinzip sehr gut an (in der Tat ist meine Einzelschrittmethode schon fertig und ich hätte diese nur in einer Schleife ausgeführt). Der Timer würde ja zwischen seinen Intervallen nicht zum einfrieren der UI führen?! Für sehr lang laufende Maschinen möchte ich unbedingt noch die Möglichkeit geben einen Stop-Button zu haben, der das ganze Deaktivieren kann
Allerdings bin ich dann trotzdem an den Intervall des Timers gebunden. Ich kann damit zwar sehr gut die eingestellte Geschwindigkeit umsetzen, eine wirkliche Ausführung in Echtzeit habe ich dann allerdings nie. Mit "Echtzeit" meine ich eben eine Ausführung ohne Verzögerung, nur die Leistung der Hardware zählt.
Das im Falle einer solchen Echtzeitausführung der Benutzer die ablaufenden Schritte ohnehin nicht wirklich so schnell sehen kann wie sie dann durch die Oberfläche durchrennen ist klar. In diesem Fall wäre ich dann auch damit zufrieden wenn einige Schritte garnicht erst auf die UI gezeichnet werden. Der Benutzer ist ohne jegliche Verzögerung wie gesagt ja ohnehin nicht in der Lage das genau zu Verfolgen.
Edit: Mein Vorschlag mit Invoke und Wait() scheint nicht wirklich funktioniert zu haben. Trotz Wait() stockte die Oberfläche recht heftig. Zu stark um noch Stop klicken zu können.
Ich verstehe an deinem Beispiel schon richtig, dass fest alle 250 ms aktualisiert wird?
Um mein Problem evtl. besser zu erläutern zu der eigentlichen Anwendung. Es soll eine Art Simulator für eine Turingmaschine entstehen. Geschwindigkeit ist also absolut nebensächlich, da die Visualisierung im Vordergrund steht. Die Programmlogik wird im Hintergrund ausgeführt und ich möchte sicherstellen, dass jeder Schritt auf der Oberfläche sichtbar ist. Dazu soll der Benutzer die Geschwindigkeit regeln können.
Mein Plan war es je nach Geschwindigkeit per Sleep sicherzustellen, dass jeder Einzelschritt (jede Position des Lesekopfs) für eine gewisse Zeit sichtbar ist.
Bei maximaler Geschwindigkeit, wollte ich eine Art Ablauf in Echtzeit, bei der die GUI ruhig der limitierende Faktor sein kann, hauptsache sie friert nicht ein.
Desweiteren wird zur Aktualisierung Zugriff auf alle Werte der Turingmaschine/des Automaten gebraucht, sodass während des Vorgangs der Hintergrundthread eigentlich nicht weiterarbeiten sollte.
Wie verhält es sich auf Grund des Timers und dessen 250 ms mit Unterschiedlich schneller Hardware? Wenn ich es richtig sehe, würde in beiden Fällen der Hintergrundthread auf dem maximum laufen und unabhängig von seinem Fortschritt alle 250ms eine Aktualiserung des UI stattfinden. Alles was an Veränderungen im Thread dazwischen passiert würde übersprungen/nicht angezeigt werden?
Was passiert, wenn ein langsamer Rechner die UpdateMethode nicht in 250ms durchbekommt, ein anderer aber nur 100 brauchen würde. Dann laggt das Programm ja doch wieder, da nach 250 ms wieder aktualisiert wird, der GUI-Thread Dauerbeschäftigung erhält und einfriert?!
Hi, danke für die schnelle Antwort.
Wenn ich den Code gerade richtig verstehe, wird bei jedem Tick die GUI aktualisiert. In meiner späteren echten Anwendung will ich jedoch nicht in bestimmten Zeitintervallen aktualisieren, sondern innerhalb einer Berechnung.
Im Prinzip geht es mir um folgendes. Ein Thread arbeitet im Hintergrund eine Berechnung oder Sonstiges ab. An bestimmten Stellen soll er die GUI aktualisieren. Das Aktualisieren der GUI dauert etwas Zeit und ich möchte nicht, dass der Hintergrund schon einen neueren Aktualisierungsaufruf an die GUI sendet bevor diese den letzten beendet hat um nicht einzufrieren-
Ich möchte, dass die GUI falls nötig den Rest bremst um hinterher zu kommen. Dabei will ich nicht Sleeps im Background-Thread haben um auf die GUI zu warten, sondern ich möchte, dass das Programm so schnell abläuft, wie die GUI auf dem jeweiligen Rechner flüssig aktualisiert werden kann. Also soll der Thread im Hintergrund warten, bis die GUI ihren Updatevorgang vollständig ausgeführt hat.
Benötige ich dafür eine richtige Thread-Synchronisation? An diese habe ich mich bisher noch nicht gewagt und mich lediglich theoretisch eingelesen, welche Klassen etc. dort wie verwendet werden.
Ansonsten würde ich gerne noch wissen, welche Vorteile der Threadpool bietet. Für mein kleines Beispiel erschien mir ein einfacher Thread am geeignetsten, für eine spätere Anwendung könnte dieser aber interessant sein.
PS: In wie weit ist denn der von mir zitierte Auszug aus dem FAQ zu verstehen? Wartet Invoke doch nicht?
Hallo zusammen,
bei meiner Frage handelt es sich um ein ganz grundsätzliches Problem zu Thema "Threading mit Zugriff auf die GUI". Ich habe etwas ähnliches schon vor längerem bereits einmal gepostet. In der Zwischenzeit habe ich mich näher über Threads eingelesen und dabei mein Problem in eine Mini-Anwendung überführt um das Wesentliche abzubilden.
Bei meiner kleinen Anwendung handelt es sich um eine einfache WPF-Anwendung deren Oberfläche zwei Buttons, sowie eine TextBox enthält.
Die beiden Buttons starten und stoppen die Ausführung eines Hintergrundthreads.
In diesem Thread soll eine beliebige aufwändige Aktion laufen. In meinem Beispiel einfach nur das endlose Durchlaufen einer Schleife.
private void cmdStart_Click(object sender, RoutedEventArgs e)
{
thr = new Thread(new ParameterizedThreadStart(Test));
thr.Start(this);
}
public void Test(object obj)
{
var wnd = (MainWindow)obj;
int i = 0;
while (true)
{
i++;
if (i % 1000000 == 0) //Ab ca. 1000000 schafft die UI es
{
wnd.Dispatcher.Invoke(new Action(UpdateGUI));
}
}
}
Hin und wieder soll durch den Thread eine Änderung auf der UI ausgelöst werden. Hier dargestellt durch die Methode UpdateGUI().
Diese schreibt lediglich einen zufälligen Buchstaben in das Textfeld.
public void UpdateGUI()
{
Random r = new Random();
txt.Text = "";
txt.Text = ((char)r.Next(65, 91)).ToString();
}
Natürlich wäre in einer echten Anwendung das Berechnen des Zufallswertes im UI Thread absolute Verschwendung, die Methode soll hier lediglich eine beliebig aufwendige Aktualisierung der Oberfläche simulieren.
Wie in der Methode Test() sichtbar ist, regelte ich zu Testzwecken die Häufigkeit der Aktualisierung per Schleifenzähler. Je nachdem wie "selten" upgedatet wurde, blieb die GUI logischerweise flüssig oder stockte, wenn z.B. alle 10 durchläufe Aktualisiert wurde.
Was ich allerdings erreichen möchte ist, den Hintergrundthread warten zu lassen, sodass dieser seine Ausführung (hier einfach den Durchlauf der Schleife) erst dann fortsetzt, wenn die GUI die Methode UpdateGUI beendet hat. Ich möchte keinen pauschalen Sleep in meinen Thread einfügen um "sicher" zu gehen, dass die GUI fertig ist. Die Anwendung soll einfach auf einem schnelleren PC schneller ausgeführt werden können als auf einem langsameren.
In den beiden FAQs des Forums zum Thema Threading und UI (die übrigens sehr hilfreich sind), fand ich dazu folgendes:
"In vielen Fällen kann man wählen, ob man Control.BeginInvoke oder Control.Invoke benutzt. Der Hauptunterschied ist, dass Invoke wartet, bis der GUI-Thread die Aktion ausgeführt hat, wogegen BeginInvoke den Arbeitsauftrag nur die Nachrichtenschlange des GUI-Threads stellt und sofort zurückkehrt. Invoke arbeitet also (in vielen Fällen unnötig) synchron. Ein weiter Unterschied ist, dass man bei Invoke leichter an einen evtl. Rückgabewert der Aktion kommt als bei BeginInvoke."
Darunter verstand ich dann, dass die Verwendung von Invoke genau das gewünschte Verhalten zeigen würde.
Nach einigem weiteren suchen stieß ich darauf, dass BeginInvoke ein Objekt vom Typ DispatcherOperation zurückliefert, dass eine Wait()-Methode beinhaltet. Diese Methode kam mir etwas suspekt vor, da ich zu diesem Zeitpunkt schon davon ausging, die Threads mit Monitor etc. synchronisieren zu müssen. Eine Veränderung konnte ich durch die Benutzung von Wait() nicht ausmachen, würde allerdings trotzdem gerne wissen was im Hintergrund dieser Methode passiert.
var __op = wnd.Dispatcher.BeginInvoke(new Action(UpdateGUI));
__op.Wait(); //Magische Methode mit der der Arbeitsthread wartet?!
Ich hoffe das Problem ist einigermaßen verständlich.
Danke schonmal.
Ok danke, dann les ich auf jedenfall mal die CollectionViews ein.
Ok danke für die Antwort,
unabhängig von der CollectionView müsste ich in meinem Fall eigentlich ja nur bewerkstelligen, dass das Binding zusammen mit eben dem Konverter neu durchlaufen wird.
Ich habe sogar schon versucht als "Notlösung" im Code die BindingExpression zu holen und mit UpdateSource() das ganze manuell zum Starten zu zwingen, was aber leider keinen Effekt hat.
BindingExpression be = lstDokumente.GetBindingExpression(ListView.ItemsSourceProperty);
be.UpdateSource();
MfG Roper
Hallo zusammen,
ich habe ein kleines Problem mit dem Update meines DataBindings. Grob geht es um folgendes. Ich binde eine Liste von Objekten (hier einfach mal x genannt) an eine ListView. Sobald sich ein Property in einem dieser Objekte ändert soll ein Update erfolgen.
Meine ersten Versuche waren eine ObservableCollection<x> zu benutzen und in **x **INotifyPropertyChanged zu implementieren. Wie ich festgestellt habe führt das nicht zum Erfolg weil eine ObservableCollection das Event in einem der Objekte x nicht erkennt oder weiterleitet. Bei meiner Suche bin ich dann auf BindingList<T> gestoßen die eigentlich genau dies tun soll.
In meinem speziellen Fall ist es jetzt so. Die BindingList<x> ist an ein ListView gebunden. Dabei ist noch ein Converter vorhanden der die Liste per Linq so filtert, dass sie nur aus Objekten x besteht in denen das Property Zahl den Wert 1 hat.
Daneben gibt es einen Button auf der Anwendung der diesen Wert Zahl beim in der ListView markierten Objekt ändert. Danach müsste sich die Liste updaten und logischerweise das eine Objekt nicht weiter anzeigen.
Ich habe im Objekt x auf dem Property Zahl PropertyChanged implementiert und testweise das ListChanged-Event der BindingList<x> mal umgebogen auf eine Methode die mir eine MessageBox ausgibt. Dadurch sehe ich dass beide Events wirklich starten, der Konverter wird aber nicht betreten, folglich gibt es kein Update auf der GUI.
Falls das jetzt etwas wirr erklärt war hier noch die Kurzfassung der wichtigstens Code-Bestandteile:
<ListView ItemsSource="{Binding Path=Objekte, Converter={StaticResource Konv}}" />
public class Allgemein
{
public BindingList<Objekt> Objekte { get; set; }
...
public Alles()
{
Objekte.ListChanged += ListChanged;
}
...
void ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show(e.ListChangedType.ToString());
}
}
public class Objekt : INotifyPropertyChanged
{
private int _zahl;
public int Zahl
{
get { return _zahl; }
set { _zahl = value; OnPropertyChanged("Zahl"); }
}
//PropertyChanged Implementierung darunter
}
Wie gesagt feuert ListChanged mit der MsgBox beim ändern des Wertes von Zahl.
Muss ich evtl. noch etwas im XAML in Richtung UpdateSourceTrigger unternehmen?
MfG Roper
Sorry ich steh glaub grad etwas auf dem Schlauch.
MethodInvoker wird mir noch angemeckert. Ich habe es jetzt so:
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
...
int invoking = 0;
void Update()
{
if((Interlocked.Exchange(ref invoking,1)==0))//setze auf 1 und wenn vorher 0 war:
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, (MethodInvoker)(() =>
{
invoking=0;
pgr1.Value = pgr1.Value + 1;
}));
}
}
(Laut Hilfe ist das in Microsoft.JScript!?)
Ok danke. In wie weit kann ich dein Beispiel zum testen genau so übernehmen? Welche usings muss ich noch hinzufügen. BeginInvoke und MethodInvoker erkennt er so nicht. Für Interlocked habe ich System.Threading hinzugefügt.
Beim Backgroundworker ist es ja so, dass worker.ReportProgress das Ereignis ProgressChanged auslöst indem ich normal auf meine Steuerelemente zugreifen kann.
Wenn ich jetzt stattdessen diese Update()-Methode aufrufen würde, könnte ich normal auf die GUI-Elemente zugreifen oder ändert sich etwas?
So wie ich das jetzt nach einigen Tests sehe ist mein Problem wohl zum einen, dass ich zu oft update und zum anderen, dass meine Schleife und Berechnung weiterläuft während irgendwann das Ereignis dann mal Lust hat zu starten.
Da ich aber den BackgroundWorker nutze erledige ich ja meine GUI-Updates im ProgressChanged bzw. dann am Ende im RunWorkerCompleted. Wie passt das mit Invoke da rein.
Oder ist der BackgroundWorker in meinem Fall mist und ich sollte es wirklich mit Threads machen?
Ok danke.
An sich benutze ja nicht direkt Threads (wobei er BackgroundWorker intern sicher das selbe tut).
Meine Updates erledige ich ja im ProgressChanged-Event in dem ich normalen Zugriff auf meine Steuerelemente habe.
Würde es reichen das updaten nicht bei jedem Schleifendurchlauf auszuführen, sondern seltener?
Hallo zusammen,
ich arbeite gerade an einem Programm (WPF-Anwendung), dass je nach Benutzereingabe relativ zeitaufwendige Operationen ausführt und benutze dafür den BackgroundWorker.
Um zu testen ob alles überhaupt mal klappt habe ich vorher eine Mini-Anwendung geschrieben die einfach eine Schleife durchläuft und eine ProgressBar dazu mitlaufen lässt. Dort klappt alles wunderbar.
In meiner großen Anwendung habe ich jetzt das Problem, dass ich zwar bei kleinen Eingaben wo alles in sekundenschnelle ausgerechnet ist kein Problem habe (dort sehe ich ja auch nichts von GUI updating etc.), wenn ich allerdings größere Werte eingebe wo das Programm dann länger rechnet, blockiert meine GUI wie wenn ich das ganze in einer einfachen Schleife ausführen würde. Änderungen an der GUI werden nicht sichtbar und auch den Stop-Button kann ich nicht benutzen.
Meine Methode die im BackgroundWorker ausgeführt wird arbeitet auf einem Objekt, an dessen Eigenschaften auch GUI-Elemente gebunden sind. Kann es vielleicht daran liegen?
In meinem ProgressChanged-Ereignis führe ich deutlich komplexere und zeitaufwendigere Änderungen an der GUI durch als nur das updaten einer Progressbar. Kann es sein, dass mein Programm zu oft updatet und evtl. der letzte Update noch garnicht fertig ist?
Die XML-DAtei habe ich gemäß diesem Vorschlag HIER als "Inhalt" und "Immer kopieren" angegeben. Ich kann im Projekt also einfach nur per Name der Datei (in meinem Fall start.xml) darauf zugreifen.
Was muss ich hier einstellen damit ich es gemäß deinem Vorschlag umsetzen kann und vorallem: Kann ich es so einbinden dass es weitern einfach per Name klappt und trotzdem alles in der .exe steckt?
EDIT: Ach so, die Dateien MyProject.pdb, MyProject.vshost.exe, MyProject.vshost.exe.manifest und WPFToolkit.pdb kannst du ohne Probleme weglassen.
Ok danke, das war mir noch wichtig das zu wissen.
EDIT: Für meine .xml Datei gibt es noch ein Problem: Wenn ich sie als Resource angebe wird sie zwar mit in die .exe hineinkompiliert, allerdings lässt sich diese dann nicht starten.
Die XML-Datei kann ich wenn nötig auch komplett lassen. Meine Anwendung liest Abläufe aus XML Dateien aus und es gibt einen Startablauf der gleich geladen werden soll. Deshalb habe ich eine XML-Datei zum Projekt hinzufügen wollen..
Wie funktioniert das mit der WPFToolkit.dll? Kann ich beim Verweis auf diese irgendwie angeben, dass sie mit integriert werden soll. Oder brauche ich zusätzliche Software wie dieses .NETZ?
@Floste:
Ok danke. Aber wie binde ich die beiden ein? Ist das nur eine Einstellung?
Die XML-Datei habe ich gemäß dem Vorschlag HIER als "Inhalt" und "Immer kopieren" festgelegt. Wenn ich es in "Ressource" ändere will mein Programm nicht mehr starten!?
@Ploetzi:
Damit habe ich mich bisher nie befasst. Bietet Visual Studio da Optionen oder geht das nur über zusätzliche Software?
Hi,
ich habe gerade ein Problem bezüglich einer fertigen WPF-Anwendung die ich kompiliert habe.
Im bin Ordner finden sich eine ganze Reihe von Dateien wieder:
MyProject.exe
MyProject.pdb
MyProject.vshost.exe
MyProject.vshost.exe.manifest
WPFToolkit.dll
WPFToolkit.pdb
Außerdem noch eine .xml Datei die ich eigentlich in das Projekt mit eingebunden hatte.
Nun läuft die Anwendung nicht wenn ich die .exe allein herauskopiere. Ich habe verschiedenes getestet. Mindestes die WPFToolkit.dll und meine XML-Datei müssen im selben Verzeichnis sein, damit das Programm überhaupt startet. (Brauche ich den Rest dann überhaupt mit verteilen, wenn ich das Programm weitergebe?)
Kann ich das Ganze außerdem irgendwie so lösen, dass alles nötige bereits in der .exe steckt und diese allein läuft? Zumindest meine XML-Datei die ich ins Projekt eingebunden habe. Bei der .dll befürchte ich, dass es garnicht klappt.
Gruß Roper
Hallo zusammen,
ich versuche gerade eine XML-Datei einzulesen, die ich fest in mein Projekt eingebunden habe. Das Einlesen einer XML-Datei ist gar kein Problem und funktioniert wenn ich einen Pfad auf meiner Festplatte angebe.
Allerdings geht es mir darum eine zum Projekt hinzugefügte Datei zu benutzen. Die Datei befindet sich mit den Klassen usw. in der Projekmappe.
Einfach den Namen der Datei angeben scheint nicht zu funktionieren.
Hast du evtl. eingestellt dass die Änderungen erst übernommen werden wenn das Control den Fokus verliert?
Guter Tipp, ist wirklich so. Ist das beim Binding Standard oder kann ich den Zeitpunkt ändern?
Eingestellt habe ich jetzt nichts spezielles. Wobei laut Google LostFocus wohl standard ist!?
Wie verhält es sich eigentlich mit dem anderen Weg. Werden Eingaben in Controls auch wirklich immer direkt in das Property geschrieben. Ich habe jetzt teilweise das Verhalten, dass es scheinbar nicht so erfolgt.
Ok, danke. Jetzt habe ich das auch verstanden.
Musste INotifyPropertyChanged jetzt wegen des Objekts implementiert werden?
Bisher habe ich bei Bindings in anderen Programmen nur zwei Controls an einander gebunden (Slider und Textbox), wo das ganze nicht nötig war.
@Mr Evil: Ja, habe ich per MSDN gerade herausgefunden (deswegen zuerst der Fehler mit "does not implement").
Bei mir muss auch _OnPropertyChanged("pStatus"); _übergeben werden. (Habe meine Propertys mit p... benannt).
Funktioniert jetzt aber.^^
Danke
Ich hab das mal so in meinen Code eingebaut. Testweise auch nur beim Status-Property.
Allerdings bekomme ich den Fehler:
... does not implement interface member 'System.ComponentModel.INotifyPropertyChanged.PropertyChanged'
Zugegebnermaßen weiß ich auch nicht wirklich was ich da tue. Ich rufe beim Ändern der Variable im Set (logisch wird ja immer ausgeführt beim ändern der Variable) eine Funktion OnPropertyChanged. Was in diesem unteren Teil dann gemacht wird, ist mir allerdings nicht klar.
Mein pStatus innerhalb des _AktMaschine-Objekts sieht so aus:
public int pStatus
{
get { return status; }
set { status = value; }
}
Interessant ist, dass ich z.B. bei dem oberen Beispiel mit der Liste an meinem Datagrid die Zuweisung
grdNeu.DataContext = Liste;
neu machen muss, damit es problemlos funktioniert.
Kann das damit zusammenhängen, dass ich nicht wie ich es sonst immer sehe im XAML-Code per StaticResource-Verweis auf das Objekt verweise sondern das einfach zur Laufzeit per DataContext setze?
EDIT: Evtl. fehlt ähnlich wie bei meiner List<T> ein Ereignis, dass die Änderung auslöst!?
Ich bins nochmal.
Noch eine kleine Frage bezüglich des DataBinding.
Ich setze beim initialisieren meines Fensters am Start per
this.DataContext = _AktMaschine;
den DataContext.
Im XAML habe ich jetzt eine Reihe von Textfeldern mit Binding auf einzelne Eigenschaften des Objekts.
<TextBox Height="20" Name="txtStatus" Text="{Binding Path=pStatus, Mode=TwoWay}" TextChanged="txtStatus_TextChanged" />
Ich gebe hier per Path die Eigenschaft des Objekts an. Allerdings updatet sich trotz TwoWay das Feld nicht wie von mir gewünscht.
Im Internet sehe ich immer wieder das per StaticResource das Objekt selbst im XAML angegeben wird. Wie füge ich ein Objekt dort hinzu, sodass es auch bekannt ist. Und in wie weit spielt das Attribut UpdateSourceTrigger eine Rolle.
(Material zum Lesen gibt es zu WPF irgendwie relativ wenig. Bis auf ganz gute GalileoComputing eBooks, die aber nur kleine Kapitel aufwenden finde ich kaum etwas.)
Ok Danke.
Mit ObservableCollection statt einer List funktioniert es (für meinen Anwendungsfall) ohne weitere Änderungen.
Ja ich benutze eine List<T> mit eigenen Objekten darin.
Zu dieser Liste füge ich dann nur ganze Objekte hinzu, oder entferne sie wieder. Änderungen gibt es so garnicht.
Was genau ist eine ObservableCollection<T>, und in wie weit unterscheidet sich die Benutzung dann von meiner einfachen List<T>?
Auch wenn ich es wahrscheinlich nicht brauche. Wie darf ich mir das mit dem implementieren von INotifyPropertyChanged durch meinen Datentyp vorstellen? Ist das ein EventHandler?
Sorry dass ich den Thread hier noch einmal rauskrame.
Allerdings wollte ich jetzt ungern einen neuen Eröffnen.
Folgendes Problem:
Ich habe wie oben schon erwähnt eine Liste von Objekten an ein DataGrid gebunden, indem ich ItemsSource damit belegt habe.
Wenn ich jetzt die Liste von Objekten die angebunden ist, um weitere Objekte erweitere muss danach das DataGrid aktualisiert werden. Allerdings weiß ich nicht wie das funktionieren soll.
Die Liste neu an ItemsSource zuweisen scheint nicht zu funktionieren. Ich habe auch schon versucht den Mode als TwoWay anzugeben.
Hier der Code den ich aktuell benutze:
grdNeu.DataContext = Liste;
<toolkit:DataGrid Grid.Column="3" Grid.Row="1" Grid.RowSpan="2" CanUserSortColumns="False" Name="grdNeu" SelectionMode="Single" AutoGenerateColumns="False" BorderThickness="0" IsReadOnly="True">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Header="Status" Width="0.2*" Binding="{Binding Path=pStatus, Mode=TwoWay}" />
<toolkit:DataGridTextColumn Header="Lese" Width="0.2*" Binding="{Binding Path=pLese, Mode=TwoWay}" />
<toolkit:DataGridTextColumn Header="Schreibe" Width="0.2*" Binding="{Binding Path=pSchreibe, Mode=TwoWay}" />
<toolkit:DataGridTextColumn Header="Richtung" Width="0.2*" Binding="{Binding Path=pRichtung, Mode=TwoWay}" />
<toolkit:DataGridTextColumn Header="Neuer Status" Width="0.2*" Binding="{Binding Path=pStatusNeu, Mode=TwoWay}" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
Ja, habe es jetzt mit einer Schaltervariable wie im Beispiel gelöst.
Danke
Ich könnte dsa Problem umgehen, indem ich das Textfeld nicht binde sondern den Wert immer manuell zuweise.
Hiho,
ich hoffe zuerstmal ich bin hier richtig und es gehört nicht in die GUI Sektion.
Meine Frage betrifft folgendes. Ich habe ein TextBox-Control und möchte innerhalb des TextChanged-Ereignisses wissen ob der Benutzer eine Eingabe getätigt hat, oder ob per Programm ein neuer Wert zugewiesen wurde.
Gibt es irgendein Unterscheidungsmerkmal dafür?
Die TextBox ist per Binding einer Objekteigenschaft zugeordnet und soll bei Eingaben durch den Benutzer eine bestimmte Variable setzen.
Gruß Roper
Ok klappt super.
Danke, hat mir sehr geholfen.
Das WPF DataGrid hat die Eigenschaft ItemsSource. Dort habe ich meine Liste jetzt mal zugewiesen. Es funktioniert an sich jetzt auch.
Vorher sah mein Grid so aus:
grdProg.DataContext = _AktMaschine.pProgramm;
<toolkit:DataGrid CanUserSortColumns="False" Name="grdProg" SelectionMode="Single">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Header="Status" Width="0.2*" Binding="{Binding Path=pStatus}" />
<toolkit:DataGridTextColumn Header="Lese" Width="0.2*" Binding="{Binding Path=pLese}" />
<toolkit:DataGridTextColumn Header="Schreibe" Width="0.2*" Binding="{Binding Path=pSchreibe}" />
<toolkit:DataGridTextColumn Header="Richtung" Width="0.2*" Binding="{Binding Path=pRichtung}" />
<toolkit:DataGridTextColumn Header="Neuer Status" Width="0.2*" Binding="{Binding Path=pStatusNeu}" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid
Jetzt habe ich es einfach so angegeben:
grdProg.ItemsSource = _AktMaschine.pProgramm;
<toolkit:DataGrid CanUserSortColumns="False" Name="grdProg" SelectionMode="Single" AutoGenerateColumns="True"/>
Allerdings werden die Spalten ja jetzt vom Code erstellt. Ich habe also keinen Einfluss auf den Titel oder den Typ der Spalte (CheckBox-Spalte etc.).
Müssen derartige Änderungen jetzt nach der Zuweisung im Code vorgenommen werden?
Und da ich jetzt kein Binding habe. Wie verhält sich das ganze bei Änderungen der Werte? (Bei Bindings habe ich ja Modi wie TwoWay etc.)
Gruß Roper
EDIT: Habe noch etwas getestet. Wenn ich die ColumnHeader Definitonen wie oben drin lasse und ItemsSource benutze, habe ich dank Binding auch die richtigen Werte drin. Allerdings habe ich dann die doppelte Anzahl Spalten, weil diese mit ihren Eigenschaftsnamen nocheinmal eingefügt werden.
Hallo zusammen,
ich beschäftige mich jetzt neuerdings mit WPF. Dabei gibt es ja die Möglichkeit Objekte (bzw. bestimmte Eigenschaften) an ein Control zu binden.
In meinem speziellen Fall sieht es folgendermaßen aus. Ein Objekt hat eine Eigenschaft, die eine Liste aus anderen Objekten bildet. Diese Objekte haben Eigenschaften die ich einem DataGrid zuweisen möchte (DataGrid aus dem WPFToolkit).
Zur Veranschaulichung ein kleines Beispiel wie meine Klassen aussehen (in Wahrheit etwas abstrakter):
Die Klasse Haus hat als Eigenschaft Fenster eine Liste von Fenster Objekten. Die Fenster Objekte haben wiederum Eigenschaften wie z.B. string Farbe; usw.
Jetzt ginge es mir darum die Eigenschaften wie Farbe usw. an das DataGrid zu binden.
Dazu setze ich den DataContext auf das Property, dass die Liste darstellt:
DataGrid.DataContext = Haus.FensterListe;
Innerhalb von XAML würde ich dann bei den einzelnen ColumnHeadern per Path die Eigenschaften der Fenster-Objekte angeben:
<toolkit:DataGridTextColumn Header="Farbe" Width="0.2*" Binding="{Binding Path=FensterFarbe}" />
So zeigt sich allerdings keine Reaktion. Ich frage mich natürlich in wie weit das DataGrid so einfach mit der Liste von Objekten umgehen kann, die ich an es übergebe.
Bei dieser Gelegenheit habe ich noch eine Frage am Rande. Gibt es eine Möglichkeit Controls auf dem WPF Window von einer Klasse heraus anzusprechen?
So einfach kann ich ja aus einer Klasse z.B. Haus heraus kein Textfeld auf der Form benutzen. Gibt es eine Möglichkeit dies zu bewerkstelligen?
Gruß Roper