WPF TextBox does not update from bounded class - c#

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;
}
}

Related

How to execute code when a property is changed using INotifyPropertyChanged

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;
}
}
}

How to change textbox value when I change it from another window [duplicate]

This question already has answers here:
Change a textbox's text value on a UserControl from a Window in WPF
(2 answers)
Closed 3 years ago.
Ok, I have two windows in my WPF app. I want to change the text of a textbox, from a second window. This should also happen parallel.
I know, this is about multithreadin, but I know very little about it.
This is what my current code looks like, but this is for copying textbox text.
private void copyButton_Click(object sender, RoutedEventArgs e)
{
secondWindow = new SecondWindow();
secondWindow.textBoxS.Text = textBox.Text;
secondWindow.Show();
}
But, I want to change textbox texts dynamically between the MainWindow and the Second window.
So I tried something like this:
private void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
Task t = Task.Run(() =>
{
secondWindow = new SecondWindow();
secondWindow.textBoxS.Text = textBox.Text;
secondWindow.Show();
});
t.Start();
}
There are several ways to do this. I put two way in below:
Method 1
You can create a public method (e.g. SetTextBoxValue) and
pass the window that contains the TextBox to other window. Then change the TextBox value using that method. like this:
MainWindow
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
public void SetTextBoxValue(string value)
{
SampleTextBox.Text = value;
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var otherWindow = new AnotherWindow(this);
otherWindow.Show();
}
}
Other Window
public partial class AnotherWindow
{
private readonly MainWindow _mainWindow;
public AnotherWindow(MainWindow mainWindow)
{
_mainWindow = mainWindow;
InitializeComponent();
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
_mainWindow.SetTextBoxValue("New value from other window");
}
}
Method 2
You can create a event for other window and subscribe to it in main window. like this:
MainWindow
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var otherWindow = new AnotherWindow();
otherWindow.TextBoxValueChanged += OtherWindowOnTextBoxValueChanged;
otherWindow.Show();
}
private void OtherWindowOnTextBoxValueChanged(object sender, TextBoxValueEventArgs e)
{
SampleTextBox.Text = e.NewValue;
}
}
Other Window
public partial class AnotherWindow
{
public event EventHandler<TextBoxValueEventArgs> TextBoxValueChanged;
public AnotherWindow()
{
InitializeComponent();
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var newValue = "New value from other window";
OnTextBoxValueChanged(new TextBoxValueEventArgs(newValue));
}
protected virtual void OnTextBoxValueChanged(TextBoxValueEventArgs e)
{
TextBoxValueChanged?.Invoke(this, e);
}
}
public class TextBoxValueEventArgs : EventArgs
{
public string NewValue { get; set; }
public TextBoxValueEventArgs(string newValue)
{
NewValue = newValue;
}
}
OUTPUT
try this, initialize the second window in first window constructor
private SecondWindow _secondWindow;
public FirstWindow()
{
_secondWindow = new SecondWindow();
}
and your second form before constructor
private string text;
public string Text
{
get
{
return text;
}
set
{
textBoxOfSecondWindow.Text = value;
text = value;
}
}
then in the copybutton function
private void copyButton_Click(object sender, RoutedEventArgs e)
{
_secondWindow.Text= textBox.Text;
_secondWindow.Show();
}
in the textchange of the first window
private void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
_secondwindow.Text = textBox.Text;
}
in your second window, place this code in the textBox_TextChanged method.
((MainWindow)Application.Current.MainWindow).txtFirstWindow.Text = txtSecondWindow.Text;

MVVM showDialog with owner and close when background worker finishes

I'm just trying to get a grasp of MVVM pattern in WPF (currently without any framework).
Scenario:
I have a main window, I click a button "Start work" that is bound to some command in the viewmodel. Progress dialog should open with "Cancel" button, it should show on the center of the owner window(so I need to pass the owner), I press cancel and I invoke "CancelAsync" method on background worker.
The principle of MVVM is that the view model should never know anything about the view and in my case I'm violating this rule.
Code-behind (No MVVM) solution:
Main window part:
private void Button_Click(object sender, RoutedEventArgs e)
{
backgroundWorker.RunWorkerAsync();
progressWindow = new ProgressWindow(backgroundWorker);
progressWindow.Owner = this;
progressWindow.ShowDialog();
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressWindow.Close();
}
Progress window part:
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
backgroundWorker.CancelAsync();
}
My attempt to convert this code to MVVM (this is wrong)
public class MainViewModel
{
public ICommand DoSomething { get; }
private BackgroundWorker backgroundWorker;
private PleaseWaitView pleaseWaitView;
public MainViewModel()
{
backgroundWorker = new BackgroundWorker() { WorkerSupportsCancellation = true };
backgroundWorker.DoWork += BackgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
var pleaseWaitViewModel = new PleaseWaitViewModel(backgroundWorker);
pleaseWaitView = new PleaseWaitView();
pleaseWaitView.Owner = Application.Current.MainWindow;
pleaseWaitView.DataContext = pleaseWaitViewModel;
DoSomething = new ActionCommand<object>(DoSomethingImpl);
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pleaseWaitView.Close();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Some work
Thread.Sleep(5000);
}
private void DoSomethingImpl(object parameter)
{
pleaseWaitView.ShowDialog();
}
}
How to solve this? I did what I wanted in code-behind in a matter of 20 minutes, I wanted to try MVVM pattern and it takes me few hours to solve simple problem.
I was looking at some solutions with EventAggregator but that requires using a framework like Prism, Caliburn.Micro. So I get some kind of communication between VM and the View.
You can pass an interface to MainViewModel which contains the needed methods
interface IMainView
{
void Init(PleaseWaitViewModel viewModel);
void ShowDialog();
void Close();
}
public class MainViewModel
{
private IMainView _view;
public MainViewModel(IMainView view)
{
_view = view;
backgroundWorker = new BackgroundWorker() { WorkerSupportsCancellation = true };
backgroundWorker.DoWork += BackgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted +=
BackgroundWorker_RunWorkerCompleted;
var pleaseWaitViewModel = new PleaseWaitViewModel(backgroundWorker);
_view.Init(pleaseWaitViewModel);
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_view.Close();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Some work
Thread.Sleep(5000);
}
private void DoSomethingImpl(object parameter)
{
_view.ShowDialog();
}
}
Messenger approach
public class PersonsViewModel
{
private RelayCommand _addPersonCommand = null;
public RelayCommand AddPersonCommand
{
get
{
return _addPersonCommand ?? (_addPersonCommand = new RelayCommand(
() =>
{
Action<Person> callback = (person) =>
{
_persons.Add(person);
RaisePropertyChanged("Persons");
};
Messenger.Default.Send<NotificationMessageAction<Person>>(new NotificationMessageAction<Person>(this, new Person(), "myNotification", callback), this);
}));
}
}
}
private PersonsViewModel _viewModel = null;
public PersonsView()
{
InitializeComponent();
DataContext = _viewModel = new PersonsViewModel();
Messenger.Default.Register<NotificationMessageAction<Person>>(this, _viewModel, message =>
{
if(message.Notification == "myNotification")
{
Person person = (Person)message.Target;
Action<Person> callback = message.Execute;
ModalView view = new ModalView(person);
if(true == view.ShowDialog())
{
callback.Invoke(view.Person);
}
}
});
}
Action property on view model approach
1) Add action property on the viewmodel
2) Wire it up in the view code behind
3) Invoke action it in the viewmodel logic where needed
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Wire up CancelAction in the View
var windowToClose = new Window();
var castedContext = (ViewModel) DataContext;
castedContext.CancelAction = () => windowToClose.Close();
}
}
public class ViewModel
{
private ICommand _doSomethingCommand;
public Action CancelAction { get; set; }
public ICommand DoSomethingCommand
{
get
{
if (_doSomethingCommand != null)
return _doSomethingCommand;
_doSomethingCommand = new MyCommandImplementation(() =>
{
// Perform Logic
// If need to cancel - invoke cancel action
CancelAction.Invoke();
});
return _doSomethingCommand;
}
}
}
// Stubbed out for the sake of complete code
public class MyCommandImplementation : ICommand
{
public MyCommandImplementation(Action action)
{
throw new NotImplementedException();
}
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public void Execute(object parameter)
{
throw new NotImplementedException();
}
public event EventHandler CanExecuteChanged;
}
}

BackgroundWorker track method from another class

[EDIT]
I edited my question with complete code and explanation and hope something can give me clearer explanation.
I have the following Class that has a backgroundworker to track the percentage progress of a loop and update the percentage progress on a Label on ProgressWin's XAML. The following code works perfectly. (My question is far below, after the code.)
public partial class ProgressWin : Window
{
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
public ProgressWin()
{
InitializeComponent();
InitializeBackgroundWorker();
startAsync();
}
// Set up the BackgroundWorker object by
// attaching event handlers.
private void InitializeBackgroundWorker()
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
private void startAsync()
{
backgroundWorker1.RunWorkerAsync();
}
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
// This event handler updates the progress.
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Content = (e.ProgressPercentage.ToString() + "%");
}
// This event handler deals with the results of the background operation.
public void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
resultLabel.Content = "Done!";
}
}
Here comes my problem, now instead of tracking the loop within the ProgressWin, I need to track the loop in my MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 1; i <= 10; i++)
{
System.Threading.Thread.Sleep(500);
????.ReportProgress(i * 10);
}
}
}
And I have no idea where to go from here. I tried instantiating an object from ProgressWin and call the DoWork but I end up frozen the ProgressWin window.
Due to the fact that the question was rewritten, i also rewrote my whole answer.
To get this to work your MainWindowhas a ProgressWindow and should use it like a background worker:
public partial class MainWindow : Window
{
private ProgressWindow _Progress;
public MainWindow()
{
InitializeComponent();
_Progress = new ProgressWindow();
_Progress.ProgressChanged += OnProgressChanged;
}
private void OnProgressChanged(object sender, ProjectChangedEventArgs e)
{
//ToDo: Update whatever you like in your MainWindow
}
}
To accomplish this your ProgressWindow should simply subscribe to the worker event and rethrow it:
public partial class ProgressWin : Window
{
// Add this to your class above in your question
public event ProgressChangedEventHandler ProgressChanged;
// Change or merge this with your existing function
private void backgroundWorker1_ProgressChanged(object sender, ProjectChangedEventArgs e)
{
var temp = ProgressChanged;
if(temp !=null)
temp(this, e);
}
}
You can simply call method of another class by doing
backgroudnWorker.DoWork += new DoWorkEventHandler(SomeClass.SomeStaticMethod);
or
backgroudnWorker.DoWork += new DoWorkEventHandler(SomeClassInstance.SomeMethod);
for calling a method of MainWindow class from another class ProgressScreen you should have reference of instance of MainWindow class in ProgressScreen then using that instance you can call any public method of MainWindow from ProgressScreen class
and as Oliver Said, you will need the instance of backgroundworker to send the progress updates from other method.

updating gui from another class c#

hey i am new to c# plz help.
i am writing a program that sorts data in a file and it is a time consuming process so i thought that i should run it in a separate thread and since it has alot of step so i made a new class for it. the problem is that i want to show the progress in the main GUI and i know for that i have to use Invoke function but the problem is that the form control variables are not accessible it this class. what should i do ??????
sample code:
public class Sorter
{
private string _path;
public Sorter(string path)
{
_path = path;
}
public void StartSort()
{
try
{
processFiles(_path, "h4x0r"); // Just kidding
}
catch (Exception e)
{
MessageBox.Show("Error: " + e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void processFiles(string Dir, string[] key)
{
/* sorting program */
}
and it is used as
public partial class Form1 : Form
{
Sorter sort;
public Form1()
{
InitializeComponent();
}
private void browseBtn_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
textBox1.Text = folderBrowserDialog1.SelectedPath;
}
private void startBtn_Click(object sender, EventArgs e)
{
if (startBtn.Text == "Start Sorting")
{
Thread worker = new Thread(new ThreadStart(delegate() {
sort = new Sorter(textBox1.Text);
sort.StartSort(); }));
worker.start();
}
else
MessageBox.Show("Cancel");//TODO: add cancelling code here
}
}
plz help..
Add an Event to your class that is doing the multi-threaded work, that triggers when the progress changes. Have your form subscribe to this event and update the progress bar.
Note ProgressEventArgs is a little class that inherits EventArgs and has an Integer for the progress.
// delegate to update progress
public delegate void ProgressChangedEventHandler(Object sender, ProgressEventArgs e);
// Event added to your worker class.
public event ProgressChangedEventHandler ProgressUpdateEvent
// Method to raise the event
public void UpdateProgress(object sender, ProgressEventArgs e)
{
ProgressChangedEventHandler handler;
lock (progressUpdateEventLock)
{
handler = progressUpdateEvent;
}
if (handler != null)
handler(sender, e);
}
I would recommend you read up on the BackgroundWorker class. It is exactly for the problem you are trying to solve and makes things a lot easier than doing manual threading yourself.
Brief Example
public Form1()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void btnStart_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i < 101; ++i)
{
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
//Sort Logic is in here.
Thread.Sleep(250);
backgroundWorker.ReportProgress(i);
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy && backgroundWorker.WorkerSupportsCancellation)
backgroundWorker.CancelAsync();
}
You could do something like this:
public delegate void StatusReporter(double progressPercentage);
public class MainClass
{
public void MainMethod()
{
Worker worker = new Worker(ReportProgress);
ThreadStart start = worker.DoWork;
Thread workThread = new Thread(start);
workThread.Start();
}
private void ReportProgress(double progressPercentage)
{
//Report here!!!
}
}
public class Worker
{
private readonly StatusReporter _reportProgress;
public Worker(StatusReporter reportProgress)
{
_reportProgress = reportProgress;
}
public void DoWork()
{
for (int i = 0; i < 100; i++ )
{
// WORK, WORK, WORK
_reportProgress(i);
}
}
}
There are a few option available to solve this sort of issue. In any case, you will have to fiddle with Invoke to get the UI to update.
You could...
...add an event that fires on your new class which your UI can listen to, and Invoke as applicable - you'd still need to pass the data to your worker class (by constructor, properties, method call, etc)
...keep the method as a method on your form, and pas that to start your new thread from (after all, a new thread doesn't have to be starting in a different class)
...change the access modifiers on your controls to be (say) internal such that any class within the same assembly can Invoke changes to the controls, or read from them.
...make your worker class a child of the form it needs to access - it can then see the privates of its parent, as long as it is passed a reference to the instance.

Categories