Laden...

Forenbeiträge von brocel1 Ingesamt 9 Beiträge

09.07.2015 - 19:09 Uhr

Naja. Besser als gar nicht 😄. In der Funktion 'andereKlasse.startMeasurementFileAnalysis(); //Auswertung' wird ein anderes Programm zur Auswertung von Messdateien durch einen Prozess gestartet. Dies dauert dann zwischen 30 sek und 3 min. Erst wenn das Programm durchgelaufen ist, wird die Funktion 'andereKlasse.startMeasurementFileAnalysis();' beendet und dann durch die Funktion 'if (ct.IsCancellationRequested) return;' der Task beendet.

09.07.2015 - 18:50 Uhr

Ich seh nirgends, dass Du OperationCanceledException fängst.
Task-Kaskadierung ist übrigens ein Thema, das Microsoft als Pitfall deklariert.


private void startAnalysis()
{
   bool state = true;
   cts = new CancellationTokenSource();
   ct = cts.Token; //global angelegt
   try{
      //AuswerteTask starten          
      startAnalysisTask = Task.Factory.StartNew(() =>
      {
         //Task, um Token zu überwachen, ob Abbruchanforderung gestellt wird
         Task waitForTaskCancelling = Task.Factory.StartNew(() =>
          {
              try{
                 while (state)
                 {   
                      if (ct.IsCancellationRequested)
                      {
                         ct.ThrowIfCancellationRequested(); //Löst OperationCanceledException aus
                      }
                  }
              catch(OperationCanceledException ex){
                  throw ex; //Exception weiterleiten
              }
          });

         function1(); //DoSomething
         function2(); //DoSomething
         foreach(var element in liste){
            andereKlasse.startMeasurementFileAnalysis();     //Auswertung
         }
         andereKlasse.createTestReport();  //Testreport erstellen
         state = false; //Beenden des Überwachungsstasks
      }, ct);
   }
   catch(OperationCanceledException ex){
      return; 
   }
}

Hab nun den try-catch-Block hinzugefügt. Die Exception wirft er auch, aber sie kommt nicht im überliegenden catch-block an und der startAnalysisTask läuft trotzdem weiter...

warum kannst nicht nach jedem Schritt auf den Abbruch prüfen?

Das geht auch. Problem ist nur, dass er auf jeden Fall noch die Methode, in der gerade steckt bis zum Ende durchläuft und erst dann die Hauptmethode verlässt. Und ich muss halt bei wirklich jeder Funktion abprüfen...


private void startAnalysis()
{
   
   cts = new CancellationTokenSource();
   ct = cts.Token; //global angelegt

   //AuswerteTask starten          
   startAnalysisTask = Task.Factory.StartNew(() =>
   {     

      function1(); //DoSomething
      if (ct.IsCancellationRequested) return; 

      function2(); //DoSomething
      if (ct.IsCancellationRequested) return;

      foreach(var element in liste){
         andereKlasse.startMeasurementFileAnalysis();     //Auswertung
         if (ct.IsCancellationRequested) return;
      }

      if (ct.IsCancellationRequested) return;
      andereKlasse.createTestReport();  //Testreport erstellen
      
   }, ct)
}

09.07.2015 - 15:05 Uhr

Ich habe meinen Task nun etwas umgeschrieben und verzichte auf weitere Tasks, die ich in diesem Task aufrufe. Sieht nun wie folgt aus:


startAnalysisTask = Task.Factory.StartNew(() =>
{
   function1(); //DoSomething
   function2();  //DoSomething
   foreach(var element in liste){
      andereKlasse.startMeasurementFileAnalysis();     //Auswertung
   }
   andereKlasse.createTestReport();   //Testreport erstellen 
})

Damit ich im Hintergrund überprüfen kann, ob eine Abbruchanforderung gefordret wird, benutze ich einen seperaten Task. Hier die gesamte Funktion in der beide Tasks laufen:


private void startAnalysis()
{
   bool state = true;
   cts = new CancellationTokenSource();
   ct = cts.Token; //global angelegt

   //AuswerteTask starten           
   startAnalysisTask = Task.Factory.StartNew(() =>
   {
      //Task, um Token zu überwachen, ob Abbruchanforderung gestellt wird
      Task waitForTaskCancelling = Task.Factory.StartNew(() =>
       {
           while (state)
           {
                if (ct.IsCancellationRequested)
                {
                   ct.ThrowIfCancellationRequested(); //Löst OperationCanceledException aus
                }
       });                        
                     
      function1(); //DoSomething
      function2(); //DoSomething
      foreach(var element in liste){
         andereKlasse.startMeasurementFileAnalysis();     //Auswertung
      }
      andereKlasse.createTestReport();  //Testreport erstellen
      state = false; //Beenden des Überwachungsstasks
   }, ct)
}

Und über den Button führ ich nun die Anforderung aus:


private void cancelAnalysisButton_Click(object sender, EventArgs e)
{            
   cts.Cancel();
}

Durch die Cancellationrequest bekommt der Überwachungstask eine Information, dass ct eine Exception werfen soll und beendet dadruch den Task. So viel zur Theorie. Nur der Task wird nicht beendet -.-.

Weiß jemand wieso? Problem ist auch, wenn z.B. die Funktion andereKlasse.createTestReport() ausgeführt wird und der Task beendet wird, läuft diese trotzdem weiter. Eigentlich möchte ich ja, dass alles abrupt beendet wird. Hat dazu jemand vlt. eine Idee?

09.07.2015 - 09:21 Uhr

Innerhalb des Task werden Funktionen aufgerufen und eine foreach-Schleife verwendet, die für jede übergebene Messdatei eine Auswertung innerhalb eines untergeordneten Tasks abarbeitet. Nach der Schleife wird ein Task zum Erstellen einer Exceldatei verwendet. Also ungefähr so:


startAnalysisTask = Task.Factory.StartNew(() =>
{
   function1();
   function2();
   foreach(var element in liste){
      Task startAnalysis = Task.Factory.StartNew(() => andereKlasse.startMeasurementFileAnalysis(), ct);
      startAnalysis.Wait();
   }
   testReportTask = Task.Factory.StartNew(() => andereKlasse.createTestReport(), ct);
   testReportTask.ContinueWith((x) =>
                     {
                        //DoSomething
   });
}

Ich kann also nicht dauerhaft prüfen, ob


if (ct.IsCancellationRequested)
{
   ct.ThrowIfCancellationRequested();
}

Auf jeden Fall sollten alle Tasks beendet werden.

09.07.2015 - 08:22 Uhr

Guten Morgen,

wie der Titel es schon beschreibt suche ich nach einer Möglichkeit, einen Task über einen Button zu beenden. Hintergrund ist, dass der Task eine Auswertung durchführt. Wenn jemand versehentlich etwas falsches eingestellt hat, soll er die Möglichkeit haben, die Auswertung sofort zu beenden. Dazu muss also nur der laufende Task beendet werden. Nur wie?

Ich benutze ein WinForm, in der die Funktion 'startAnalysis()' aufgerufen wird. Diese startet den Auswertealgorithmus. Dieser wird sequentiell abgearbeitet. Daher kann ich z.B. keine while-Schleife verwenden und prüfen, ob 'ct.IsCancellationRequested' stattgefunden hat.


 private void startAnalysis()
        {            
            cts = new CancellationTokenSource();
            CancellationToken ct = cts.Token;
            startAnalysisTask = Task.Factory.StartNew(() =>
            {
                  //DoWork;
                  //Es wird keine Schleife verwendet
            }, ct);
        }
 

Damit mein Button später den Task kennt, wird dieser vorher deklariert.


public partial class MainForm : Form
    {
          private Task startAnalysisTask;
          private CancellationTokenSource cts;
          public MainForm()
         {
               //MainForm-Class
          }
    }

Über einen Button wird das Click-Event aufgerufen:


 private void cancelAnalysisButton_Click(object sender, EventArgs e)
        {
            if (startAnalysisTask.Status == TaskStatus.Running)
            {
                cts.Cancel();
                Console.WriteLine(startAnalysisTask.Status); //already running :(
               
            }
            buttonSperren(cancelAnalysisButton);
        }

Der Aufruf funktioniert einwandfrei, nur bringt mir das nichts, da der Task nach cts.Cancel() immer noch den Status 'running' besitzt und daher nicht beendet wurde. Weiß jemand, an was das liegt und hat vlt. einen Verbesserungsvorschlag oder eine andere Idee, den Task zu beenden?

Ich bedanke mich für eure Aufmerksamkeit.

03.06.2015 - 14:09 Uhr

Scheinbar hab ich etwas gravierendes vergessen. Und zwar muss MyObject das Interface "BusinessObject" implementieren. Dieses wiederrum implementiert die Interfaces "INotifyPropertyChanged, IDataErrorInfo, IEditableObject".

Ohne das Interface "Businessobject" funktioniert es nämlich nicht. Die Klasse habe ich aus einem anderen Projekt entnommen, welches mein Vorgänger erstellt hatte. Diese benötigt noch die Klasse "Rule". Im folgenden könnt ihr beide Klassen begutachten. Vielleicht kann hier jemand erklären, wieso diese zusätzlich noch notwendig sind.


[Serializable()]
    public abstract class BusinessObject : INotifyPropertyChanged, IDataErrorInfo, IEditableObject
    {
        [NonSerialized()]
        private List<Rule> rules;
        [NonSerialized()]
        private MemoryStream memoryStream;
        [NonSerialized()]
        private BinaryFormatter formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));

        private bool hasBrokenRulesThatMustBeFollowed = false;
        private bool editing = false;

        /// <summary>
        /// Constructor.
        /// </summary>
        public BusinessObject()
        {
        }

        public bool HasBrokenRulesThatMustBeFollowed
        {
            get { return hasBrokenRulesThatMustBeFollowed; }
            protected set { hasBrokenRulesThatMustBeFollowed = value; }
        }

        /// <summary>
        /// Gets a value indicating whether or not this domain object is valid. 
        /// </summary>
        public virtual bool IsValid
        {
            get { return this.Error == null; }
        }        

        /// <summary>
        /// Gets an error message indicating what is wrong with this domain object. The default is an empty string ("").
        /// </summary>
        public virtual string Error
        {
            get
            {
                string result = this[string.Empty];
                if (result != null && result.Trim().Length == 0)
                    result = null;

                return result;
            }
        }

        /// <summary>
        /// Gets the error message for the property with the given Name.
        /// </summary>
        /// <param Name="propertyName">The Name of the property whose error message to get.</param>
        /// <returns>The error message for the property. The default is an empty string ("").</returns>
        public virtual string this[string propertyName]
        {
            get
            {
                var builder = new StringBuilder();

                 propertyName = CleanString(propertyName);

                foreach (Rule r in GetBrokenRules(propertyName))
                    if (propertyName == string.Empty || r.PropertyName == propertyName)
                    {
                        builder.AppendLine(r.Description);
                    }

                return builder.Length != 0 ? builder.ToString() : null;
            }
        }

        /// <summary>
        /// Validates all rules on this domain object, returning a list of the broken rules.
        /// </summary>
        /// <returns>A read-only collection of rules that have been broken.</returns>
        public virtual ReadOnlyCollection<Rule> GetBrokenRules()
        {
            return GetBrokenRules(string.Empty);
        }

        /// <summary>
        /// Validates all rules on this domain object for a given property, returning a list of the broken rules.
        /// </summary>
        /// <param Name="property">The Name of the property to check for. If null or empty, all rules will be checked.</param>
        /// <returns>A read-only collection of rules that have been broken.</returns>
        public virtual ReadOnlyCollection<Rule> GetBrokenRules(string property)
        {
            hasBrokenRulesThatMustBeFollowed = false;

            property = CleanString(property);

            // If we haven't yet created the rules, create them now.
            if (rules == null)
            {
                rules = new List<Rule>();
                rules.AddRange(this.CreateRules());
            }
            List<Rule> broken = new List<Rule>();

            foreach (Rule r in this.rules)
                // Ensure we only validate a rule 
                if (r.PropertyName == property || property == string.Empty)
                {
                    bool isRuleBroken = !r.ValidateRule(this);
                    //Debug.WriteLine(DateTime.Now.ToLongTimeString() + ": Validating the rule: '" + r.ToString() + "' on object '" + this.ToString() + "'. Result = " + ((isRuleBroken == false) ? "Valid" : "Broken"));
                    if (isRuleBroken)
                    {
                        broken.Add(r);
                        if (r.RuleMustBeFollowed)
                            hasBrokenRulesThatMustBeFollowed = true;
                    }
                }

            return broken.AsReadOnly();
        }

        /// <summary>
        /// Occurs when any properties are changed on this object.
        /// </summary>
        [field:NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Override this method to create your own rules to validate this business object. These rules must all be met before 
        /// the business object is considered valid enough to save to the data store.
        /// </summary>
        /// <returns>A collection of rules to add for this business object.</returns>
        protected virtual List<Rule> CreateRules()
        {
            return new List<Rule>();
        }

        /// <summary>
        /// A helper method that raises the PropertyChanged event for a property.
        /// </summary>
        /// <param Name="propertyNames">The names of the properties that changed.</param>
        protected virtual void NotifyChanged(params string[] propertyNames)
        {
            foreach (string name in propertyNames)
                OnPropertyChanged(new PropertyChangedEventArgs(name));

            OnPropertyChanged(new PropertyChangedEventArgs("IsValid"));
        }

        /// <summary>
        /// Cleans a string by ensuring it isn't null and trimming it.
        /// </summary>
        /// <param name="s">The string to clean.</param>
        protected string CleanString(string s)
        {
            return (s ?? string.Empty).Trim();
        }

        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, e);
        }

        protected void createSnapShot(object item)
        {
            if (!editing)
            {
                memoryStream = new MemoryStream();
                if (formatter == null)
                    formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
                formatter.Serialize(memoryStream, item);

                editing = true;
            }
        }

        protected void restoreSnapshot<T>(ref T item)
        {
            if (editing)
            {
                memoryStream.Position = 0;
                item = (T)formatter.Deserialize(memoryStream);

                editing = true;
            }
        }

        protected void throwAwaySnapshot()
        {
            if (memoryStream != null)
            {
                memoryStream.Dispose();
                memoryStream = null;
            }

            editing = false;
        }

        #region IEditableObject Member

        public virtual void BeginEdit()
        {
            // Childklassen sind für die Implementierung zuständig
        }

        public virtual void CancelEdit()
        {
            // Childklassen sind für die Implementierung zuständig
        }

        public virtual void EndEdit()
        {
            throwAwaySnapshot();         
        }

        #endregion
    }

Klasse Rule:


/// <summary>
    /// An abstract class that contains information about a rule as well as a method to validate it.
    /// </summary>
    /// <remarks>
    /// This class is primarily designed to be used on a domain object to validate a business rule. In most cases, you will want to use the 
    /// concrete class SimpleRule, which just needs you to supply a delegate used for validation. For custom, complex business rules, you can 
    /// extend this class and provide your own method to validate the rule.
    /// </remarks>
    [Serializable]
    public abstract class Rule
    {
        private string _description;
        private string _propertyName;
        private bool _ruleMustBeFollowed = false;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="propertyName">The Name of the property the rule is based on. This may be blank if the rule is not for any specific property.</param>
        /// <param name="brokenDescription">A description of the rule that will be shown if the rule is broken.</param>
        public Rule(string propertyName, string brokenDescription)
        {
            this.Description = brokenDescription;
            this.PropertyName = propertyName;
        }

        public Rule(string propertyName, string brokenDescription, bool ruleMustBeFollowed)
        {
            this.Description = brokenDescription;
            this.PropertyName = propertyName;
            this._ruleMustBeFollowed = ruleMustBeFollowed;
        }

        public bool RuleMustBeFollowed
        {
            get { return _ruleMustBeFollowed; }
        }

        /// <summary>
        /// Gets descriptive text about this broken rule.
        /// </summary>
        public virtual string Description
        {
            get { return _description; }
            protected set { _description = value; }
        }

        /// <summary>
        /// Gets the Name of the property the rule belongs to.
        /// </summary>
        public virtual string PropertyName
        {
            get { return (_propertyName ?? string.Empty).Trim(); }
            protected set { _propertyName = value; }
        }

        /// <summary>
        /// Validates that the rule has been followed.
        /// </summary>
        public abstract bool ValidateRule(BusinessObject domainObject);

        /// <summary>
        /// Gets a string representation of this rule.
        /// </summary>
        /// <returns>A string containing the description of the rule.</returns>
        public override string ToString()
        {
            return this.Description;
        }

        /// <summary>
        /// Serves as a hash function for a particular type. System.Object.GetHashCode()
        /// is suitable for use in hashing algorithms and data structures like a hash
        /// table.
        /// </summary>
        /// <returns>A hash code for the current rule.</returns>
        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }
    }

Somit die Klasse MyObject:


public class MyObject: BusinessObject
    {
//...
    }

03.06.2015 - 10:53 Uhr

Ich habe es hinbekommen:

Zuerst erstelle ich mir eine bindingSource - entweder über den WinForm-Designer - und manuell.
Diese verwaltet alle Bindungen für jedes Windows-Steuerelement:


BindingSource bindingSource = new BindingSource();

Beim Öffnen des Forms, dass die Textfeldern beinhaltet (MyForm), wird der bindingSource die Datenquelle hinzugefügt. MyObject ist eine Klasse, in der Variablen sowie deren dazugehörigen set-/get-Methode deklariert/implementiert sind.

public class MyObject
    {        
        private string userName = string.Empty;
        private string departmentCode = string.Empty;
        //...
        public string UserName { get { return userName; } set { userName = value; } }
        public string DepartmentCode { get { return departmentCode; } set { departmentCode = value; } }

        //...
}
public MyForm()
        {
            InitializeComponent();
            bindingSource.DataSource = MyObject;
       ...
}

Da wir in oben gezeigten Fall zwei Textfelder haben müssen wir diese auch mit unserer bindingSource verknüpfen. Dies geschieht durch:


userTextEdit.DataBindings.Add(new Binding("Text", bindingSource, "UserName"));
departmentTextEdit.DataBindings.Add(new Binding("Text", bindingSource, "DepartmentCode"));

Binding(...) setzt für DataSourceUpdateMode den Defaultwert auf "OnValidation". Das heißt, immer wenn das Textfeld validiert wurde, wird die bindingSource informiert. Wenn wir aber unser Form durch den Button "Abbrechen" oder über das X schließen, soll dies nicht geschehen. Daher müssen wir für jedes Textfeld den DataSourceUpdateMode auf "Never" setzten.

Dazu habe ich mir eine Hilfsklasse erstellt:


 public static class DataBindingUtils
    {
        public static void SuspendTwoWayBinding(BindingManagerBase bindingManager)
        {
            if (bindingManager == null)
            {
                throw new ArgumentNullException("bindingManager");
            }

            foreach (Binding b in bindingManager.Bindings)
            {
                b.DataSourceUpdateMode = DataSourceUpdateMode.Never;
            }
        }

        public static void UpdateDataBoundObject(BindingManagerBase bindingManager)
        {
            if (bindingManager == null)
            {
                throw new ArgumentNullException("bindingManager");
            }

            foreach (Binding b in bindingManager.Bindings)
            {
                b.WriteValue();
            }
        }
    }

Die Klasse DataBindingUtils besitzt zwei Funktionen. Die Funktion "SuspendTwoWayBindung" sorgt dafür, dass der DataSourceUpdateMode aller Bindungen der bindingSource auf "Never gesetzt wird". Die Funktion "UpdateDataBoundObject" sorgt dafür, dass die Werte in die bindingSource übernommen werden.

Die Funktion muss nach dem Festlegen der Bindungen aufgerufen werden!

Also:


public partial class MyForm: Form
    {               
        private BindingSource bindingSource = new BindingSource();
        public MyForm()
        {
            InitializeComponent();
            bindingSource.DataSource = MyObject;

            userTextEdit.DataBindings.Add(new Binding("Text", bindingSource, "UserName"));
            departmentTextEdit.DataBindings.Add(new Binding("Text", bindingSource, "DepartmentCode"));

            DataBindingUtils.SuspendTwoWayBinding(this.BindingContext[bindingSource]);
        }     

Wird nun etwas in die Textfelder eingegeben, so wird die BindingSource nicht informiert. Wird X oder Abbrechen gedrückt, so werden die Änderungen nicht übernommen. Schon mal ein kleines Erfolgserlebnis.

Damit die Änderungen übernommen werden muss beim Drücken des Buttons "Änderungen übernehmen" die andere Funktion der Hilfsklasse "UpdateDataBoundObject(..)" aufgerufen werden.


private void okButton_Click(object sender, EventArgs e)
        {
            DataBindingUtils.UpdateDataBoundObject(this.BindingContext[bindingSource]);
        }

Diese fordert alle Bindungen auf, den aktuellen Wert des Steuerelements (in unserem Fall der Text der Textfelder) der bindingSource zu übergeben. Somit wird nur dann, wenn "Änderungen übernehmen" gedrückt wird die bindingSource informiert.

Und genau das wollte ich. Daher bedanke ich für eure Vorschläge. Einen Weg mit dem IEditObject (Begin,Cancel,End -Edit) würde mich auch interessieren. Ich selber bin damit nicht weiter gekommen..

01.06.2015 - 13:59 Uhr

Wenn ich die Eigenschaft auf OnValidation setzte, updatet er meine DataSource exakt dann, wenn ich das Textfeld verlasse.

  userTextEdit.DataBindings.Add(new Binding("EditValue",bindingSource,"UserName",true,DataSourceUpdateMode.OnValidation));

Um dies zu verhindern, kann ich für das Textfeld folgende Option festlegen:

this.userTextEdit.CausesValidation = false;

Wenn ich nun den Button "Änderungen übernehmen" drücke, wird das Click-Event aufgerufen.

private void simpleButton2_Click(object sender, EventArgs e)
        {     
            userTextEdit.DoValidate();
            //hier weitere Textfelder updaten
        }

Problem nur ist, dass er nur den Wert eines Textfelder übernimmt und nicht alle. Das gleiche passiert aber auch, wenn ich auf X drücke. Lösche ich alles raus und drücke den X-Button, so steht in einem der Wert - im anderen nicht mehr.

@FZelle:
Das Attribute bei C# eine andere Bedeutung haben, war mir bislang nicht klar. Danke für den Hinweiß.

Ich versuche es mal mit INotifyPropertyChanged Interface umzusetzten.

01.06.2015 - 11:33 Uhr

Hallo Forummitglieder,

derzeit programmiere ich in c# und Win Forms und habe ein Problem. Und zwar folgendes:

Ich habe ein Main-Form, über das sich ein weiteres Form über den Button "Konfiguration" öffnen lässt. Dieses besteht aus Labels und Textboxen besteht. Jeder Textbox ist an einem Objekt gebunden:

 userTextEdit.DataBindings.Add(new Binding("Text",Settings.Instance,"UserName",true,DataSourceUpdateMode.OnPropertyChanged));

Das Objekt Settings.Instance besitzt verschiedene Attribute sowie die dazugehörigen get/set-Methoden:

private string userName = string.Empty;
public string UserName { get { return userName; }set { userName = value; } }

Wenn ich nun etwas in die Textbox eingebe, wird über die Datenbindung der Wert in die Variable zurückgeschrieben und gespeichert. Wenn ich das Form schließe und wieder öffne wird der neue Wert der Variable auch in der Textbox angezeigt. Soweit so gut.

Nun möchte ich aber, dass er diese Werte nur übernimmt, wenn ich den Button "Änderungen übernehmen" drücke und nicht direkt bei der Eingabe. Würde man etwas ändern und z.B. das X-Symbol oder den Button "Abbrechen" drücken, so dürfte nichts übernommen werden und der alte Wert sollte wieder darin stehen. Doch das tut es leider 🙁

Ich habe auch schon probiert, DataSourceUpdateMode.OnValidation zu verwenden, leider ohne Erfolg. Hier werden auch alle Änderungen direkt übernommen, wenn das Textfeld verlassen wird.
Würde ich die Option CausesValidation=false für jedes Textfeld setzen, dann würden zwar die Textfelder nur validieren werden, wenn ich eine entsprechende DoValidate-Methode an den Textfeldern aufrufe, wenn der Button gedrückt wird, dennoch werden auch in diesem Fall nicht alle Werte korrekt gespeichert.

Ich habe dazu ein Bild angehängt, um den Sachverhalt etwas zu verdeutlichen.

Meine Frage besteht nun darin, wie ich die DataBinding-Methode aufrufen muss, damit die Werte zwar gespeichert werden, aber nur dann, wenn auch der Button "Änderungen übernehmen" gedrückt wird?