How to execute code when a property is changed using INotifyPropertyChanged - c#

I´ve started learning only recently so this is a newbie question.
Maybe someone could help me out in regards to what I´d have to do differently for my code to work.
In short: I have a class that inherits from INotifyPropertyChanged (which I´v tried to implement according to MSDN). When I press a button I want to change a variable in this class which in turn should raise a PropertyChanged Event. When the event is raised some code should be executed.
My ValueChanged class:
public class ValueChange : INotifyPropertyChanged
{
public ValueChange()
{
_size = 1;
}
private int _size;
public int Size
{
get
{
return _size;
}
set
{
_size = value;
OnPropertyRaised("Size");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyRaised([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
My event listeners:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ValueChange test = new ValueChange();
test.Size = 10;
}
private void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
switch (args.PropertyName)
{
case "Size":
// txtbox is just some textbox in my UI
txtbox.Text = "some text";
// This is merely a placeholder as I´d like to be able to execute any code in here
break;
}
}
}

There are a few issues with the code.
You are creating a new instance of the ValueChange class every time you click.
You are not subscribing to PropertyChanged event.
Although this will fix your code, is there a reason you are using PropertyChanged here instead of executing your code directly in the Button_Click event handler? PropertyChanged is usually used when binding, it is rarely used directly as you are doing here.
public partial class MainWindow : Window
{
ValueChange test = new ValueChange();
public MainWindow()
{
test.PropertyChanged += PropertyChanged;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
test.Size = 10;
}
private void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
switch (args.PropertyName)
{
case "Size":
// txtbox is just some textbox in my UI
txtbox.Text = "some text";
// This is merely a placeholder as I´d like to be able to execute any code in here
break;
}
}
}

Related

WPF TextBox does not update from bounded class

I'm currently stuck on how to update the text of my textbox whenever any class changes the text. Currently, only my main thread does so, and I have tried various methods (including a dispatch) to update the view from anywhere. My code looks like this:
XAML:
<TextBox x:Name ="textBoxResults" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
XAML.CS:
public partial class MainWindow : Window
{
public ConsoleLog cl { get; set; }
public MainWindow()
{
InitializeComponent();
MainWindow_Load();
cl = new ConsoleLog();
DataContext = cl;
}
}
private void ButtonBeginTests_Click(object sender, RoutedEventArgs e)
{
new Thread(() =>
{
App.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(() =>{
tc = new TestController(.., .., cl); //other args not important
tc.beginTest();
}));
}).Start();
}
ConsoleLog Class:
using System;
using System.ComponentModel;
namespace Test_DesktopApplication.src
{
public class ConsoleLog : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public void addToLog(string text)
{
App.Current.Dispatcher.Invoke(new Action(delegate { this.Text += text; }));
}
}
A class calls "addToLog" multiple times during a separate thread of background processes. Any indication to what I could be doing wrong?
EDIT: I can actually get this to work by using this after every addToLog call:
App.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new ThreadStart(delegate { }));
however I don't know if this is a "good" fix.
EDIT 2:
I have updated the MainWindow class to show when this class is called, the class that calls the log is below:
public class Testcontroller
{
ConsoleLog cl;
public GEN2TestController(.., .., ConsoleLog console)
{
//other constructor things
cl = console;
}
public void beginTest(){
testList[0].result = unprogrammedCurrent_test(.., ..); //this is an example of what the test looks like..
cl.addToLog(TestList[0].result);
...//repeat for the rest of the test lists and tests..
...
...
}
The log will not update until all tests are done.
You code example is working fine!
EDIT: There should be no need to force an empty dispatcher cycle like that, this is the equivalent of the old WinForms Application.DoEvents() there must be something else in your code that is blocking the dispatcher (UI thread), perhaps you should share an example of how your background worker is constructed and initiated.
I added a button to your form:
<Button Margin="0,267,376,0" Click="Button_Click" Height="54" VerticalAlignment="Top" HorizontalAlignment="Right" Width="142"/>
And the button click logic in the code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
cl.addToLog(DateTime.Now.ToString() + Environment.NewLine);
}
I suspect that you issue then is how you are calling addToLog() Most likely you are calling a different instance of the object to the one that is set as the data context.
I have modified your example to include a Background worker that is initiated from the form, this is working quite well:
public partial class MainWindow : Window
{
private void Button_Click(object sender, RoutedEventArgs e)
{
if (worker.IsBusy)
worker.CancelAsync();
else
{
cl.Text = String.Empty;
worker.RunWorkerAsync();
}
}
public ConsoleLog cl { get; set; }
private BackgroundWorker worker = null;
public MainWindow()
{
InitializeComponent();
cl = new ConsoleLog();
DataContext = cl;
worker = new BackgroundWorker { WorkerSupportsCancellation = true };
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) => cl.addToLog($"{e.Result}");
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
Random r = new Random();
while(true)
{
if ((sender as BackgroundWorker).CancellationPending) break;
cl.addToLog(DateTime.Now.ToString() + Environment.NewLine);
System.Threading.Thread.Sleep(r.Next(500, 3000));
}
e.Result = "Stop" + Environment.NewLine;
}
}

C# Event name is null

I have a WinForms application wherein I have my main application with a separate class that is part of the solution. In the class which is defining a User control with Dev Express buttons, I have defined my event delegate, event, method and eventargs.
In the main program, i have defined my listener.
I am getting a null value in my event method and cannot see why. I have reviewed this a number of times and as far as I can see, it is completely correct.
I would appreciate any comments/corrections that would be useful here.
This is the code in my class.
public partial class XtraUserControl1 : XtraUserControl, IAnyControlEdit
{
public delegate void ButtonClickedEventHandler(object sender, ClickEventArgs e);
public event ButtonClickedEventHandler ButtonClicked;
public XtraUserControl1()
{
InitializeComponent();
}
public void OnButtonClicked(ClickEventArgs e)
{
if (ButtonClicked != null)
{
ButtonClicked(this, e);
}
}
public class ClickEventArgs : EventArgs
{
public readonly SimpleButton buttonClicked;
public ClickEventArgs(SimpleButton button)
{
this.buttonClicked = button;
}
}
This is the main code where I have defined the listener.
private void frmEHHeaders_Load(object sender, EventArgs e)
{
// Create the button group from the User Control XtraUserControl1 and add it to the grid repository
btnGroup = new User_Controls.XtraUserControl1();
RepositoryItemAnyControl riAny = new RepositoryItemAnyControl();
riAny.Control = btnGroup;
grdEHHeaders.RepositoryItems.Add(riAny);
colButtons.ColumnEdit = riAny;
// Add event handlers
this.grdEHHeaders.Views[0].MouseDown += gridView1_MouseDown;
gridView1.CustomRowCellEdit += GridView1_CustomRowCellEdit;
// Listener for the button class
btnGroup.ButtonClicked += new User_Controls.XtraUserControl1.ButtonClickedEventHandler(btnGroup_ButtonClicked);
GetData();
}
private void btnGroup_ButtonClicked(object sender, User_Controls.XtraUserControl1.ClickEventArgs e )
{
SimpleButton myButton = e.buttonClicked;
MessageBox.Show("You clicked " + myButton.Text);
}

How to set control state in MVP

I want to disable button(or other control) when user can't raise event. What is the best way to do this? View handles that or presenter should pass value by property in view and then view will update control's state.
For example if previous query is not finished user shouldn't start new.
Option 1:
interface IView
{
event EventHandler Event;
}
class View : IView
{
private readonly Button _button;
public event EventHandler Event;
public void button_Click(object sender, EventArgs e)
{
_button.Enabled = false;
if(Event != null)
{
Event(this, EventArgs.Empty);
}
_button.Enabled = true;
}
}
class Presenter
{
public void View_Event(object sender, EventArgs e)
{
// code...
}
}
Option 2:
interface IView
{
event EventHandler Event;
bool CanRaiseEvent { set; }
}
class View : IView
{
private readonly Button _button;
public event EventHandler Event;
public bool CanRaiseEvent
{
set
{
_button.Enabled = value;
}
}
public void button_Click(object sender, EventArgs e)
{
if (Event != null)
{
Event(this, EventArgs.Empty);
}
}
}
class Presenter
{
private readonly IView _view;
public void View_Event(object sender, EventArgs e)
{
_view.CanRaiseEvent = false;
// code...
_view.CanRaiseEvent = true;
}
}
I know that i should check in presenter query's status before executing next query but I want to inform view that user shouldn't even try.
Two 'litmus' tests I use for MVP design are: 1) Is the logic testable? and 2) Could I replace the concrete view and the application still work?
From this perspective, option 2 looks the more attractive.

Why is the Binding Format event called twice?

I am playing around with data binding and noticed that the Binding Format is called twice upon loading the form in the code below. I thought it would only happen once when the test class TextBoxText property is fist bound to textbox1. Is this normal? If not, then what can I do to prevent it? Note, when I press button1 and it changes the TextBoxText property of the test class, the format event fires once as expected.
public partial class Form1 : Form
{
Test _test = new Test();
public Form1()
{
InitializeComponent();
Binding binding = new Binding("Text", _test, "TextBoxText");
binding.Format += new ConvertEventHandler(Binding_Format);
this.textBox1.DataBindings.Add(binding);
}
private void Binding_Format(object sender, ConvertEventArgs e)
{
Debug.WriteLine("Format");
}
private void button1_Click(object sender, EventArgs e)
{
_test.TextBoxText = "test1";
}
}
class Test : INotifyPropertyChanged
{
private string _text;
public string TextBoxText
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged(new PropertyChangedEventArgs("TextBoxText"));
}
}
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged(this, e);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
The simple answer: "Because that is the way Microsoft implemented it".
The goal is to just respond to the event... whenever it happens... however often it occurs. We can't make any assumptions. There are cases where you might get called six times on the same event.
We just have to roll with it and continue to be awesome.

Winforms user controls custom events

Is there a way to give a User Control custom events, and invoke the event on a event within the user control. (I'm not sure if invoke is the correct term)
public partial class Sample: UserControl
{
public Sample()
{
InitializeComponent();
}
private void TextBox_Validated(object sender, EventArgs e)
{
// invoke UserControl event here
}
}
And the MainForm:
public partial class MainForm : Form
{
private Sample sampleUserControl = new Sample();
public MainForm()
{
this.InitializeComponent();
sampleUserControl.Click += new EventHandler(this.CustomEvent_Handler);
}
private void CustomEvent_Handler(object sender, EventArgs e)
{
// do stuff
}
}
Aside from the example that Steve posted, there is also syntax available which can simply pass the event through. It is similar to creating a property:
class MyUserControl : UserControl
{
public event EventHandler TextBoxValidated
{
add { textBox1.Validated += value; }
remove { textBox1.Validated -= value; }
}
}
I believe what you want is something like this:
public partial class Sample: UserControl
{
public event EventHandler TextboxValidated;
public Sample()
{
InitializeComponent();
}
private void TextBox_Validated(object sender, EventArgs e)
{
// invoke UserControl event here
if (this.TextboxValidated != null) this.TextboxValidated(sender, e);
}
}
And then on your form:
public partial class MainForm : Form
{
private Sample sampleUserControl = new Sample();
public MainForm()
{
this.InitializeComponent();
sampleUserControl.TextboxValidated += new EventHandler(this.CustomEvent_Handler);
}
private void CustomEvent_Handler(object sender, EventArgs e)
{
// do stuff
}
}

Categories