Prism Region Switching and Synchronizing with Container Instance - c#

I am working in WPF with a MainWindow that has a (Prism) region "MainViewRegion". This switches based off the User's desired view and when it does, the MainWindow resizes to snap to the new dimensions of the embedded view.
I have some code to keep the window fully visible on the desktop after the Region switch. Here's the code:
private void WindowModeChange(string uri)
{
IRegion mviewRegion = regionManager.Regions[RegionNames.MainViewRegion];
if (mviewRegion == null) return;
regionManager.RequestNavigate(mviewRegion.Name, new Uri(uri, UriKind.Relative));
//Get the MainWindow instance from the container
var uc = container.Resolve<MainWindow>(InstanceNames.MainWindowView);
//Make sure the entire window is visible onscreen
ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(uc);
}
The issue is that the "uc" variable will always equal the MainWindow parameters from before the region change. The "uc" is always one step behind what I want, so the "snap-to" code is always off.
What am I missing?

Often when you have a piece of code that executes upon some event, and you need to ensure UI has already responded to that event to some degree, your best bet is to use a Dispatcher to defer the execution. Judging by the code you provided, the UI change is triggered by the regionManager.RequestNavigate call. Now I cannot point you to a piece of documentation that states that, but I know from experience that the framework will process this request asynchronously, i.e. the control will be returned to your method (allowing it to proceed) before all the work caused by this request is finished (hence your problem). That is (as far as I can tell) accomplished internally by the framework by using the said Dispatcher.
Depending on to what extent you need the framework to have processed the triggered change, you should use appropriate DispatcherPriority enum value to ensure certain things are done with once your code is executed. Basing on your question, I think the DispatcherPriority.Loaded is a good choice (from documentation the dispatcher will execute such code after the UI is rendered (which is crucial condition for you), but before any user input is processed). I personally tend to maximize the priority up to the point when it stops working (possibly avoiding some unexpected behavior caused by, for instance, user input).
So this modification to your code should be sufficient to accomplish your goal:
var uc = container.Resolve<MainWindow>(InstanceNames.MainWindowView);
uc.Dispatcher.InvokeAsync(() =>
ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(uc),
DispatcherPriority.Loaded);
Now I've noticed that you ultimately settled with Dispatcher.Invoke rather than Dispatcher.InvokeAsync - I guess it's ok in your case since it's the last instruction in your method, but generally the safe way is to use Dispatcher.InvokeAsync (I've personally experienced situations where Dispatcher.Invoke did not cut the mustard).

Thanks to Grx70 in the comments to my question, he gave me the solution. The body of the method now reads:
IRegion mviewRegion = regionManager.Regions[RegionNames.MainViewRegion];
if (mviewRegion == null) return;
mviewRegion.RequestNavigate(new Uri(uri, UriKind.Relative), (x =>
{
var uc = container.Resolve<MainWindow>(InstanceNames.MainWindowView);
uc.Dispatcher.Invoke(() => ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(uc), DispatcherPriority.Loaded);
}));
I am unsure as to why I had to call the Dispatcher in the callback parameter of the RequestNavigate(), but it won't work without it.

Related

Problem with adding with ++ operator, what can I do?

So, I as trying to create an "Open world exploration" game in C# WinForms, And while coding the mining, (which works just fine), I encountered a problem with saving the number of broken blocks to the inventory (a label). Basically, for every block player breaks, it gets added to the inventory as inventoryWrite.Text = $"Grass: {grassHolder}, Rock: {rockHolder}";.
Now, the thing is, sometimes, even though I use the ++ operator, it adds up to 4 to the inventory. I'm citing the code below.
private void Remove(object sender, EventArgs e, PictureBox itm)
{
if (itm.BorderStyle == BorderStyle.FixedSingle)
{
if (itm.Tag.Equals("grass") && items.Contains(itm))
{
grassHolder++;
itm.Tag = "";
}
if (itm.Tag.Equals("rock") && items.Contains(itm))
{
rockHolder++;
itm.Tag = "";
}
if (itm.Tag.Equals("dio") && items.Contains(itm))
{
dioHolder++;
itm.Tag = "";
}
this.Controls.Remove(itm);
items.Remove(itm);
}
}
I update the inventory in a public loop, don't worry about that (interval is 1ms). But I don't think that's the problem, since I tried putting it in the Remove() function, and nothing seemed to change.
I've even double locked the if statement, but nothing! It still adds more than 1. Can anybody tell me how to solve this? Thank you a lot.
EDIT:
As a reply to Ronald's comment, the if statement is called ONLY when the block is selected. ONLY once when the method is called.
There are too many points to cover in a comment and so I've had to enter an answer.
In itself the ++ operator is not the issue and will always behave as it should, but as someone reviewing a small piece of code the following points crop up.
grassHolder, rockHolder, dioHolder appear to have
accessibility beyond this function and so could be altered
elsewhere.
Function void Remove(object sender, EventArgs e, PictureBox itm) appears to be an event handler and yet there is no locking mechanism to ensure that the externally accessible parameters are not changed or used elsewhere whilst the function code is executed. Specifically items which is appears to be a collection of sorts and is used both in logic to determine whether parameters in (1) are incremented, but also has its contents changed within the function.
From comments made it would appear that this logic is run in
response to user interaction, maybe by use of a mouse button or key
event. Is this base event de-bounced to ensure that multiple
triggers aren't handled?
Your statement "saving the number of broken blocks to the inventory (a label)." Implies that you are storing game data within the UI. This should be avoided as it ties game data directly to the UI implementation and therefore makes it difficult to alter the game, but also ties any handling of game data directly to the UI thread.
Recommended actions:
Ensure that the parameters in question are not accessed and altered
elsewhere causing the issue seen.
Utilize a lock(x) statement to ensure that items is not changed
whilst this function is being executed. More information here
De-bounce the mouse button or key click that triggers this function
to ensure that multiple events aren't triggered. This is performed
by placing a minimum time between event triggers. A minimum time
period of say 150ms would be a good starting point. This would equate to a reasonably quick, conscious user action, but be slower than multiple events triggered by partial/poor switch contact. Incidentally this is especially true on touch screen interfaces.
Consider controlling access to global parameters through use of
access functions. For example
int IncrementRockHolder(){ rockHolder++;} Although implementation may appear onerous, they can greatly help with debugging as call stack information is then available showing what code is calling the function and thus making the change.
Implement a game engine class to control access to game data and implement game logic. This
would allow you to unit test game functionality whilst also freeing
it from UI implementation and restrictions.

"An ItemsControl is inconsistent with its items source" thrown when calling MessageBox.Show()

I'm extremely new to WPF and I have the following problem:
I'm trying to develop a practice application to help me have control of my budgets.
I have a class Partida like so:
public class Partida
{
public delegate void PartidaChangedHandler(Partida p);
public event PartidaChangedHandler OnPartidaChanged;
private ObservableCollection<PartidaEntry> content;
public Partida()
{
content = new ObservableCollection<PartidaEntry>();
content.CollectionChanged += PartidaEntriesCollectionChanged;
}
public void PartidaEntriesCollectionChanged(object s, NotifyCollectionChangedEventArgs args)
{
if (OnPartidaChanged != null)
{
OnPartidaChanged(this);
}
}
}
I'm displaying the content collection with a Datagrid, but I need to know when the content collection of this class changed and fire the OnPartidaChanged event.
Outside of the class (in MainWindow) I hook up to the OnPartidaChanged event like so:
p.OnPartidaChanged += (Partida ppp) =>
{
int foo = 5;
MessageBox.Show("A partida has changed!");
};
When I add a new row within the DataGrid, the event fires correctly, however as soon as MessageBox gets executed I get an InvalidOperationException with the following message:
An ItemsControl is inconsistent with its items source.
Any idea how can a fix that problem without losing the ability to listen to the ObservableCollection's CollectionChanged event and firing OnPartidaChanged after that event?
Thanks in advance!
P.S.: Also, I would like to know what on earth MessageBox has to do with ItemControl... why is the exception firing if MessageBox only displays a simple box! :S
The answer in WPF add item to datagrid bound to observablecollection exception explains clearly exactly what is happening: the event handler is being called while the operation that is changing the collection is in progress, and calling MessageBox.Show() gives the dispatcher message-handling loop a chance to start processing messages again. This causes re-entrancy that is incompatible with the way WPF works: the collection-changing operation has not been completely resolved, but the UI is being given a chance to try to run the logic that should not be run until after that operation has been completely resolved.
In other words, just as the exception states, the control is in an inconsistent state, because it was allowed to do some processing that should not have occurred until after it was completely done dealing with the collection changing.
I grant that the advice in the other answer is not terribly compelling. It is reasonable advice as far as it goes, but offers no real alternatives.
In your scenario, without changing anything else about your implementation, one obvious solution is to defer the message box until after the collection-changing operation has been completely resolved. You can do this by using the Dispatcher.InvokeAsync() method to defer execution of the call to MessageBox.Show():
p.OnPartidaChanged += (Partida ppp) =>
{
int foo = 5;
Dispatcher.InvokeAsync(() => MessageBox.Show("A partida has changed!"));
};
Of course, there is the question as to whether showing a message box is really the best way to handle the event. It's not really clear from the limited information in your question why your event handler looks the way it does. If you are sure that showing a message box every time the collection changes is really the right thing to do, the above should resolve your problem.
But you might want to consider alternative means of presenting the information to the user, such as displaying it in a status field in the UI, or even providing some kind of log of events e.g. in a multi-line text box or list-box. These kinds of approaches will generally involve data-binding that fit within the normal flow of event and data handling in WPF, and would work as synchronous code without running into the issue you're seeing here.

No closure for delegates in anonymous function inside foreach loop

UPDATE: turns out that this is a problem with the Mono compiler used by Unity3D. I'm not sure if the current version of Mono (3.10.0) fixes it, but the old version used in the engine (2.0.5) seems to not implement this feature for delegates, or simply work as it should.
At some point in a game I'm developing (in the Unity3D engine), the player unlocks several items at once. For each of them I must present a simple information dialog that the user must click to advance to the next, until all have been read.
I have a simple C# static method to show a dialog (just a colored overlay with some text in Unity, and NOT in any way related to C# UI frameworks):
ConfirmationDialog.Create("Item X Unlocked!", callback);
When the user finally presses the dialog, callback is called.
I wanted to chain all dialogs so that each one is only created when the previous is clicked on, so I tried this:
Action callback = delegate {};
foreach (string item in unlockedItems) {
var cb = callback; // I though this would create a closure for delegates too
callback = (() => ConfirmationDialog.Create(item + " Unlocked!", cb));
}
callback();
This made sense in my head, as the anonymous function would use a different "cb" delegate in each iteration of the cycle. It seems I'm mistaken, though, as this code seems to result in the same dialog being repeatedly called when clicked, as would happen in a recursive function (which I guess is what it became).
I know I can, for example, do int value = i; inside a for loop (where i is the loop iterator) to use the correct value of i inside the anonymous function (I found a lot of material on this). Delegates seem different, but what in particular makes them not work in the same way? Or am I doing something terribly wrong? Could I do this chaining in some similar way? I say "similar" because I can certainly think of doing this in other - more complicated - ways...
Note: please DO correct me if my use of the words "closure", "delegate", etc, is not right :)
Do you have control over the ConfirmationDialog.Create("Item X Unlocked!", callback); API? If so, instead of taking a callback, you should look at refactoring that to use Task. If you had that, you could simply chain a bunch of tasks as continuations and have them execute one after the other.
I know with game development, these things are often quite asynchronous in nature, the way UI gets shown and subsequently reacted to ... so you could use TaskCompletionSource so you have a handle to something that you can signal that your task is done.

ViewModel's constructor is being called again on navigation, and so messenger subscriptions are being subscribed again

I am building a cross-platform mobile application using MvvmCross framework.
Since I would like to share information between ViewModels, I am registering notifications inside the ViewModel's constructor, using the built in MvxMessenger.
Let's assume a message named ShowAdsMsg, and then the ViewModel looks as follows:
public class AdsViewModel : BaseLookersViewModel, IAdsViewModel
{
private MvxSubscriptionToken _showAdsMsgToken;
public AdsViewModel()
{
_showAdsMsgToken = MvxMessenger.Subscribe<ShowAdsMsg>(message => onShowAdsNavigation(), MvxReference.Weak);
MyMessenger.PublishLastMessage();
}
private void onShowAdsNavigation()
{
//Do Stuff
}
}
About the MyMessenger thing:
The actual navigation to the ViewModel is performed from MainViewModel.
Since that at the very moment of the navigation itself the AdsViewModel does not exist yet, messages published from the MainViewModel cannot reach it.
So, my idea was to naively "remember" the message and publish it when the new ViewModel is ready.
So now the navigation call from the MainViewModel looks like that:
private void navigate()
{
MyMessenger.RememberMessage(new ShowAdsMsg(this));
ShowViewModel<AdsViewModel>( );
}
I am now able to navigate to the ViewModel, and all the notifications are successfully caught.
However...
When I press the BACK button on the device and re-navigate to the same ViewModel,
The constructor is being called again, and so the message subscription re-occur.
As a result, when a message arrives the onShowAdsNavigation() handler is being fired twice!
I found this similar post, discussing the question of how to properly dispose a ViewModel,
but it does not contain a direct solution to my problem.
What I need is a solution. It can be either one of the following:
Idea how Not to subscribe to messages on the ViewModel's ctor.
Guidance about how and when to correctly dispose the ViewModel.
Explanation on why the constructor is being called again, and how to avoid that.
A complete different approach to ViewModel information messaging.
Thanks in advance for you help!
Edit:
I found this SO Answer, which basically answers item number 3 in the list above.
Still, I am wondering what approach should I take regarding the messenger issue.
Another Edit:
I verified that the same behavior exists with MvvmCross tutorial N-05-MultiPage. I simply added a ctor to SecondViewModel, and I hit a breakpoint inside it after each BACK+Renavigate.
Explanation on why the constructor is being called again, and how to avoid that.
The ctor is not called twice on the same object - instead what might happen is that a new View and a new ViewModel are created each time.
By default I would expect a new ViewModel to be created on every forwards navigation on every platform.
By default I would **not expect this to happen during a back button on WindowsPhone - it doesn't happen here for my test cases - but it could happen if:
WindowsPhone removes your first Page (and it's ViewModel) from memory - I guess this might happen if your app is tombstoned or if you are using a custom RootFrame - but I don't expect this to happen by default.
you somehow null the ViewModel (DataContext) in your first Page
Without seeing more of your code I can't guess any more about why this might happen.
I'd personally recommend you look deeper at why you are seeing new ViewModels created during Back, but if you just want a quick fix, then you could look at overriding the ViewModelLocator within MvvmCross - see MvvmCross: Does ShowViewModel always construct new instances?
Note that on WindowsStore, I would expect this to happen - WindowsStore doesn't hold Pages from the backstack in memory by default - but you can overriding this by setting NavigationCacheMode = NavigationCacheMode.Enabled; if you need to.

C# Null Reference Exception

I am working on the following program which deals with threads and thread priorities.
I have constructed a windows form containing a rich textbox (DisplayTextBox) and two buttons (StartButton for running app and ExitButton for closing app).
In the form, I am creating several threads and running them one after another. The method used by each thread is found in the Threading class. The method in question is PrintOnScreen().
In this method, I am appending the thread name as well as its priority to str of type StringBuilder. Then I want to display the contents of str in DisplayTextBox (which is in Form.cs).
However, I am getting a "NullReferenceException was unhandled: Object reference not set to an instance of an object" error. The line where the error crops up is the following:
DisplayTextBox.Text = Convert.ToString(str);
Can you please help me to solve this error? Thanks.
Edit
Thanks to all for your help. In order to solve the problem, I copied the PrintOnScreen method to the Form.cs class and discarded Threading.cs.
I later used the code given by Anand and placed it below t2.Join(). Now it works like a charm.
The problem occurs in the constructor of your form. You declare DisplayText again as local member, so that the field of your form isn't initialized. Change your constructor like this:
private void Form1_Load(object sender, EventArgs e)
{
DescTextBox.Visible = false;
DisplayTextBox = new RichTextBox();
DisplayTextBox.Location = new Point(15, 31);
DisplayTextBox.Height = 258;
DisplayTextBox.Width = 303;
panel1.Controls.Add(DisplayTextBox);
}
and it should work like you expect.
Edit: But beware of the problems you will get, when you want to change UI elements from a background worker thread. In those cases you will have to use the invoke pattern.
I tried this in simple console application and it worked fine.
The issue is with DisplayTextBox. If you want to interact with any control, then you have to use UI thread or Dispatcher.
Use the following code in your Threading Class,
instead of
DisplayTextBox.Text = Convert.ToString(str);
use this one
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(
delegate()
{
DisplayTextBox.Text = Convert.ToString(str);
}
));
You cannot communicate with controls from any other thread than main application's thread. You need to use the dispather for this. Have a look here: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx
EDIT
#Fischermaen - heh, didn't notice this but it still won't work because of the line, where he wrote it fails. He still needs to use dispatcher to set the Text property on the control from the non-main threads
EDIT 2
Related threads:
How to update the GUI from another thread in C#?
In WinForms, why can't you update UI controls from other threads?
Um.
Can't see from here, but part of the reason you are having difficulty is the lack of encapsulation and poor lifetime management in your code.
Instantiate str inside the PrintOnScreen method, give it a better name as well, no appranrent need to be a member variable, no need at all to make it public.
Instead of having a void result, have it return the string result
e.g.
SomeTextBox = PrintOnScreen(); // (GetThreadDetails might be a better name...)
As a tip don't mix presentation and logic. Keep you UI controls thoroughly inside whatever owns them.
And don't do
public SomeType SomeName;
make it a property and give it getter and a setter , even if it's the short form
public SomeType SomeName {get; set;}
Any other bit of code could do something really foolish like
Form1.SomeType = (SomeOtherType)someVar; // with appaling consequences.
Nothing should have more than one owner, any other path leads to ruin.
PS str.ToString() would be a better option than Convert.ToString(str);
Well, this is not very clear for me what you are trying to achieve, however the problem is that the base form Threading class inherits from not being loaded when you are trying to access control on it (window handle still not exists) Also this is the reason you see nothing on Form1 itself, because your threads outputs into hidden form, which is not shown.
I think that you are trying to solve thread access issue, raised by SOReader. This is not proper way to do this.

Categories

Resources