Why bind item is not updated after control value is changed? - c#

I'm having a hard time binding properties. I'm probably missing something here. The thing is that my object properties are not updated by the interface control changes until I do another interaction with the UI, like press an empty dummy button. This is really odd. What I'm missing here? How can I make the bind property to be updated on control checked change?
Sample:
Create an empty Form, add a CheckBox and a button.
Bind the Checkbox to the IsOnSale Car property.
Add a console writeline to see when IsOnSale property will be changed.
Build and click on the Checkbox, IsOnSale wont change (no console msg) until a click on another button or something.
Click on the dummy button, the IsOnSale property will be changed! What!??
private Car car;
public Form1()
{
InitializeComponent();
car = new Car();
car.IsOnSale = false;
checkBox1.DataBindings.Add("Checked", car, "IsOnSale");
}
//U dont need this for the test..
private void btnPrintStatus_Click(object sender, EventArgs e)
{
string s = car.ToString();
Console.WriteLine(s);
}
Car is just a class that implements INotifyPropertyChanged. Also, It prints on console every time a property change request is made (just for debugging). As you can see if you build the sample, the change request is only made after a click on the dummy button...
internal class Car : INotifyPropertyChanged, INotifyPropertyChanging
{
private string name;
public string Name
{
get { return name; }
set
{
string propName = GetName(new { Name });
UpdateField(ref name, value, propName, null);
}
}
private bool isOnSale;
public bool IsOnSale
{
get { return isOnSale; }
set
{
string propName = GetName(new { IsOnSale });
UpdateField(ref isOnSale, value, propName, null);
}
}
public override string ToString()
{
return string.Format("Car Name: {0}. IsOnSale: {1}", Name, IsOnSale);
}
#region INotifyPropertyChanged, INotifyPropertyChanging
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
public static string GetName<T>(T item) where T : class
{
return typeof(T).GetProperties()[0].Name;
}
protected bool UpdateField<T>(ref T field, T newValue, string propertyName, Action action = null)
{
bool needChange = !EqualityComparer<T>.Default.Equals(field, newValue);
Console.WriteLine("Try to UpdateField {0}. Need update? {1}", propertyName, needChange);
if (needChange)
{
OnPropertyChanging(propertyName);
field = newValue;
if (action != null)
action();
OnPropertyChanged(propertyName);
}
return needChange;
}
protected void OnPropertyChanging(string propertyName)
{
PropertyChangingEventHandler handler = PropertyChanging;
if (handler != null)
handler(this, new PropertyChangingEventArgs(propertyName));
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
EDIT - workaround
I have been able to find a work around, but it smells..
I'm using a BackgroundWorker to "get out" of the change event and after that I perform a click on a dummy button.
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
//Wont work, need to get out of this event.
//btnDummy.PerformClick();
//Using another thread to get out of this event:
RunAsyncBindFixer();
}
private void RunAsyncBindFixer()
{
var workerBindFixer = new BackgroundWorker();
workerBindFixer.DoWork += WorkerBindFixer_DoWork;
workerBindFixer.RunWorkerCompleted += WorkerBindFixer_RunWorkerCompleted;
Console.WriteLine("Starting RunAsyncBindFixer");
workerBindFixer.RunWorkerAsync();
}
void WorkerBindFixer_DoWork(object sender, DoWorkEventArgs e)
{
//Aparently we need nothing here.
//Thread.Sleep(0);
}
void WorkerBindFixer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("RunAsyncBindFixer RunWorkerCompleted");
btnDummy.PerformClick();
//Checked change on a dummy checkbox wont work also
//ckbDummy.Checked = !ckbDummy.Checked;
Console.WriteLine("Dummy action applied");
}

Databinding defaults to writing the value whenever the bound control is validated, which happens normally when the control loses focus (such as when a button is pressed as in your example)
You can force the databinding to use the onpropertychanged of the control provided the control supports it (in this case CheckedChanged , which is supported by the checkbox control)
checkBox1.DataBindings.Add("Checked", car, "IsOnSale", true, DataSourceUpdateMode.OnPropertyChanged);

Related

c# WinForms DataBinding doesn't update my TextBox.text

I wanna bind a TextBox to a class property, so when this property changes, my TextBox changes automatically too (Windows Forms).
I have a class like this:
class Device : INotifyPropertyChanged
{
private string can_rpm;
public string Can_rpm
{
get { return can_rpm; }
set { can_rpm = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
\\lots of other codes
}
My main form has some code like this (with a textbox called 'tbTest'):
private void Form1_Load(object sender, EventArgs e)
{
Device device= device = new Device();
tbTest.DataBindings.Clear();
tbTest.DataBindings.Add(new Binding("Text",device,"Can_rpm",true,DataSourceUpdateMode.OnPropertyChanged));
\\lots of other stuff
}
My problem: My textBox never updates! A have some other code that updates the 'Can_rpm' property, but nothing shows on my textbox.text. BUT, if I change the empty value of my textbox to something else, my property DOES change too!
So it's working 'one way', but not the other!
I've searched here and googled it, but all I find is examples that does what is already done in my code, but mine doesn't work.
Thanks for helping if you can.
Try with this:
tbTest.DataBindings.Add(nameof(TextBox.Text), device, nameof(Device.Can_rpm));
I've tested the code with your Device class code and this code in the form constructor:
var device = new Device();
this.textBox1.DataBindings.Add(nameof(TextBox.Text), device, nameof(Device.Can_rpm));
device.Can_rpm = "Hello";
After that, my textbox has "Hello" text.
UPDATE
You need update controls always in the thread in which they was created, usually in the main thread. I use a Form extension methods to do that:
public static class FormExtends
{
public static void RunInMyThread(this Form form, Action operation)
{
if (form.InvokeRequired)
{
form.BeginInvoke(operation);
}
else
{
operation();
}
}
}
With the previous extension, you can do (in your Form code) your updates in this way:
this.RunInMyThread(() => device.Can_rpm = "Hello");
Another way to do that:
public class Device : INotifyPropertyChanged
{
private static SynchronizationContext GuiContext = SynchronizationContext.Current;
private string can_rpm;
public string Can_rpm
{
get { return can_rpm; }
set { can_rpm = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
GuiContext.Post(
s => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)),
null);
}
}
}
GuiContext is initialized in the main thread so it runs the code in that thread. If you change your PropertyChanged event to throw in the Post of that context, you don't need take care about where your device properties are changed because the notiy always run in the main thread.

Detect the source of CheckChanged of a checkbox in Xamarin Forms

The CheckedChanged event of a CheckBox is fired when the page is loading up (on data binding), in addition to the user actually checking/unchecking.
Is there a way to detect whether CheckedChanged event was fired by a user action or otherwise?
In WinForms/WPF, I used to define a boolean variable like Loading = true; and then used to set Loading = false; once page load is finished. Then, the CheckedChanged event would check if Loading is false to execute the logic. Here's how I used to do it:
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
if (Loading == true) return;
//execute logic...
}
With async functions all over, I am unable to fit the Loading trick correctly. Is there a better way?
In WinForms/WPF, I used to define a boolean variable like Loading = true; and then used to set Loading = false; once page load is finished. Then, the CheckedChanged event would check if Loading is false to execute the logic. Here's how I used to do it:
I change bool userchecked value before and after setting BindingContext = new checkmodel(), to To detect whether the CheckBox_CheckedChanged first time trigger is user or data binding.
public partial class Page18 : ContentPage
{
private bool userchecked;
public Page18()
{
InitializeComponent();
userchecked = false;
this.BindingContext = new checkmodel();
userchecked = true;
}
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
if(userchecked==true)
{
Console.WriteLine("user checked!");
}
else
{
Console.WriteLine("don't user checked!");
}
}
}
public class checkmodel:ViewModelBase
{
private bool _ischecked;
public bool ischecked
{
get { return _ischecked; }
set
{
_ischecked = value;
RaisePropertyChanged("ischecked");
}
}
public checkmodel()
{
ischecked = true;
}
}
The ViewModelBase is class that implementing INotifyPropertyChanged, to notify data update.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Why does raising the PropertyChanged event cause other controls to update

I have a view model that has several properties that are databound to several controls.
When I raise PropertyChanged on one of them, the controls unexpectedly all update. I would expect only the one I am raising the event on to update.
For my form, I have this:
public partial class MainForm : Form
{
AmountCalculatorVM amountCalculatorVM;
public MainForm()
{
InitializeComponent();
}
private void setBindings()
{
textBoxTotalAmount.DataBindings.Add("Text", amountCalculatorVM, "TotalAmount");
textBoxAverage.DataBindings.Add("Text", amountCalculatorVM, "Average",true, DataSourceUpdateMode.Never,null, "#.00");
textBoxCount.DataBindings.Add("Text", amountCalculatorVM, "Count");
listBoxLineAmounts.DataSource = amountCalculatorVM.Amounts;
}
private void MainForm_Load(object sender, EventArgs e)
{
amountCalculatorVM = new AmountCalculatorVM();
setBindings();
}
private void buttonAddAmount_Click(object sender, EventArgs e)
{
if (int.TryParse(textBoxLineAmount.Text.Replace(",", ""), out int amount))
{
amountCalculatorVM.Amounts.Add(amount);
textBoxLineAmount.Text = "";
textBoxLineAmount.Focus();
}
}
private void buttonClear_Click(object sender, EventArgs e)
{
textBoxLineAmount.Text = "";
amountCalculatorVM.Amounts.Clear();
textBoxLineAmount.Focus();
}
}
Then, for my view model, I have this:
class AmountCalculatorVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private readonly AmountList amounts;
public BindingSource Amounts { get; }
public int TotalAmount => amounts.TotalAmount;
public int Count => amounts.Count;
public decimal Average => amounts.Average;
public AmountCalculatorVM()
{
amounts = new AmountList();
Amounts = new BindingSource();
Amounts.DataSource = amounts;
Amounts.ListChanged += Amounts_ListChanged;
}
private void Amounts_ListChanged(object sender, ListChangedEventArgs e)
{
//Any one of these will cause all three textboxes to update in the form
//I would think that with Count and Average commented out, the Count and
//Average textboxes would not update.
OnPropertyChanged("TotalAmount");
//OnPropertyChanged("Count");
//OnPropertyChanged("Average");
//Using any other word will not
//OnPropertyChanged("SomeOtherRandomWord");
}
}
Here is the AmountList class for reference:
class AmountList : List<int>
{
public int TotalAmount
{
get
{
int total = 0;
foreach (int amount in this)
{
total += amount;
}
return total;
}
}
Now, unexpectedly, all three textboxes update if an item is added to the amounts list, which fires ListChanged, and then in turn, the PropertyChanged event.
It doesn't matter which of the three properties I fire PropertyChanged on, but it won't work if I use a different value - it needs to be either TotalAmount, Count, or Average.
I can't understand this behaviour. I would have expected only the text box bound to TotalAmount to be updated, and not the other two, since nothing seems to be notifying them that an update has occurred.
Any ideas?
Why don't you implement the propertychanged like this:
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
You can control now, in the setter, which property fires the event:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
you know what I mean?

c# DataChanged event does trigger on a windows form (Desktop Application)

I have a form, I select some checkboxes, edit some text field, select from a combobox etc. then I click Exit. Based on the fact that "Data has been changed ??" I wish to perform actions. The problem is I can't get the event work :
private void DataChanged(object sender, EventArgs e)
{
MessageBox.Show("Data is changed", "debug");
isDataSaved = false;
}
When is this method called, how do I make it work? Is this supposed to get fired when the form's fields have some data i.e filL a text box ?
I dont really get the API: DataChanged event
Note: I'm following Mike Murach C# 5th edition chapter 10 example.
Edit (exact words from book):
Generate an event handler named DataChanged for the
SelectedIndexChanged event of the XXXX Name combo box. Then , wire
this event handler to the TextChanged event of the YYYYY Method label
and add the code to this event handler so it sets the isDataSaved
variable to false
When I double click on the commbo box the generated event handler it is not named DataChanged but cboNames_SelectedIndexChanged... (is this a book screw up or me total noob ? PS: There is no .. 'database' in the project)
Personally I mostly use databinding these days to get notified of changes in data.
A data holder class, which implements INotifyPropertyChanged. This interface gives you the possibility to get notified when the value of a property changes.
public class SomeData: INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "") {
if (!EqualityComparer<T>.Default.Equals(field, value)) {
field = value;
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
}
private boolean _someBoolean;
public int SomeBoolean {
get { return _someBoolean; }
set {
SetProperty(ref _someBoolean, value);
}
}
private string _someString;
public string SomeString {
get { return _someString; }
set {
SetProperty(ref _someString, value);
}
}
// etc
}
Now our form, which uses the data class and it's INotifyPropertyChanged implementation to get notified when a change in data occurs.
public partial class SomeForm: Form {
private SomeData _data;
private void LoadData() {
_data = new SomeData();
_data.PropertyChanged += Data_PropertyChanged;
}
private void SaveData() {
// TODO: Save data
}
private void AddDataBindings() {
checkbox1.DataBindings.Add("Checked", _data, "SomeBoolean");
textbox1.DataBindings.Add("Text", _data, "SomeString");
// add other
}
private void Data_PropertyChanged(object sender, PropertyChangedEventArgs e) {
// Here you can add actions that must be triggered when some data changes.
if (e.PropertyName == "SomeBoolean") {
// Do something when some-boolean property changes
}
// Set is-changed-boolean to true to 'remember' that something has changed.
_isChanged = true;
// Give message
MessageBox.Show(string.Format("Data changed, property {0}", e.PropertyName));
}
private bool _isChanged = false;
protected void Form_Closed(object sender, EventArgs e) {
// If data is changed, save it
if (_isChanged) {
SaveData();
}
}
}
Your problem is not known where the method DataChanged use and how. I have a suggestion for you that is use Focus Activated in properties.Add datachanged printing method Activated
good luck.
You must make properties this like

Handling OnPropertyChanged

I'm not well versed in event-based programming. Basically, I'm still stumbling around with it. I'm trying to get something set up, but even with the tutorials, I can't wrap my head around it. What I would like to do (in words) is the following:
I have a dataobject where a property changes. I notice this in the setter of the property, and want to raise an event that the property has changed.
Elsewhere (in a different class entirely), I want to know that the property on this object has changed, and take some action.
Now I'm sure this is a common enough scenario, but my google-fu is letting me down. I'm simply not understanding http://msdn.microsoft.com/en-us/library/ms743695.aspx.
I have this:
public class ChattyClass {
private int someMember;
public event PropertyChangedEventHandler PropertyChanged;
public int SomeMember {
get {
return this.someMember;
}
set {
if (this.someMember != value){
someMember = value;
// Raise event/fire handlers. But how?
}
}
}
public class NosyClass{
private List<ChattyClass> myChatters;
public void addChatter(ChattyClass chatter){
myChatters.add(chatter);
// Start listening to property changed events
}
private void listner(){
// I want this to be called when the PropertyChangedEvent is called
Console.WriteLine("Hey! Hey! Listen! A property of a chatter in my list has changed!");
}
}
What do I do to wire this up?
Concerning the comment pointing me back to the link:
In the example I see:
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
What I'm not understanding:
Why isn't this just calling PropertyChanged(this, new PropertyCHangedEventArgs(name))
Where does PropertyChanged get assigned?
What does the assignment look like?
You have to fire the event. In the example on MSDN, they made a protected method OnPropertyChanged to handle this easier (and to avoid duplicate code).
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
What this method does, is look whether there is an event handler assigned or not (if it is not assigned and you just call it, you'll get a NullReferenceException). If there is one assigned, call this event handler. The event handler provided, has to have the signature of the PropertyChangedEventHandler delegate. This signature is:
void MyMethod(object sender, PropertyChangedEventArgs e)
Where the first parameter has to be of the type object and represents the object that fires the event, and the second parameter contains the arguments of this event. In this case, your own class fires the event and thus give this as parameter sender. The second parameter contains the name of the property that has changed.
Now to be able to react upon the firing of the event, you have to assign an event handler to the class. In this case, you'll have to assign this in your addChatter method. Apart from that, you'll have to first define your handler. In your NosyClass you'll have to add a method to do this, for example:
private void chatter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("A property has changed: " + e.PropertyName);
}
As you can see, this method corresponds to the signature I explained before. In the second parameter, you'll be able to find the information of which parameter has been changed. Last thing to do, is add the event handler. Now in your addChatter method, you'll have to assign this:
public void AddChatter(ChattyClass chatter)
{
myChatters.Add(chatter);
// Assign the event handler
chatter.PropertyChanged += new PropertyChangedEventHandler(chatter_PropertyChanged);
}
I would suggest you to read something about events in .NET / C#: http://msdn.microsoft.com/en-us/library/awbftdfh . I think after reading/learning this, things will be more clear to you.
You can find a console application here on pastebin if you would like to test it quickly (just copy/paste into a new console application).
With newer versions of C#, you can inline the call to the event handler:
// inside your setter
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MyProperty)));
You could also use something like Fody PropertyChanged to automatically generated the necessary code (visit the link to their GitHub page, with samples).
The link that you looked is for the MVVM pattern and WPF. It is not a general C# implementation. You need something like this:
public event EventHandler PropertyChanged;
public int SomeMember {
get {
return this.someMember;
}
set {
if (this.someMember != value) {
someMember = value;
if (PropertyChanged != null) { // If someone subscribed to the event
PropertyChanged(this, EventArgs.Empty); // Raise the event
}
}
}
...
public void addChatter(ChattyClass chatter) {
myChatters.add(chatter);
chatter.PropertyChanged += listner; // Subscribe to the event
}
// This will be called on property changed
private void listner(object sender, EventArgs e){
Console.WriteLine("Hey! Hey! Listen! A property of a chatter in my list has changed!");
}
If you want to know what property has changed you need to change your event definition to:
public event PropertyChangedEventHandler PropertyChanged;
And change the calling to:
public int SomeMember {
get {
return this.someMember;
}
set {
if (this.someMember != value){
someMember = value;
if (PropertyChanged != null) { // If someone subscribed to the event
PropertyChanged(this, new PropertyChangedEventArgs("SomeMember")); // Raise the event
}
}
}
private void listner(object sender, PropertyChangedEventArgs e) {
string propertyName = e.PropertyName;
Console.WriteLine(String.Format("Hey! Hey! Listen! a {0} of a chatter in my list has changed!", propertyName));
}
why isn't this just calling PropertyChanged(this, new
PropertyCHangedEventArgs(name))
Because if no one attached an handler to the event, then the PropertyChanged object returns null. So you'll have to ensure it's not null before calling it.
where does PropertyChanged get assigned?
In the "listener" classes.
For example, you could write in other class:
ChattyClass tmp = new ChattyClass();
tmp.PropertyChanged += (sender, e) =>
{
Console.WriteLine(string.Format("Property {0} has been updated", e.PropertyName));
};
What does the assignment look like?
In C# we use the assignment operators += and -= for events. I recommend reading the following article to understand how to write event handlers using the anonymous method form (example above) and the "old" form.
From taking the original code, and incorporating #Styxxy 's answer, I come out with:
public class ChattyClass : INotifyPropertyChanged
{
private int someMember, otherMember;
public int SomeMember
{
get
{
return this.someMember;
}
set
{
if (this.someMember != value)
{
someMember = value;
OnPropertyChanged("Some Member");
}
}
}
public int OtherMember
{
get
{
return this.otherMember;
}
set
{
if (this.otherMember != value)
{
otherMember = value;
OnPropertyChanged("Other Member");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class NosyClass
{
private List<ChattyClass> myChatters = new List<ChattyClass>();
public void AddChatter(ChattyClass chatter)
{
myChatters.Add(chatter);
chatter.PropertyChanged+=chatter_PropertyChanged;
}
private void chatter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("A property has changed: " + e.PropertyName);
}
}

Categories

Resources