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
Related
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.
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);
I would like to notify a program immediately when there is a change in a bool variable that is a public variable of an object. For example;
say, an instance of class conn is created within a windows form application.
there is a Ready variable, a public variable of the class conn is present.
I would like to get notified whenever there is a change in this variable.
I did a quick research to solve this problem within stackoverflow but the answers suggested the use of property, which, I think is not suitable for my application.
I will assume you are referring to a field when you say public variable.
With few exceptions, it is preferable to not have public fields in C# classes, but rather private fields with public accessors:
class BadClass
{
public int Value; // <- NOT preferred
}
class GoodClass
{
private int value;
public int Value
{
get { return this.value; }
set { this.value = value; }
}
}
One of the reasons to structure your code this way is so you can do more than one thing in the property's getter and setters. An example that applies to your scenario is property change notification:
class GoodClass : INotifyPropertyChanged
{
private int value;
public int Value
{
get { return this.value; }
set
{
this.value = value;
this.OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name);
}
}
}
If you were to implement your class like this, you could use it this way:
void SomeMethod()
{
var instance = new GoodClass();
instance.PropertyChanged += this.OnPropertyChanged;
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
// Do something here.
}
}
If you change the Value property, not only will it change the value of the underlying field, but it will also raise the PropertyChanged event, and call your event handler.
You want to use the Observer pattern for this. The most straight forward way to do this in .NET is the event system. In the class conn, create an event:
public event EventHandler ReadyChanged;
and then when you create an instance of conn, subscribe to that event:
o.ReadyChanged += (s, e) =>
{
// do something
}
and then finally, when the flag changes in conn, fire the event via a new method named OnReadyChanged:
protected virtual void OnReadyChanged()
{
if (ReadyChanged != null) { ReadyChanged(this, new EventArgs()); }
}
I have a UserControl that has a Textbox, Button, and a Tooltip controls on it. It does implement INotifyPropertyChanged I have tried overriding the Text property and adding my own property, but in all cases the control reads from the bound data source fine, but never updates the data source. My events are raised when the text is changed. Some of the code is below. All other standard controls are working fine. What do I need to get the control to update the data source when the user has entered or changed the value?
public partial class UrlControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[Bindable(true)]
[Browsable(true)]
public string Url
{
get
{
return url.Text;
}
set
{
if (value != url.Text)
{
url.Text = value;
OnPropertyChanged("Url");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
This is the binding code from the form designer.
this.urlControl1.DataBindings.Add(new System.Windows.Forms.Binding("Url", this.customerBindingSource, "First", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
}
INotifyPropertyChanged is for datasources. It allows your datasource to notify bound controls and other listeners of property changes. However, controls themselves use a different mechanism. It's a bit strange: you create events on your control with the naming convention <PropertyName>Changed. When the value of a property changes, you raise the associated event.
Example:
public string Url
{
get { return url.Text; }
set
{
if (value != url.Text)
{
url.Text = value;
OnUrlChanged(); // raise event
}
}
}
public event EventHandler UrlChanged;
private void OnUrlChanged()
{
// raise the UrlChanged event
if (UrlChanged != null)
UrlChanged(this, new EventArgs());
}
That's all you need to do. The Databinding Fairies will see that event and hook it up when you create the binding.
Here's the topic on MSDN: How to: Apply the PropertyNameChanged Pattern
This should work well for reading values from the datasource.
However, when it comes to writing values to the datasource it looks like you're storing and getting the Url value directly from the url textbox. However, you're not raising property change notifications when the textbox's text is changed within the UI. To fix this, add a TextChanged event handler on the textbox, which can simple call:
void url_TextChanged(object sender, EventArgs e)
{
OnPropertyChanged("Url");
OnUrlChanged(); // See additional note below
}
As a side, although implementing INotifyPropertyChanged should work... When it comes to Windows Forms binding you can also create an event with the property name suffixed with "Changed" and the binding should watch that:
public event EventHandler UrlChanged;
protected virtual void OnUrlChanged()
{
var handler = UrlChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
[Bindable(true)]
[Browsable(true)]
public string Url
{
get
{
return url.Text;
}
set
{
if (value != url.Text)
{
url.Text = value;
OnPropertyChanged("Url");
OnUrlChanged();
}
}
}
I have a List of complex objects, and a ListBox that is bound to this List with BindingSource. When user selects items in listbox, he can edit it's properties via PropertyGrid and it's text separately via TextBox. When property is changed via PropertyGrid, BindingSource's CurrentItemChanged is called, but I'm having problems with updating DataBinding when users just edits TextBox
Here is some code to explain my situation better:
class Song
{
public string Title{get;set}
[Browsable(false)]
public string Text{get;set;}
...
}
class SongBook
{
public List Songs {get;set;}
...
}
// Initialization: we are setting ListBox's DataSource to songBookBindingSource
private void InitializeComponent()
{
...
this.allSongsList.DataSource = this.songBookBindingSource;
...
}
// We create new SongBook object, and set BindingSource's DataSource to
// list of songs in songbook
private void OpenSongBook()
{
...
currentSongBook.Deserialize( path );
songBookBindingSource.DataSource = currentSongBook.Songs;
}
// When user selects a song in ListBox, we try to edit it's properties
private void allSongsList_SelectedValueChanged(object sender, EventArgs e)
{
...
songProps.SelectedObject = allSongsList.SelectedItem;
songTextEdit.Text = (allSongsList.SelectedItem as Song).Text;
}
// This get called whenever user changes something in TextBox.
// If it does, we want to mark song as Unsaved and refresh
// ListBox, so it would display a nice little "*" next to it!
private void songTextEdit_TextChanged(object sender, EventArgs e)
{
currentSong.Text = editSongTextBox.Text;
currentSong.Unsaved = true;
// As far as I understand, this SHOULD make ListBox bound to songBookBindingSource
// update its items. But it does not! How do I make it understand that data changed?
songBookBindingSource.RaiseListChangedEvents = true;
// And if I do this, ListBox DOES gets updated, but something also inserts A COPY OF CURRENT ITEM
// into it. If I select it, allSongsList.SelectedItem throws "Out of bounds" exception. As far
// as I understand, it gets added only to ListBox, but NOT to underlying List. But why is it
// even getting added at all?!
// songBookBindingSource.ResetCurrentItem();
}
I feel like .NET Framework hates me :)
Your objects need to implement INotifyPropertyChanged, that way the binding will refresh when a property changes:
class Song : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged("Title");
}
}
private string _text;
[Browsable(false)]
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("Text");
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}