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.
Related
My tooltip should show how long my program is running. So I try to add +1 to my tooltip, but that doesn't work.
That is my xaml code:
<StatusBarItem >
<Image ToolTip="{Binding Path=ToolTipStatus}"/>
</StatusBarItem>
And thats my C# code:
private string _toolTipStatus = "0";
private string ToolTipStatus
{
get { return _toolTipStatus; }
}
private void Example()
{
_toolTipStatus = _toolTipStatus + 1;
}
First, nowhere in this code is there any reason for the UI to guess when or if your private field has changed. Second, your property is private too, so the UI can't see it either. Finally, repeatedly appending "1" to a string is going to get you a string that looks like "11111111111111111111111111111" after the timer fires a few times. If that's what you want, that's fine, but I think it might not be.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MyViewModel : ViewModelBase
{
private int _toolTipStatus = 0;
private int ToolTipStatus
{
get { return _toolTipStatus; }
protected set {
if (_toolTipStatus != value)
{
_toolTipStatus = value;
OnPropertyChanged(nameof(ToolTipStatus));
}
}
}
}
private void Example()
{
ToolTipStatus += 1;
}
You won't say if you've got a viewmodel. You won't say what class your code is in or how (or if) it gets called. All your properties are private. You won't say what the XAML looks like or even if there is any. I sense a theme of obsessive secrecy here. You need to learn when to open up and share.
And you need a viewmodel, and you need it to implement INotifyPropertyChanged.
You should refresh your xaml someway. The best way I think is inheriting the form from INotifyPropertyChanged.
Then declare the event and the raise method like this
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropChanged(string name)
{
var eh = this.PropertyChanged;
if (eh != null)
{
eh(this, new PropertyChangedEventArgs(name));
}
}
then your property ToolTipStatus should be this:
private string toolTipStatus;
public string ToolTipStatus
{
get { return toolTipStatus; }
set
{
toolTipStatus = value;
RaisePropChanged("ToolTipStatus");
}
}
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
So I am trying to implement the MVVM pattern in a simple sample app. Essentially my app allows a user to choose from a list of search providers in a SettingsPage, and then in the MainPage when the user clicks the 'search' button he or she will be navigated to the search provider's website. Everything seems to work ok, no errors, except when navigating directly back to MainPage from SettingsPage the search property does not seem to be updated. Everything is fine though when the application is completely exited and launched fresh. What I have is as follows
MainPage.xaml.cs
void search_Click(object sender, EventArgs e)
{
TheBrowser.Navigate(App.ViewModel.SearchProvider.Address);
}
App.xaml.cs
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
MainViewMode.cs
public ListItem SearchProvider { get; private set; }
public MainViewModel()
{
SearchProvider = Settings.SearchProvider.Value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
and in my SettingsPage is where I am allowin ga user to select a search provider
SettingsPage.xaml.cs
private void PopulateSearchProviderList()
{
searchProviderList = new ObservableCollection<ListItem>();
searchProviderList.Add(new ListItem { Name = "Bing", Address = "http://www.bing.com" });
searchProviderList.Add(new ListItem { Name = "Google", Address = "http://www.google.com" });
SearchProviderListPicker.ItemsSource = searchProviderList;
}
private void stk_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
if (SearchProviderListPicker.SelectedIndex != -1)
{
var selectedItem = (sender as StackPanel).DataContext as TestApp.Classes.ListItem;
Settings.SearchProvider.Value = selectedItem; //Setting the search provider
}
}
and finally my ListItem class which is fairly straightforward
ListItem.cs
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
So essentially I am not updating the ViewModel correctly based on the SettingsPage, but I am unsure of how to go about this properly.
You have to call the OnNotifyPropertyChanged("propertyName") for the item to update in the UI.
For example (assuming the Name and Address properties are bound to your UI elements.)
private string name;
private string address;
public string Name
{
get { return name;}
set {
name = value;
OnNotifyPropertyChanged("Name");
}
}
public string Address
{
get { return address; }
set {
address = value ;
OnNotifyPropertyChanged("Address");
}
}
There are a few issues I can see. We'll start from there.
Your MainViewModel needs to implement INotifyPropertyChanged see here
Your SearchProvider setter needs to raise PropertyChanged
You need to set the value of the SearchProvider. Currently that is only performed in the constructor which is probably why you are seeing things working on app startup only.
You need to make sure you are correctly binding the value of SearchProvider in your xaml. If you post your xaml we can check that out too.
In your ViewModel, add:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Update the SearchProvider property to something like:
private ListItem searchProvider;
public ListItem SearchProvider
{
get { return searchProvider; }
set
{
searchProvider = value;
OnPropertyChanged();
}
}
I use INotifyPropertyChanged to notify class when there is any change in a variable of a particular object within it.
Below is the class:
public class MyClass
{
public SecClass MyObj { get; set; }
//A few more variables
}
SecClass:
public class SecClass:INotifyPropertyChanged
{
private bool _noti= false;
public bool Noti
{
get { return _noti; }
set
{
_noti= value;
NotifyPropertyChanged("Noti");
}
}
//A few more variables
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Here my function that makes the event registration:
public void Register()
{
MyObj.PropertyChanged += MyObj_PropertyChanged;
}
Function works and the registration is done, but when it comes to change it displays the Property Change as null (I guess that somewhere registration deleted, before happens change, how can I check this?)
I hooked this together with:
static class Program
{
static void Main()
{
var c = new MyClass();
c.MyObj = new SecClass();
c.Register();
c.MyObj.Noti = !c.MyObj.Noti;
}
}
adding (for illustration):
private void MyObj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName);
}
to MyClass, and:
public event PropertyChangedEventHandler PropertyChanged;
to SecClass (to get them to compile), and it works fine - printing "Noti" at runtime. There is a theoretical thread-race, but it is very unlikely in any sane usage, but recommended usage is:
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
Also, for info: if you add [CallerMemberName] to that, you don't need to specify the property explicitly:
private void NotifyPropertyChanged([CallerMemberName] string name = null) {...}
with:
NotifyPropertyChanged(); // the compiler adds the "Noti" itself
But fundamentally: "cannot reproduce" - it works fine. I wonder if maybe it relates to your PropertyChanged implementation, since you don't actually show that. In particular, I wonder if you actually have two events: one explicitly implemented. That would mean that it is getting treated differently by your cast.
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();
}
}
}