Winforms throws ArgumentOutOfRangeException when setting TabPage.Text - winforms

I have code like this
someTabPage.Text = "hello";
where someTabPage is a non-null instance of System.Windows.Forms.TabPage created by the Winforms designer.
Occasionally (no one can reproduce this yet) this exception is thrown
[System.ArgumentOutOfRangeException] InvalidArgument=Value of '-1' is not valid for 'index'.
Parameter name: index
at System.Windows.Forms.TabControl.SetTabPage(Int32 index, TabPage tabPage, TCITEM_T tcitem)
at System.Windows.Forms.TabControl.UpdateTab(TabPage tabPage)
at System.Windows.Forms.TabPage.UpdateParent()
at System.Windows.Forms.TabPage.set_Text(String value)
at my code which calls the setter
I am looking at the source starting from this point in the call stack but I can't imagine what is wrong. As Ginosaji suggests in the comments, it seems that the parent-child relationship is broken - the TabPage is pointing to its parent TabControl, but the parent is not holding the child in its collection. I would think "race condition", but only the UI thread should be able to touch Winforms controls.
It might be worth noting that this TabControl is nested in another TabControl, but that doesn't give me any ideas.
Does anyone know why this could be happening?
Progress
I caught it in the debugger and confirmed that the parent-child relationship is indeed broken.
? tabPageProblem.Parent
{System.Windows.Forms.TabControl, TabPages.Count: 2, TabPages[0]: TabPage: {Unit Data}}
System.Windows.Forms.TabControl: {System.Windows.Forms.TabControl, TabPages.Count: 2, TabPages[0]: TabPage: {Unit Data}}
? tabPageProblem.Parent.Name
"tabControlParent"
? tabControlParent.TabPages.Contains(tabPageProblem)
False
The tab page still has its Parent set, but the parent does not Contain the tab page.
Unfortunately, I still don't know how it got into this state.

I've been running into this error with .TabPages.Add and finally found a consistent repro and a solution that may help you:
Add in this new line:
someTabPage.PerformLayout();
someTabPage.Text = "hello";

You mentioned the issue happens in a child TabControl... Is the tabpage containing this child TabControl inactive when the Text is set? (Meaning a different tabpage of the parent TabControl is in the foreground.) I believe that while the child TabControl is not visible (being in a "background" tabpage) it has a tendency to behave unexpectedly.

Here is the workaround I used:
If tabControl.TabPages.Contains(tabPage) Then
tabPage.Text = "the text"
ElseIf tabPage.Parent IsNot Nothing Then
logger.Warn("there is still a problem with this tab page, parent is {0}", tabPage.Parent)
End If

Related

MVVM - Ctor fails : Specified element is already the logical child of another element | WPF

I know there are answers to "Specified element is already the logical child of another element" problem but this one is weird.
I have two view models; parent being say ParentVM and child being ChildVM.
In ParentVM, I create and display a user control with DataContext=ChildVM. And in ChildVM's constructor I had a code like this:
if(somecondition)
{
Xceed.Wpf.Toolkit.MessageBox.Show("sth");
}
When this MessageBox.Show() method is executed, the error happens. Then I moved this code from constructor to a command and the problem disappeared.
What is the cause of this? Although the problem seems disappeared I want to know why it is solved when the code was moved out of constructor (Initialization of visual components?)
Thanks.

WPF Modal dialog goes in background

I have problem with an application where modal WPF dialog occasionally goes behind the main application window. This hapens when I click button on the dialog which does some processing and updates controls (through binding) in the main application window. When it goes in background - clicking anywhere in the application brings it back into foreground.
var dialog = LoadDialogWindowThroughMEF();
dialog.Owner = Application.Current != null ? Application.Current.MainWindow : null;
dialog.ShowInTaskbar = false;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
return dialog.ShowDialog();
The above code shows how I open modal window. This happens very rarely.
Does anybody know what could be the problem?
I'm not sure I have an answer for you, but I can share some of my thoughts:
Every time I've encountered this type of problem, it happened because the Owner wasn't set properly. So, I'd try to not set the Owner and see if that makes the problem reproducible. You need to be absolutely sure that Owner is set to the correct parent window at all times1. You might also want to check that it is the actual MainWindow of your application that are supposed to be the parent. I think that most of the time it is beneficial to be explicit2 in your code. In this case that means that it is better to assign the known parent (maybe you have a reference to the parent somewhere that you could use), rather than relying on the Application.Current to provide you with that reference. Doing so will put you in control of the assignment to Owner. It could even make it possible to get rid of the ?: operator since you would have the means to control the reference even during unit testing.
I also want you to make sure that the code that is actually updating the parent window doesn't in any way force focus to a specific control on the parent window, or anything like that. (As long as the correct parent is set as Owner, I don't see this as a likely problem.)
I hope this helps you, but I understand if it doesn't. The fact that your dialog reappears when you click the parent window disproves some (or all!) of my points...
1 Except when running your unit tests, but that's a completely different matter.
2 As in the first meaning of the word according to wiktionary.org/wiki/explicit, and as opposed to implicit.

The whole wpf application is blocked after i call Show() for new window

I am developing a WPF-application using mvvm pattern. And a strange problem occurred to me.
There is a form, which contains a devexpress DXGrid control. There is a command binded to double click gesture in presenter. When the command triggers a new window is created and shown through factory class(the Show() method is used).
So, it happens from time to time that the whole application(all application windows) is blocked when this window is shown. This lockup disappears after i focus any other application.
For the first time this problem occurred after updating devexpress version. Then this problem occurred any time new window was shown after double click on grid row. The problem was partially fixed by setting new window`s Owner property.
Now this problem occurs from time to time. It seems as if threads are involved here, but i dont understand how. =(
p.s.:
there is one more strange thing, when new window is shown and no lockup-problem occurred, the first window is still focused and i have to click on newly shown window before i can use any controls, placed on it.
I have tried:
set ShowActivated property
call Activate() after Show()
newform.Dispatcher.CheckAccess() to
determine which thread calls Show()
method
check newform.IsActive property after
show (value = true)
Could you tell me how to fix, please?
Thank you.
Well to fix the issue of first window being focused rather than the newly shown window, you need to do the following, after calling the show method for the new window:
Mouse.Capture(null);
Hopefully the issue would be resolved.

WPF: Determine if a Panel is visible to the user

I have a WPF usercontrol (myGraphicControl) in a tab (WPF application).
When the form size changes, I redraw the graph in myGraphicControl.
Since the redrawing operation is a I need to do it only the control in in the visible tab.
How the WPF (user)control can detect if it's "visible" actually or not?
PS.
by Visible I mean that user can see it.
say, if a Visible TextBox is located in the currently invisible tab, this textBox is not visible by the user.
I don't believe there is a quick-fix solution here, but you may be able to do something using UIElement.InputHitTest(Point).
You could make a call similar to
//get the coordinates of the top left corner of the child control within
//the parent
var childTopLeft = childControl.TranslatePoint(new Point(), parentControl);
//check whether or not the child control is returned when you request the element
//at that coordinate through hit testing
var isVisible = (parentControl.InputHitTest(childTopLeft) == childControl);
However, I should point out that I haven't tried this myself, and that it probably won't work in the following scenarios:
Transparent items - generally, transparent backgrounds cause hit testing of a control to pass to the parent
Partially occluded items - you can only hit-test one point at a time, so if only part of your child control is visible you will have to check the correct point
I've found that while Steve's method generally works, it works much more reliably if you get a point from somewhere in the middle of the child control. I'm guessing that maybe layout rounding somewhere along the way makes the InputHitTest check somewhat inexact. So, change his first line to the following and you're golden:
var childTopLeft = childControl.TranslatePoint(new Point(childControl.RenderSize.Width/2, childControl.RenderSize.Height/2), parentControl);
Maybe UIElement.IsVisible will be helpful? It works for tab contents well.
Anyway you can use a solution described here.
I have one more solution. The current implementation of TabControl removes inactive tabs from visual tree. So, another way to determine whether your element is visible is to find PresentationSource. It will be null for elements of inactive tabs.

WPF parent-child window: binding reference problem

I have a WPF Window that open a modal child window to load some data. Both window have a own viewmodel, now I have this problem: after I close the child window it seems still running in background!
To close the child window I set DialogResult from viewmodel command; now, if I create a new data and then I edit it from parent window (with the child window closed before), the child window still capture the property changed event for the properties previously bind.
How can avoid this?
I would clear every reference with data when I close modal window. Which is the best practise to do it?
Ensure that you don't keep any references to your window, even an indirect one. One of the most common cause of leaks are events. If a window B is adding an event handler to an event of window A, B won't be released until A is also.
For example, if you're directly listening to property changes, you should use the Weak Event Pattern and replace all your += with a call to PropertyChangedEventManager.AddListener. In general, every strong handler you add to an event should be removed to avoid leaking.
More information about leaks in .NET in this MSDN article.
You can use a memory profiler like Scitech's mem profiler or Jetbrains dotTrace to see what ojects are keeping your windows in memory.
Edit: In response to your comments, your case is really simpler than I first thought: the Garbage Collector simply didn't collect the window yet. Adding GC.Collect on Test_Click for testing purposes solves the issue.
Here, remove the SelectionChanged event from the ComboBox when the form is closing so you can let the GC do its job and reclaim the form later without having problems. If you really need the whole form to get released right now, you might consider calling GC.Collect although you should avoid it when you can.
Edit 2: In response to your third comment, it should only matters for objects that are shared between views, and where the changes in the view will change something back in a shared object. In your test project the SelectionChanged does nothing on the original list so it doesn't really matter if the event is raised or not. The form will get collected eventually.

Resources