Update MVVM binding from event fired by asynchronous task - c#

I have a task that runs a continuous TCP read operation, when that read operation reads a JSON encoded message from the remote server it will fire an event to the Command.cs class where it will handle the raw JSON and convert it into a Response class, do the necessary checks and then fires an 'Finished' event where ultimately the client subscribes to where they can receive a Response class that can be added to a Observable Collection.
I've tried to implement a RaisePropertyChanged that calls the Invoke function on the Dispatcher that should update the ObservableCollection properly.
TCPRequest.cs
private static CancellationTokenSource cancellation;
private static event OnDataReceived datareceivedevent;
public static event OnDataReceived DataReceivedEvent
{
add
{
if(datareceivedevent == null)
{
datareceivedevent += value;
}
}
remove
{
datareceivedevent -= value;
}
}
private static async void ReadOperation(object t)
{
var token = (CancellationToken)t;
var stream = tcpClient.GetStream();
var byteBuffer = new byte[tcpClient.ReceiveBufferSize];
while (!token.IsCancellationRequested)
{
int lRead = 0;
if (stream.DataAvailable)
{
lRead = await stream.ReadAsync(byteBuffer, 0, byteBuffer.Length);
}
if (lRead > 0)
{
var response = ASCIIEncoding.ASCII.GetString(byteBuffer, 0, lRead);
datareceivedevent(response);
}
}
}
public static void StartReading()
{
Task.Factory.StartNew(ReadOperation, cancellation.Token, cancellation.Token);
}
The above code is the task that runs until a cancel is requested. When the data is available it will convert to string and will fire the event (showed above).
Command.cs
public event OnDataReceivedDeserialized OnDataReceivedDeserialized;
public bool Execute()
{
this.JSONFormat = ToJson();
TCPRequest.DataReceivedEvent += TCPRequest_DataReceivedEvent;
if (!JSONFormat.Equals(string.Empty))
{
return TCPRequest.SendToServer(this);
}
else
{
return false;
}
}
The execute function is within a Command class that handles everything needed to send JSON to the server. This Command class subscribes to the TCPRequest datareceivedevent event.
if (TCPRequest.IsConnected)
{
Command cmd = new Command();
cmd.RequestCommand = new Request(RequestType.info);
cmd.OnDataReceivedDeserialized += Cmd_OnDataReceivedDeserialized;
cmd.Execute();
}
public void Cmd_OnDataReceivedDeserialized(RequestResponse response)
{
LoPyList.Add(new LoPy() { name = "Test", id = "00" });
}
And then above it will create the command that subscribes to the command event where the value of the server response will go.
GraphViewModel.cs
private ObservableCollection<LoPy> lopyList;
public ObservableCollection<LoPy> LoPyList
{
get { return lopyList; }
set {
lopyList = value;
RaisePropertyChangedEvent("LoPyList"); }
}
And lastly, above is the LoPyList that is binded to the interface combobox.
GraphView.xaml
<ComboBox Grid.Row="1" ItemsSource="{Binding Path=LoPyList}"
DisplayMemberPath="name"/>
The thing I need is that the function inside of the ViewModel updates the LoPyList and that I can view it in the UI.

When your event fires, what you are doing is calling the "Add" method on the ObservableCollection:
LoPyList.Add(new LoPy() { name = "Test", id = "00" });
The problem here is that your RaisePropertyChangedEvent only occurs when you set the LoPyList property. When you call Add(), the setter does not run and so RaisePropertyChangedEvent is never called and your binding is never updated.
What you could do is add:
RaisePropertyChangedEvent("LoPyList"); after you call Add():
public void Cmd_OnDataReceivedDeserialized(RequestResponse response)
{
LoPyList.Add(new LoPy() { name = "Test", id = "00" });
RaisePropertyChangedEvent(nameof(LoPyList));
}
Alternatively, ObservableCollection exposes a CollectionChanged event that
Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
You can hook into that event and then call RaisePropertyChangedEvent("LoPyList");

It turned out that I initialize the viewmodel twice and use the second viewmodel that is not connected to the view. Afterward I used the dispatcher and it works like a charm.
Thanks for the help!

Related

Async call + event + dispatcher = unexpected behavior

I have a mysterious problem, which I can't reproduce on my PC, but it happens on other PCs.
Here is a simplified cut which shows the problem
class SomeViewModel
{
Item _item;
Job _job = new Job();
public SomeViewModel()
{
_job.Progress += (s, e) => App.Current.Dispatcher.Invoke(() => Progress());
}
async void DoSomething()
{
...
SomeItem.SomeProperty = newValue; // this proves SomeItem is not null
_item = SomeItem;
var result = await Task<bool>.Run(() => _job.Do());
_item = null;
...
}
void Progress()
{
if(_item == null) { ... } // sometimes true, why???
...
}
}
class Job
{
public event EventHandler Progress;
public bool Do()
{
...
Progress?.Invoke(this, EventArgs.Empty); // in cycle
...
return true;
}
}
DoSomething is called from button command. Idea is to store current item in the field and access it within event handler. Event is never called before item is set to something and is never called after item is set to null.
My question: how is it possible for _item == null in event handler?
It will either never happens (my PC) or happens every time (other PC). And I can't understand what is wrong. Any ideas?

Do an action from ViewModel right after ShowDialog

I have a ViewModel like this:
public class WelcomeWindowVm : ViewModel
{
private ViewModel view;
public WelcomeWindowVm(){
this.View = new LoginVm() {
Completed += (o, e) => {
this.View = new OtherVm(e.User){
Completed += (o, e) =>; // and so on
}
}
};
}
public ViewModel View {
get {
return this.view;
}
set {
this.view = value;
this.OnPropertyChanged(nameof(this.View));
}
}
}
LoginVm is another Viewmodel whose Completed event is triggered when a Command on it is completed (The event is only triggered when correct login credentials are used). OtherVm is another vm whose completed event is triggered for whatever reason.
I render the View using a DataTemplate. For example:
<Window.Resources>
<DataTemplate DataType="vm:LoginVm">
Textboes and buttons here
</DataTemplate>
<DataTemplate DataType="vm:OtherVm">
...
</DataTemplate>
</Window.Resources>
<ContentControl Content={Binding View} />
The DataContext of this window is set to WelcomeWindowVm class above, before ShowDialog.
This works well. When the Window is shown using ShowDialog, LoginVm is shown. Then OtherVm when whatever task of LoginVm is completed, and so on.
Now I thought of converting the Completion stuff to Async/await pattern. The LoginVm now looks like this:
public LoginVm{
...
private TaskCompletionSource<User> taskCompletionSource = new TaskCompletionSource<User>();
...
// This is the Relay command handler
public async void Login()
{
// Code to check if credentials are correct
this.taskCompletionSource.SetResult(this.user);
// ...
}
public Task<User> Completion(){
return this.taskCompletionSource.Task;
}
}
Instead of this:
public LoginVm{
public event EventHandler<CustomArgs> Completed;
// This is the Relay command handler
public async void Login()
{
// Code to check if credentials are correct
OnCompleted(this.user);
// ...
}
}
So that I can use it like this:
public WelcomeWindowVm(){
var loginVm = new LoginVm();
this.View = new LoginVm();
User user = await loginVm.Completion();
var otherVm = new OtherVm(user);
this.View = otherVm;
Whatever wev = await otherVm.Completion();
//And so on
}
But I can't use await in a Constructor and even if I use an async Method for that, how will I call it in another class after calling ShowDialog since ShowDialog blocks?
I think using an async void will work. But from what I have heard, it should be avoided unless I am using it in an event handler.
Maybe use an async Task method but not await it?
You can do it like this:
public WelcomeWindowVm() {
var loginVm = new LoginVm();
this.View = loginVm;
loginVm.Completion().ContinueWith(loginCompleted =>
{
var otherVm = new OtherVm(loginCompleted.Result);
this.View = otherVm;
otherVm.Completion().ContinueWith(whateverCompleted =>
{
});
});
}

mvvm light -Sending Notification message with callback

I need the result of FolderBrowserDialog in my view-model,
CodeBehind.cs
private static void SelectFolderDialog()
{
using (System.Windows.Forms.FolderBrowserDialog folderdialg = new System.Windows.Forms.FolderBrowserDialog())
{
folderdialg.ShowNewFolderButton = false;
folderdialg.RootFolder = Environment.SpecialFolder.MyComputer;
folderdialg.Description = "Load Images for the Game";
folderdialg.ShowDialog();
if (folderdialg.SelectedPath != null)
{
var notifypath = new GenericMessage<string>(folderdialg.SelectedPath);
Messenger.Default.Send(notifypath);
}
}
What i'm planning is , From View-model send a notification with callback to view , executing the FolderBrowserDialog return the Selected path back to the view model.
How do i send notificationmessage with callback / NotificationWithAction using MVVM-Light . please help me with a sample as I'm new to Wpf and MVVM-Light.
Any Help is appreciated
I was looking for almost the exact same thing except for with a SaveFileDialog. Here is what I came up with:
Create a message class with an Action<string> property and a constructor with an Action<string> argument.
public class SelectFolderMessage
{
public Action<string> CallBack {get;set;}
public SelectFolderMessage(Action<string> callback)
{
CallBack = callback;
}
}
In your ViewModel class, pass in a method or lambda expression when you call Messenger.Default.Send. I set a property in my ViewModel class with the path returned by the view. I wrapped this inside the execute section of a RelayCommand. I bound the RelayCommand to a button in the view
...
new RelayCommand(() =>
{
Messenger.Default.Send(new SelectFolderMessage(
(pathfromview) => { viewmodelproperty = pathfromview;}));
})
In your view code behind, create a method to handle the message and register the handler with the messenger service. Don't forget to unregister if this is not your main window.
public MainWindow()
{
Messenger.Default.Register<SelectFolderMessage>(this, SelectFolderHandler);
}
private void SelectFolderHandler(SelectFolderMessage msg)
{
using (System.Windows.Forms.FolderBrowserDialog folderdialg = new System.Windows.Forms.FolderBrowserDialog())
{
folderdialg.ShowNewFolderButton = false;
folderdialg.RootFolder = Environment.SpecialFolder.MyComputer;
folderdialg.Description = "Load Images for the Game";
folderdialg.ShowDialog();
if (folderdialg.SelectedPath != null)
{
msg.CallBack(folderdialg.SelectedPath);
}
}
}
I came up with this idea reading Laurent Bugnion's Messenger article in MSDN Magazine: http://msdn.microsoft.com/en-us/magazine/jj694937.aspx

View not updating when property is changed by event from another thread

I am trying to run something like a background thread and notify a ViewModel of the status of this background thread by raising an event. The ViewModel in turn raises the OnPropertyChanged event. Unfortunately the corresponding view will not update.
The part of the background thread where I notify the ViewModel can be seen here:
private void RunThread() {
while(true) {
suspendEvent.WaitOne(Timeout.Infinite);
if (shutDownEvent.WaitOne(0)) {
break;
}
if (pauseEvent.WaitOne(0)) {
pauseEvent.Reset();
}
Notify("Cleaning started!");
pauseEvent.WaitOne(TimeSpan.FromSeconds(5));
Clean();
Notify("Cleaning finished!");
pauseEvent.WaitOne(TimeSpan.FromSeconds(5));
}
}
private void Notify( String status ) {
NotifyOfCleanerStatusHandler handler = NotifyOfcleanerStatus;
if (handler != null) {
handler(status);
}
}
The part of the ViewModel where I receive the event can be seen here:
public void SetCleanerStatus( String status ) {
CleanerStatus = status;
}
And finally, the property that I bind my view to is seen here:
public String CleanerStatus {
get {
return cleanerStatus;
}
set {
if (value != null) {
cleanerStatus = value;
OnPropertyChanged("CleanerStatus");
}
}
}
The intriguing thing is: The View will show one of the above statuses, either "Cleaning started!" or "Cleaning finished!". One would think that they should alternate in an interval of 5 seconds. This is not the case.
If I remove the first waithandle call (pauseEvent.WaitOne(TimeSpan.FromSeconds(5));) the message in the view is "Cleaning finished" and stays that way. If i keep the line, the message is "Cleaning started!" and stays that way. Debugging shows that the line OnPropertyChanged(..) is reached.
What am I doing wrong?
Simplify your RunThread method:
private ManualResetEvent suspendEvent = new ManualResetEvent(false);
private bool shutdown;
private void RunThread()
{
while (!shutdown)
{
suspendEvent.WaitOne();
if (shutdown)
{
break;
}
Notify("Cleaning started!");
Thread.Sleep(TimeSpan.FromSeconds(5));
Notify("Cleaning finished!");
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
When updating the UI it might also be necessary to invoke the PropertyChanged handler in the UI thread:
public string CleanerStatus
{
get { return cleanerStatus; }
set
{
cleanerStatus = value;
App.Current.Dispatcher.BeginInvoke(
new Action(() => OnPropertyChanged("CleanerStatus")));
}
}

How to write a Trigger?

I want my C# code to call an event whenever a value is assigned to my object.
How exactly would I need to go about that?
class MyClass {
ManualResetEvent mre;
public MyClass() {
mre = new ManualResetEvent(false);
Data = null;
}
public object Data { get; set; }
void DataSet(object sender, EventArgs e) {
Console.WriteLine("object Data has been set.");
mre.Set();
}
}
Delegates don't seem to be what I need. An event, maybe? How would I write such an event, if so?
MyClass mc;
void processA() {
mc = new MyClass();
mc.Data = GetDataFromLongProcess();
}
private object data;
public object Data {
get { return data;}
set {
if(value != data) {
data = value;
OnDataChanged();
}
}
}
protected virtual void OnDataChanged() {
EventHandler handler = DataChanged;
if(handler != null) handler(this, EventArgs.Empty);
}
public event EventHandler DataChanged;
then hook any code to the DataChanged event. For example:
MyClass mc = ...
mc.DataChanged += delegate {
Console.WriteLine("new data! wow!");
};
If you want to fire an event when your property is set, you would do something like this:
public event Action OnDataChanged;
protected object _data = null;
public object Data
{
get { return _data; }
set
{
_data = value;
if(OnDataChanged != null)
OnDataChanged();
}
}
Then you would simply wire up event handlers to your object like so:
mc = new MyClass();
mc.OnDataChanged += delegate() { Console.WriteLine("It changed!"); };
mc.Data = SomeValue();
I think you're on the right track with an event-based model. Also take a look at the Observer pattern (which is the basis for .Net delegates and events underneath it all, as I understand):
http://www.dofactory.com/Patterns/PatternObserver.aspx
But the bottom line, as the other useful answer so far (Mr. Gravell's implementation) indicates, you're going to have to have code IN the setter to get it hooked up. The only alternative would be to poll the value for changes, which just smells bad to me.
you could implement INotifyPropertyChanged (this is more or less a event) or you could take your class a Action (Trigger) and call this, whenn the property changed.
Just don't use automatic properties but a concrete setter and call your event/trigger from there.
Conceptually, you would define an event in your class, and in your property set blocks, you would invoke the event with the necessary arguments to determine what just happened.
public event SomeDelegateThatTakesIntAsParameter myEvent;
void SetData(int data)
{
if(myEvent!= null)
myEvent(data)
}

Categories

Resources