Where to place the BackgroundWorker - c#

I have a class that gathers information about a machine (this is an example - in total GetInfo() may take minutes to run):
public class Scanner
{
public void GetInfo()
{
this.Name = GetName();
this.OS = GetOS();
}
public string Name { get; set; }
public string OS { get; set; }
public string Status { get; set; }
private string GetName() { this.Status = "Getting computer name"; /*More operations*/ }
private string GetOS() { this.Status = "Getting OS"; /*More operations*/ }
}
This is called by a form that needs to provide status feedback to the user.
TextBox1.Text = scanner.Status;
My question is, to achieve this, what is the best way to implement threading so that the application remains responsive?
I have got a BackgroundWorker running in the form and calling GetName(), GetOS() etc itself and that works fine, but the code isn't very repeatable - to keep maintenance low I want to keep a GetInfo() method in the class so if I need to run a scan from elsewhere theres only one piece of code that knows about how to.
I could move the BackgroundWorker in to GetInfo(), but then how would the form be able to check the Status property without doing a loop and locking up the UI?
Maybe have a BackgroundWorker in the form run GetInfo() and then run another Worker that would check Status and update the form if a change is detected?
This is my first proper go at Threading and can't get my head around what, I think, is a fairly common task so any help would be appreciated.
Note: I'm also well open to suggestions for other ways to implement the Status property - hopefully you get what I'm trying to achieve.
/edit: Some clarification.
Scanner.GetInfo() would be called manually, for example on a form button click. GetInfo() would then start populating the objects properties as it goes gathering information, and might take 5 minutes to complete.
I need to be able to keep the user up to date on its status, and the only way I can think of that happening (with my current knowledge) is for GetInfo() to update a 'Scanner.Status' property, which the form can then check at interval/within a loop and update the UI when it changes.

How about using INotifyPropertyChanged with a thread in the class as follows:
public class Scanner : INotifyPropertyChanged
{
private string _Name, _OS;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return _Name;
}
set
{
if (value != _Name)
{
_Name = value;
NotifyPropertyChanged("Name");
}
}
}
public string OS
{
get
{
return _OS;
}
set
{
if (value != _OS)
{
_OS = value;
NotifyPropertyChanged("OS");
}
}
}
public void GetInfo()
{
_Name = string.Empty;
_OS = string.Empty;
new Thread(() => this.Name = GetName()).Start();
new Thread(() => this.OS = GetOS()).Start();
}
private void NotifyPropertyChanged(string pName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pName));
}
}
private string GetName()
{
return "long name operation here";
}
private string GetOS()
{
return "long OS operation here";
}
}
Then in your form you could use the following:
Scanner m_Scanner = new Scanner();
public void Main()
{
m_Scanner.PropertyChanged += UpdateGUI;
m_Scanner.GetInfo();
}
private void UpdateGUI(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "OS")
txtOs.Text = m_Scanner.OS;
else if (e.PropertyName == "Name")
txtName.Text = m_Scanner.Name;
}

Related

When I first enter an input (name), the message does not display the name. I have to press enter again for the message to display with the name

using System;
public class NameChangeEventArgs : EventArgs
{
public string Name { get; private set; }
public NameChangeEventArgs(string name)
{
this.Name = name;
}
}
public class Dispatcher
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
OnNameChange(_name);
_name = value;
}
}
public event EventHandler<NameChangeEventArgs> NameChange;
protected virtual void OnNameChange(string name)
{
NameChange?.Invoke(this, new NameChangeEventArgs(name));
}
}
public class Handler
{
public void OnDispatcherNameChange(object Source, NameChangeEventArgs args)
{
Console.WriteLine("Dispatcher's name changed to {0}", args.Name);
}
}
class Program
{
static void Main(string[] args)
{
var dispatcher = new Dispatcher();
var handler = new Handler();
dispatcher.NameChange += handler.OnDispatcherNameChange;
var name = "Sidd";
dispatcher.Name = name;
}
}
The purpose of my code is to change Dispatcher's name to the name passed to it, but whilst this is done, an event is raised to run a method in the Handler class to display the name to which the Dispatcher has been changed to.
The "OnDispatcherNameChange(object Source, NameChangeEventArgs args)" is called to display the message "Dispatcher's name changed to " in my dispatcher's setter.
However it outputs this instead...
Dispatcher's name changed to
Press any key to continue...
What have I done wrong?
I think it's because you're raising the event before changing the property.
The code that receives the event will read the old name and not notice the new one.
Try doing this instead:
set
{
// First change the property
_name = value;
// Then raise the event
OnNameChange(_name);
}

Changes to collection do not update UI

I don't understand why when I update a object, my bound controls do not update.
The data displays fine initially, but when I want to refresh the data displayed in the UI nothing happens when I update the object. The object updates fine. The ViewModel does use INotifyPropertyChanged on all fields.
However if I update individual items directly, I can update my UI. As commented below.
I guess I've made a school boy error somewhere here?
UPDATE: I've added the model to the question. While I understand the answers, I don't understand how to implement it. Attempted to implement a collection changed event without success. Can I have some pointers please?
public partial class CisArrivalsPanel : UserControl
{
private ApiDataArrivalsDepartures _theArrivalsDepartures;
public CisArrivalsPanel()
{
InitializeComponent();
_theArrivalsDepartures = new ApiDataArrivalsDepartures();
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Kings Cross");
this.DataContext = _theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
//However this (when uncommented, and I comment out the above line) does update the UI**
//_theArrivalsDepartures.StationMovementList[0].OriginName = "test";
//_theArrivalsDepartures.StationMovementList[0].Platform = "0";
//_theArrivalsDepartures.StationMovementList[0].BestArrivalEstimateMins = "999";
//_theArrivalsDepartures.StationName = "test";
}
private void StationHeader_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Reload();
Debug.WriteLine(_theArrivalsDepartures.StationName);
foreach (var a in _theArrivalsDepartures.StationMovementList)
{
Debug.WriteLine(a.OriginName);
Debug.WriteLine(a.BestArrivalEstimateMins);
}
}
}
EDIT : Added Model
public class ApiDataArrivalsDepartures : INotifyPropertyChanged
{
private string _stationName;
[JsonProperty(PropertyName = "station_name")]
public string StationName {
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private List<StationListOfMovements> _stationMovementList;
public List<StationListOfMovements> StationMovementList
{
get
{
return _stationMovementList;
}
set
{
_stationMovementList = value;
NotifyPropertyChanged("StationMovementList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class StationListOfMovements : INotifyPropertyChanged
{
private string _originName;
[JsonProperty(PropertyName = "origin_name")]
public string OriginName {
get
{
return _originName;
}
set
{
_originName = value;
NotifyPropertyChanged("OriginName");
}
}
[JsonProperty(PropertyName = "destination_name")]
public string DestinationName { get; set; }
private string _platform;
[JsonProperty(PropertyName = "Platform")]
public string Platform {
get
{
return _platform;
}
set
{
_platform = value;
NotifyPropertyChanged("Platform");
}
}
private string _bestArrivalEstimateMins;
[JsonProperty(PropertyName = "best_arrival_estimate_mins")]
public string BestArrivalEstimateMins {
get
{
return _bestArrivalEstimateMins;
}
set
{
_bestArrivalEstimateMins = value;
NotifyPropertyChanged("BestArrivalEstimateMins");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
There are two pieces here pertaining to your collection (technically three):
If you want a new collection to propagate, the collection property has to raise PropertyChanged (sounds like it does)
If you want add/remove on the collection to propagate, you need to use a collection that implements INotifyCollectionChanged. ObservableCollection is a good choice.
If you want changes to the items in the container to propagate, then those items need to implement INotifyPropertyChanged and raise the PropertyChanged event.
Make sure all those are covered, and the changes should appear on the UI as you expect.
You should update the DataContext and ItemsSource too.
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
DataContext = theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
Use for the collection ObservableCollection , this class notify the ui when change to the collection occurred
your reload function works because the there is PropertyChanged on all the fields include this one
it notify the ui and reload the correct collection

Has the DataSource been changed via a BindingSource?

I'm using a BindingSource to connect a single large data-structure to numerous controls (no SQL, just one data-structure, lots of controls). Old-style Windows Forms application, Visual Studio 2012 Express. I have managed to wrap numerous GUI components with properties to work around the lack of direct support for control binding of things like radio button groups, multi-select listboxes, the title bar, etc. All this works great, updates flow nicely in both directions between the GUI and the data-structure.
I need to track if any changes to the data-structure have been made via any control on the GUI, but I can't see how to do this (I did look at previous related questions here)... This is needed to provide a simple indication of "changes made but not saved", with an asterisk in the title bar and a warning if the user tries to exit the application without saving changes.
Thanks in advance for any help !
You'll have to implement the INotifyPropertyChanged interface from within your object classes, then catch whenever a change occurs through proper event handlers for your type class within your DataSource BindingSource property.
Here's an example:
using System;
using System.ComponentModel;
namespace ConsoleApplication1
{
internal class Program
{
class Notifications
{
static void Main(string[] args)
{
var daveNadler = new Person {Name = "Dave"};
daveNadler.PropertyChanged += PersonChanged;
}
static void PersonChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("Something changed!");
Console.WriteLine(e.PropertyName);
}
}
}
public class Person : INotifyPropertyChanged
{
private string _name = string.Empty;
private string _lastName = string.Empty;
private string _address = string.Empty;
public string Name
{
get { return this._name; }
set
{
this._name = value;
NotifyPropertyChanged("Name");
}
}
public string LastName
{
get { return this._lastName; }
set
{
this._lastName = value;
NotifyPropertyChanged("LastName");
}
}
public string Address
{
get { return this._address; }
set
{
this._address = value;
NotifyPropertyChanged("Address");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}

Very very simple MVVM problem

I am trying to make my very first Silverlight App ever, but I can't get the LogOn function to work, can you help me? This should properly be super simple for all of you, I will show you my two files: LogOn.xaml.cs and LogOnViewModel.cs
Apparently the problem is that UserId gets not set early enough to be availble in LogOn.xaml.cx when I need it, can you help me make it work, that would lift my moment quite a bit :-)
public partial class LogOn : PhoneApplicationPage
{
public LogOn()
{
InitializeComponent();
this.DataContext = LogOnViewModel.Instance;
}
private void btnLogOn_Click(object sender, RoutedEventArgs e)
{
if ((!string.IsNullOrEmpty(txtEmailAddress.Text)) && (!string.IsNullOrEmpty(txtPassword.Password)))
{
txbLogonMessage.Text = "";
LogOnViewModel.Instance.UserLogin(txtEmailAddress.Text, txtPassword.Password);
if (LogOnViewModel.Instance.UserId > 0)
NavigationService.Navigate(new Uri("/_2HandApp;component/Views/Main.xaml", UriKind.Relative));
else
txbLogonMessage.Text = "Login was unsuccessful. The user name or password provided is incorrect. Please correct the errors and try again. ";
}
}
}
public sealed class LogOnViewModel : INotifyPropertyChanged
{
public static LogOnViewModel Instance = new LogOnViewModel();
//public static int userId;
private SHAServiceClient WS;
private int userId;
public int UserId
{
get
{
return userId;
}
set
{
userId = value;
this.RaisePropertyChanged("UserId");
}
}
private LogOnViewModel()
{
WS = new SHAServiceClient();
WS.UserLoginCompleted += new EventHandler<UserLoginCompletedEventArgs>(WS_UserLoginCompleted);
}
void WS_UserLoginCompleted(object sender, UserLoginCompletedEventArgs e)
{
if (e.Error == null)
{
this.UserId = e.Result;
}
}
public void UserLogin(string email, string password)
{
WS.UserLoginAsync(email, password);
}
/* Implementing the INotifyPropertyChanged interface. */
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null))
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The cause of the problem is what has been highlighted by #flq. You're making an asynchronous call, meaning that you won't get the expected result right away (in your case, the UserId being assigned), but instead, you can subscibe to the Completed event (or provide a callback) to handle things when the asynchronous task finishes.
Now, the "MVVM way" to do this (or at least what I would do) is as follows: first of all, go get MVVM Light! it's a lightweight MVVM framework which would be very helpful. You should have your ViewModel class implement the ViewModelBase base class from MVVMLight, this would provide the change notification and messaging as well as other useful stuff. Then, you should encapsulate the login functionality in a command to be able to wire up it up from xaml, for that you can use MVVMLight's RelayCommand. Once the login is complete, you can just send a message to your view letting it know that (in a pretty decoupled way), and the view can simply initiate the navigation.
Here's the bits of code for that:
public class LogOnViewModel : ViewModelBase
{
private SHAServiceClient WS;
public LogOnViewModel()
{
WS = new SHAServiceClient();
WS.UserLoginCompleted += new EventHandler<UserLoginCompletedEventArgs>(WS_UserLoginCompleted);
LoginCommand = new RelayCommand(UserLogin);
}
private int userId;
public int UserId
{
get { return userId; }
set
{
userId = value;
RaisePropertyChanged(()=>UserId);
}
}
private int password;
public int Password
{
get { return password; }
set
{
password = value;
RaisePropertyChanged(()=>Password);
}
}
private int username;
public int Username
{
get { return username; }
set
{
username = value;
RaisePropertyChanged(()=>Username);
}
}
private int loginErrorMessage;
public int LoginErrorMessage
{
get { return loginErrorMessage; }
set
{
loginErrorMessage = value;
RaisePropertyChanged(()=>LoginErrorMessage);
}
}
void WS_UserLoginCompleted(object sender, UserLoginCompletedEventArgs e)
{
if (e.Error == null)
{
this.UserId = e.Result;
// send a message to indicate that the login operation has completed
Messenger.Default.Send(new LoginCompleteMessage());
}
}
public RelayCommand LoginCommand {get; private set;}
void UserLogin()
{
WS.UserLoginAsync(email, password);
}
}
for the xaml:
<TextBox Text="{Binding Username, Mode=TwoWay}"/>
<TextBox Text="{Binding Password, Mode=TwoWay}"/>
<Button Command="{Binding LoginCommand}"/>
<TextBlock Text="{Binding LoginErrorMessage}"/>
in the code behind:
public partial class LogOn : PhoneApplicationPage
{
public LogOn()
{
InitializeComponent();
this.DataContext = new LogOnViewModel();
Messenger.Default.Register<LoginCompletedMessage>(
this,
msg=> NavigationService.Navigate(
new Uri("/_2HandApp;component/Views/Main.xaml",
UriKind.Relative) );
}
....
}
You can see that there is a little bit more (but straightforward) code in the ViewModel and less in the code behind. This also took advantage of DataBinding which is in the heart of MVVM.
Hope this helps :)
P.S: the LoginCompletedMessage class is just an empty class in this case (used just to define the type message), but you can use it to send more info (maybe you still want to have the UserId sent)
Well, you're calling an async version of a login WS.UserLoginAsync, which means the execution moves on and indeed there is no user id when you check for it.
You aren't really doing MVVVM here, but anyway, let's go with the flow. Have an event on your "Viewmodel" that is raised when the login process is finished (WS_UserLoginCompleted). You can handle it and trigger Navigation in an event-handler of that event.

how to update listbox items with INotifyPropertyChanged

I have a listbox which is databound to a collection of objects.
I want to modify the way the items are displayed to show the user which one of these objects is the START object in my program.
I tried to do this the following way, but the listbox does not automatically update.
Invalidating the control also didn't work.
The only way I can find is to completely remove the databindings and add it back again. but in my case that is not desirable.
Is there another way?
class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get
{
if (PersonManager.Instance.StartPerson == this)
return _name + " (Start)";
return _name;
}
set
{
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public Person(string name)
{
Name = name;
}
}
This is the class wich manages the list and the item that is the start
class PersonManager
{
public BindingList<Person> persons { get; set; }
public Person StartPerson { get; set; }
private static PersonManager _instance;
public static PersonManager Instance
{
get
{
if (_instance == null)
{
_instance = new PersonManager();
}
return _instance;
}
}
private PersonManager()
{
persons = new BindingList<Person>();
}
}
In the form I use the following code
private void button1_Click(object sender, EventArgs e)
{
PersonManager.Instance.StartPerson = (Person)listBox1.SelectedItem;
}
I'm pretty sure that the problem is that, when you do this, you're effectively making the Person.Name properties "get" accessor change the value (and act like a set accessor as far as the UI is concerned).
However, there is nothing that's updating the bindings to say that this is happening. If PropertyChanged got called when you set start, I believe this would update.
It's clunky, but the way you have it written, I believe you could add this and make it work (NOTE: I didn't test this, so it ~may~ have issues):
private void button1_Click(object sender, EventArgs e)
{
Person newStart = (Person)listBox1.SelectedItem;
if (newStart != null)
{
PersonManager.Instance.StartPerson = newStart;
newStart.Name = newStart.Name; // Dumb, but forces a PropertyChanged event so the binding updates
}
}

Categories

Resources