I have a strange behavior in my project. I use MvvmLight messenger to notify different parts of my UI to update.
public EntryViewModel(MenuViewModel menuVM, Entry item)
{
this._menuVM = menuVM;
OpenDetailsCommand = new RelayCommand(OpenDetailsInMainWindow);
BackCommand = new RelayCommand(Back);
this._entry = item;
Refresh();
Messenger.Default.Register<CardUpdateMessage>(this, this._entry.Id, msg => Refresh(); );
}
and send with
Messenger.Default.Send(new CardUpdateMessage(id), id);
when I see Messenger content after registration it contains about 400 registered actions of CardUpdateMessage, but when I call send none of them fires.
By the way, similar code with single registered object per Message type work as I expect. What is the cause of this problem?
Update: I have made some research with debugger and found that in file https://mvvmlight.codeplex.com/SourceControl/latest#GalaSoft.MvvmLight/GalaSoft.MvvmLight (PCL)/Messaging/Messenger.cs method SendToList is fired and in its inner loop WeakAction is fired, but Refresh method is not run. Does it something I haven't suspected with WeakAction?
Solution: I have found the case of this issue. It's NOT ALLOWED to use anonimous function in Messenger.Register due to unexpected behavior.
just Messenger.Default.Register(this, this._entry.Id, Refresh); and
private void Refresh(CardUpdateMessage msg) ...
External reference: https://mvvmlight.codeplex.com/workitem/7640
This is the answer https://mvvmlight.codeplex.com/workitem/7640 and this
Strange behavior with actions, local variables and garbage collection in MVVM light Messenger
Solution in short words - do no use lambdas in Messenger.Default.Register, because the can behave not as you expected.
Related
I am trying to change a string, being used for a title, in my blazor-server-side application. But I am having trouble getting the UI to update.
I tried using StateHasChanged(), but that didn't work so I looked around and found that on the FlightFinder Demo that was made, it has an OnChange event Action, so I am trying to implement that.
It works until I try to refresh the browser, then I am hit with this error
System.InvalidOperationException: 'The current thread is not associated with the renderer's synchronization context. Use Invoke() or InvokeAsync() to switch execution to the renderer's synchronization context when triggering rendering or modifying any state accessed during rendering.'
This is what I have:
private string _title = "TestSite";
public string Title => _title;
public event Action OnChange;
public void ChangePage(string pageName)
{
_title = pageName;
NotifyStateChanged();
}
private void NotifyStateChanged(int navigationType = 0)
{
OnChange?.Invoke();
}
All I have to do is call ChangePage("some Page Title") and it works, unless as I mentioned I try to refresh.
I am just trying to change a string on one component that through another component, which doesn't sound all that crazy. If there is a better way to do titles or change things from other components, I would love to hear about it.
So, what can I do to make sure that m invoke method is on the correct thread?
Or is there a different way to change the title that would be more effective?
Thank you in advance!
I have just implemented a State Container like this and ran into the same error - but my service needs to be a singleton.
So I found an example on the aspnetcore git that does exactly what the error message says to do.
Call InvokeAsync -- not from your state container but when you try to change the state of your razor component.
https://github.com/dotnet/aspnetcore/blob/321db9d99f84cf7a67d453384292d9339de748d1/src/Components/test/testassets/BasicTestApp/DispatchingComponent.razor
So your state container doesn't need to change, just your component event handler does.
#code{
protected override void OnInitialized()
{
_YourService.OnChange += OnMyChangeHandler;
}
public void Dispose()
{
_YourService.OnChange -= OnMyChangeHandler;
}
private async void OnMyChangeHandler(object sender, EventArgs e)
{
// InvokeAsync is inherited, it syncs the call back to the render thread
await InvokeAsync(() => {
DoStuff();
StateHasChanged();
});
}
}
Now your service (if it's a singleton) can notify ALL your users at once! Think about all hoops we had to jump through in past to do this.
I posted this first thing in the morning thinking that I wouldn't have the time to look into and thinking that by time someone was able to help me out, I would have found the time to look into it more. Though I have spent a couple of days going back and forth on this already.
I finally found this article that explains that what I am trying to do is called a State Container.
What they said is that I could inject the class as a singleton, which is what I was doing or a scoped service. Turns out all I needed to do was change it to a scoped service and it works great!
no need sophisticated solution, Blazor working perfectly if you will update GUI in your event handler by
this.InvokeAsync(() => this.StateHasChanged());
it worked using await InvokeAsync(stateHasChanged); in my case
I have just started using MvvmCross, but i didn't find any info about to how i can execute UI code from a ViewModel.
On Caliburn there are coroutine so i can access the view and keep the ui code separated from the viewmodel code.
on my first case i need to open a dialow from a command inside a ViewModel, what is the correct way?
Right now i'm developing a WinRT app.
Thanks
There isn't any hard/fast rule on this within MvvmCross.
Generally, when I need to do this I use the Messenger plugin.
This answer assumes you are using the latest Alpha v3 code. For older vNext code you'll have to do some translation - see notes below.
To use this approach:
I reference Cirrious.MvvmCross.Plugins.Messenger.dll from both Core and UI projects.
Then I add a line somewhere in Setup.cs (e.g. in InitializeLastChance) to:
Cirrious.MvvmCross.Plugins.Messenger.PluginLoader.Instance.EnsureLoaded();
Then in the Core project I add a message:
public class InputIsNeededMessage : MvxMessage
{
public InputIsNeededMessage(object sender) : base(sender) {}
}
In the ViewModel I can get the Messenger by constructor injection or by:
var messenger = Mvx.Resolve<IMvxMessenger>();
and I can send messages by calling:
messenger.Publish(new InputIsNeededMessage(this));
In the View I can again get to the messenger and subscribe to messages using:
var messenger = Mvx.Resolve<IMvxMessenger>();
_token = messenger.SubscribeOnMainThread<InputIsNeededMessage>(OnInputIsNeeded);
where _token must be a member variable - if it isn't then the subscription won't persist - the subscription itself is weak by default (so you never have to unsubscribe)
and where OnInputIsNeeded is something like:
private void OnInputIsNeeded(InputIsNeededMessage message)
{
if (message.Sender != ViewModel)
return;
// do stuff here - you are already on the UI thread
}
The above sequence is what I normally do for 'proper code'
To start with using a Messenger/EventAggregator can feel uncomfortable - it certainly took me a while to get used to it - but after I did get used to it, then I now use it everywhere - the pub/sub Message decoupling is very flexible for testing and for future maintenance of code (IMO)
As alternatives to this approach above I do sometimes take shortcuts:
sometimes I fire normal C# events from the ViewModel and have the View respond to these
sometimes I have special marker properties and fire the UI code from them
Sorry for using v3 syntax - but the changeover is coming and it's what I'm now coding in...
To switch back to vNext I think you might need to:
use IMessenger instead of IMvxMessenger
use BaseMessage instead of the MvxMessage
use Subscribe instead of SubscribeOnMainThread - but then you will need to marshall the message onto the UI thread yourself.
There exists an easier way. Here is the method I use for executing any action on the main
thread:
protected void RunOnUIThread(Action action) {
var dispatcher = Mvx.Resolve<IMvxMainThreadDispatcher>();
dispatcher.RequestMainThreadAction(action);
}
Hope it helps. Cheers.
Although I think this is a fairly trivial question, I could not find any answers out there.
My question is:
Is there a way to get a notification in a MonoTouch iPhone application when my application is being closed or sent to background (by a user clicking the home button)?
I thought the WillTerminate override was good for this, but in the debugger, it is never called.
There are two ways to get notified when an app goes to the background:
a. Override the appropriate method in your AppDelegate:
public override void DidEnterBackground(UIApplication application)
{
// App entered background, do some light stuff here,
// there is not much time before getting suspended
}
b. Add notification observers through the NSNotificationCenter class:
NSObject observer = NSNotificationCenter.DefaultCenter.AddObserver(
UIApplication.DidEnterBackgroundNotification,
delegate(NSNotification ntf) {
// Same as above
});
You can use the NSObject object returned from the AddObserver method to remove the observer when you no longer need it:
NSNotificationCenter.DefaultCenter.RemoveObserver(observer);
It has been a long question, so here is the summary first,
I have a Client class for my messenger project.
My Client class has a Socket.
I use its BeginReceive method to get messages from server.
In async callback of BeginReceive, I get the message using EndReceive.
When I get the message, I fire the MessageReceived event with message itself.
I use this Client class in my message form.
In message form, I can get the received message by attaching a method to Client.MessageReceived.
I use OnMessageReceived method for this purpose.
When I get the message, I can show it on a TextBox (using Control.Invoke) or MessageBox.
I can also add a new tab to my TabControl in OnMessageReceived.
When I try to initialize a WebBrowser control, I get ThreadStateException.
The control I use to display messages derives from WebBrowser control, so I need it.
Threading.Thread.CurrentThread.ThreadState is "Background".
I don't think you'll need the details but you can find the detailed question I first intented to post below.
Many thanks.
I'm working on a messenger project (Server is a Windows Service and Client is a Windows Forms Application) using Net.Socket's async methods.
I fire Client's MessageReceived event in callback of Socket.BeginReceive;
Everything is how I want them to be until here.
I use MessageReceived event in my form (the one that two people writes to each other) I can do anything I want to the UI using Control.Invoke method (if required) with one annoying exception.
I have tabbed conversations in the form so when a message arrives, I check if there is an open conversation (tab) with the sender. If yes, I select that tab and display the message. If no, I create a new ConversationTab.
Now, I'm sorry if it's being a long question than it should be or if I can't explain myself sufficently. English is not my first language and this is my first question in Stack Overflow.
So, here is the ConversationTab:
public class ConversationTab : TabPage
{
public User Friend { get; private set; }
public MessageBrowser MessageBrowser { get; private set; }
public ConversationTab(User friend) : base(friend.DisplayName)
{
Friend = friend;
MessageBrowser = new MessageBrowser();
Controls.Add(MessageBrowser);
MessageBrowser.Dock = DockStyle.Fill;
}
}
MessageBrowser derives from WebBrowser and the reason I use this is because I could not apply custom styles (color, font, size) 'per message' using RichTextBox. RichTextBox.SelectedColor doesn't always work or I couldn't make it work as intended. MessageBrowser let's me use CSS instead. Wandering off of the subject? Sorry.
Here is the NewConversation method I call when MessageReceived event fires:
public void NewConversation(User friend)
{
ConversationTab tab = Conversations.FirstOrDefault(c => c.Friend.Id == friend.Id);
if (tab != null)
ActiveConversation = tab;
else
{
tab = new ConversationTab(friend);
// add tab to TabControl
}
// bla
}
"Conversations" gets the tab pages of the TabControl and "ActiveConversation" gets or sets the SelectedTab property of the TabControl.
My point in creating these properties are mostly thread-safety logic inside.
So the question: It's throwing ThreadStateException in "tab = new ConversationTab(friend)" part of the above code. It is the "MessageBrowser = new MessageBrowser()" part of the first code and the constructor of MessageBrowser. The reason of why I didn't provide MessageBrowser's constructor code is because the exception is thrown before any line of inner code gets executed (It is about WebBrowser's constructor, I get this exception when I try to initalize a WebBrowser, too.)
Actually I don't even get an exception, the application just closes there without notifying me about anything. I saw the exception when I try to call "MessageBrowser = new MessageBrowser()" on ConversationTab's constructor in Watch window.
I'm kind of new to using threads and asynchronous methods.
MSDN says:
ThreadStateException is thrown by methods that cannot perform the requested operation due to the current state of a thread.
In my case, the thread's state is "Background".
I have no clue about what am I doing wrong.
Thank you very much if you read the whole thing and thank you much more if you can help.
This seems to be related to using COM (web browser control uses COM) in .NET where thread apartment needs to be set to STA.
Try adding [STAThread] to your entry point.
Have a look at this.
I am trying to get my IEventAggregator to allow me to publish and event in one module and catch it in another. I have tried my code below in a single module/project and it works great. It only fails when I have one module/project publish the event and another subscribe to it.
I have having my IEventAggregator injected into both modules via unity.
I have 3 projects, two of them have modules (call them A and B) and one is just a plain class library (call it Interfaces)
In class library Interfaces there is this code:
public class RandomTestEvent : CompositePresentationEvent<string>
{
}
In module A there is this code in a button click command (this is really in a View Model in the project):
var evt2 = _eventAggregator.GetEvent<RandomTestEvent>();
evt2.Publish("Testing");
In module B there is this code:
public void Initialize()
{
var evt2 = _eventAggregator.GetEvent<RandomTestEvent>();
evt2.Subscribe(OnRandomThingDone);
}
private void OnRandomThingDone(string obj)
{
MessageBox.Show("Random Event Done With: " + obj);
}
I can trace through and I see Subscribe get called. When I look at Publish geting called the debugger says Subscriptions = 1 (so it knows that the subscription was made, so I don't seem to have 2 different instances of IEventAggregator.)
But OnRandomThingDone never gets called after Publish.
Any Ideas why? (Do I need to post more code? If so let me know.)
Really random guess - your subscriber is getting GC'd before the event is published - since the default behavior of Prism's CompositePresentationEvent is to use WeakReferences for preserving subscriber target references.
So...try calling the Subscribe overload which allows you to specify keepSubscriberReferenceAlive and pass in true.
If your subscriber then receives the event successfully, it means that your class which contains OnRandomThingDone is going out of scope and getting GC'd before the event is published.
Random API reference:
http://msdn.microsoft.com/en-us/library/ff921122(PandP.20).aspx
Actually grimcoder is correct, a weak reference requires a public Action method.
Utilizing a week reference relieves the coder of unsubscribing to the event, this is managed by the GC.
You can however use a strong reference by passing true to keepSubscriberReferenceAlive, which also can speed up your program if a large number of events is called in a short period of time.
For more information on this see: Chapter 9: Communicating Between Loosely Coupled Components Section Subscribing Using Strong References
It has nothing to do with GC since once Subsriber is attached reference to it never dies.
The real problem is due to inaccessibility of the OnRandomThingDone
if MUST be public i.e:
**public** void OnRandomThingDone(string obj)
{
MessageBox.Show("Random Event Done With: " + obj);
}