Out of range error when closing pop-up - silverlight

I have a Silverlight control that appears on top of another (picture a pop-up box). In this pop-up control, I have a scrollview (height = 250) with a stack panel (instance name = spMain, orientation = vertical) inside. Within the contents of the stack panel are several textboxes stacked on top of each other. When I tab from textbox to textbox, the scrollviewer automatically moves toward the bottom (I wrote code in an event handler that all textboxes are linked to that does that).
The problem I'm having is when I attempt to close out the pop-up control, I'm receiving an error stating that the value does not fall within the expected range.
if (sender is TextBox)
{
TextBox tb = (TextBox)sender;
try
{
// Code bombs out here when I attempt to close out the pop-up control
Point pt = tb.TransformToVisual(spMain).Transform(new Point());
if (pt.Y >= scrollViewerHeight - tb.ActualHeight)
{
svMain.UpdateLayout();
svMain.ScrollToVerticalOffset(scrollViewerHeight += pt.Y);
}
}
catch (ArgumentException aex)
{
// Don't want to eat the exception
string errorMessage = aex.Message;
System.Diagnostics.Debug.WriteLine(errorMessage);
}
}
I'm not surprised I'm getting the error, because it appears to make sense, but what I'm looking for is some sort of User Control Unloaded event or prevent the offending code from executing.
Does anyone have any ideas on how to go about this?

I think the problem lies with how you're closing the popup. Are you removing it from the visual tree or just setting its visibility to collapsed?
If (as I think your problem suggests) you're removing it from the visual tree entirely, you may be able to solve the immediate problem by collapsing the visibility of your text boxes first, and then removing the control. That assumes that your code above is being called as a result of a resize (or a potential resize) of the textboxes. Alternately, you could just empty them of content before you remove them also.
As for an Unloaded event, there isn't anything in the framework that'll do that for you. You could write a custom unload method for your control easily enough though, and just use that when you want to remove it.

Related

Windows Forms user controls on localizable forms cause display issues in Designer

I have a simple user control (just an example): it is 40x100, but resizable. It has two buttons, one anchored at the top, one anchored at the bottom.
It put this control on a form and stretch it to 40x400. This works fine.
But as soon as I switch the form to Localizable = True and change the language to translate any strings, the Designer shows the user control
as if it was 40x100 for both the default an the translated language, i.e. the bottom button is not anchored.
Or better: the bottom button is displayed as if it was not anchored. The control occupies the correct amount of space (40x400), though (see selection highlight). And it displays fine during runtime, this is just a Designer issue.
A picture showing the issue.
Did I miss something here? Is this how it is supposed to work?
Im on VS2010 at the moment, tried the old VS2005 but it's the same there.
Thanks...
I could easily repro this problem by anchoring the second button to the bottom. The Anchor property has a few oddish failure modes, layout isn't always recalculated when it should be. You found one such case. I think the underlying issue is that the Size property is a localizable property as well and the designer fails to fire the required events when it starts a new localization set. Something like that, nothing very trivial.
You'll need to punt this problem and not rely on the Anchor property to get the button positioned correctly. That just takes a one-liner in your UserControl code, like:
protected override void OnResize(EventArgs e) {
button2.Top = this.ClientSize.Height - button2.Height;
base.OnResize(e);
}

Databinding falls behind event notification - discussion

Found an interesting problem that I first found in WinForms, and found again in Silverlight, and more than likely WPF as well when it comes to databinding.
I have a tab control with several tabs. As users click across the tabs, each time should be valid before allowing the user to switch from the tab.
For example, user is in a text box which is updated. Binding of text boxes is not flushed until the control loses focus. Loss of focus occurs when the cursor is moved from the control, and focus is given to another control.
In this scenario, the user tabs into a control (let's use text box for this example), and updates the text box. At this point the databinding has not flushed the control, and hence the VM has not yet seen the change. The user then uses their mouse to click the next tab of the control.
At this point things get interesting. I used the PreviewSelectionChanged (Telerik RadTabControl), as I want to check things out before the jump to the next tab occurs, and it also gives me the ability to cancel the event.
However, when I look at the VM, in this event, it still does not have the updated data. I see the VM is clean, and go ahead and allow the jump to the next tab.
As soon as this event is over however, the databindings flush, and the VM gets updated. what now? The events are out of sync! When the mouse was used to click the next tab, the textbox should have lost focus, flushed it's bindings, before the Preview of the Tab click! It's to late to jump back and say oops we didn't catch that in time!
I think I found an interesting work around to this issue - but I'm not 100% sure it will work 100% of the time. I cancel the current event, but then I use the Dispatcher and create a delegate pointing to another method with the same signature as the current event. The Dispatcher will add this message to the message pump, which by this time will now (hopefully?) be behind the messages of the VM updating...
My two questions are:
1) I'm assuming that the textbox control either didn't flush when the mouse left the control, or the process that was fired was too slow and hence the preview message was on the pump before the databinding - either way I see this to be a major issue.
2) Is the workaround a good solution?
Ok, first to answer question 1:
Just because the mouse left the textbox area, doesn't mean that the textbox lost focus. It only loses focus once something else gets focus. For example, if you moved the mouse out of the textbox and click on some other control on your page (it can be anything from a scroll viewer to another textbox, etc.) then your textbox will lose focus.
Now, based on that, the events do not happen in the wrong order. What happens is; your click event on the other tab triggers both the textbox to lose focus (and the data binding to take place) and the move to the next frame, and based on that, you basically get a race condition where the moving to the next tab happens before the databinding takes place.
About question 2:
What you can do is, set the UpdateSourceTrigger to Explicit, you will however be forced to then have some kind of text_changed event and manually update the binding.
You can read more about that here. It might not be the most complete explanation but is a good place to start.
On the other hand, you can associate some events to the textbox and force the textbox to lose focus on those events (e.g. mouse out).
Just an idea: Why not do everything in the VM's PropertyChanged event?
protected override void OnThisViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) {
if(e.PropertyName == "WhateverProperty") {
//Do your magic here for whatever you want to set
}
}
Have your TabItems bound to a collection that will control is being disabled or not.
<sdk:TabControl>
<sdk:TabItem IsEnabled="{Binding SomeProperty, Converter={AmIDisabledOrWhatConverter}}" />
</sdk:TabControl>
That way, everything is triggered whenever a property is chaned in the vm. No more timing issues since everything is on the vm.
Just my two cents.
There's a design defect here, and you're trying to work around the defect instead of fixing it. You shouldn't have to figure out how to cancel the Click event on the tab. The tab shouldn't be processing Click events in the first place.
Generally speaking, if it's not legal for the user to click on a control, the control shouldn't be enabled. The tab should be disabled until the state of the view model is valid.
Your view model should be exposing a command for navigating to the next tab, and the tab should be bound to the command. The command's CanExecute method should only return true when the state of the view model on the current tab is valid.
This doesn't fix your other problem, which is that Silverlight doesn't support UpdateSourceTrigger="PropertyChanged" out of the box. But that's a solved problem (here is one example).
Note that if you implement commands to handle this wizard-like navigation in your application, you can, down the road, change the view to use something other than a tab control (e.g. to use navigation buttons like an actual wizard, or something like Telerik's PanelBar) without having to screw around with event handlers.
Change your bindings to include UpdateSourceTrigger="PropertyChanged".
This will ensure that your data sources are updated on every key stroke, not just LostFocus.
MyOwnTextBox()
{
this.TextChanged += (s, e) => UpdateText();
}
private void UpdateText()
{
BindingExpression be = GetBindingExpression(TextProperty);
if (be != null && be.ParentBinding.Mode == BindingModes.TwoWay)
{
be.UpdateSource();
}
}
I am using this class it updates my binding on the fly, however there is issue with empty string and null values, if your destination property is nullable string then this will update empty string in your destination property. You can get workaround by using some sort of string converter which will use null instead of empty string in case of nullable strings.

WPF TreeView PreviewMouseDown on TreeViewItem

If I handle the PreviewMouseDown event on TreeViewItem, I get events for everything I click on that TreeViewItem include the little +/- box to the left (Windows XP). How can I distinguish that?
I tried the following:
// We've had a single click on a tree view item
// Unfortunately this is the WHOLE tree item, including the +/-
// symbol to the left. The tree doesn't do a selection, so we
// have to filter this out...
MouseDevice device = e.Device as MouseDevice;
// This is bad. The whole point of WPF is for the code
// not to know what the UI has - yet here we are testing for
// it as a workaround. Sigh...
// This is broken - if you click on the +/- on a item, it shouldn't
// go on to be processed by OnTreeSingleClick... ho hum!
if (device.DirectlyOver.GetType() != typeof(Path))
{
OnTreeSingleClick(sender);
}
What I'm after is just single click on the tree view item excluding the extra bits it seems to include.
If my understanding is correct, e.OriginalSource should contain the control you actually clicked on. Make sure it's a TextBlock (or whatever is actually in your TreeView). You could also use something like this post to determine which TreeViewItem the OriginalSource belongs to, if you're paranoid.
My solution is to use the SelectionItemChanged event, seems to avoid issues with the +/- button.

WPF Focus In Tab Control Content When New Tab is Created

I've done a lot of searching on SO and google around this problem, but can't seem to find anything else to try.
I have a MainView (window) that contains a tab control. The tab control binds to an ObservableCollection of ChildViews (user controls). The MainView's ViewModel has a method that allows adding to the collection of ChildViews, which then creates a new tab. When a new tab is created, it becomes the active tab, and this works fine. This method on the MainView is called from another ViewModel (OtherViewModel).
What I am trying to do is set the keyboard focus to the first control on the tab (an AutoCompleteBox from WPFToolkit*) when a new tab is created. I also need to set the focus the same way, but WITHOUT creating a new tab (so set the focus on the currently active tab).
(*Note that there seem to be some focus problems with the AutoCompleteBox--even if it does have focus you need to send a MoveNext() to it to get the cursor in its window. I have worked around this already).
So here's the problem. The focusing works when I don't create a new tab, but it doesn't work when I do create a new tab. Both functions use the same method to set focus, but the create logic first calls the method that creates a new tab and sets it to active. Code that sets the focus (in the ChildView's Codebehind):
IInputElement element1 = Keyboard.Focus(autoCompleteBox);
//plus code to deal with AutoCompleteBox as noted.
In either case, the Keyboard.FocusedElement starts out as the MainView. After a create, calling Keyboard.Focus seems to do nothing (focused element is still the MainView). Calling this without creating a tab correctly sets the keyboard focus to autoCompleteBox.
Any ideas?
Update:
Bender's suggestion half-worked.
So now in both cases, the focused element is correctly the AutoCompleteBox. What I then do is MoveNext(), which sets the focus to a TextBox. I have been assuming that this Textbox is internal to the AutoCompleteBox, as the focus was correctly set on screen when this happened. Now I'm not so sure. This is still the behavior I see when this code gets hit when NOT doing a create. After a create, MoveNext() sets the focus to an element back in my MainView.
The problem must still be along the lines of Bender's answer, where the state of the controls is not the same depending on whether a new tab was created or not. Any other thoughts?
Final Update
As noted, majocha's suggestion worked.
I wanted to update this in case anyone happened upon this same problem with the AutoCompleteBox. It appears that setting focus does not activate it in the UI--you need to do a MoveNext on it to move focus forward once to the control's internal Textbox. This is based on my debugging experience, which may not be 100% scientific. If I have time, I will attempt to create a small repro project and submit it to the WPFToolkit team.
You can try defering the focus change with
Dispatcher.BeginInvoke(MyChangeFocusAction, DispatcherPriority.ContextIdle);
It will get queued after layout and properties updates are done.
I don't think it's best practice, but it works for me.
The control must be visible to be focused, you may try to defer focusing by subscribing to the IsVisibleChanged event, something similar to the following should work:
public static void setFocusLate(this Control control)
{
DependencyPropertyChangedEventHandler handler = null;
handler = delegate
{
control.Focus();
control.IsVisibleChanged -= handler;
};
control.IsVisibleChanged += handler;
}

Showing a tooltip inside a datagrid

I'm trying to show a windows forms tooltip inside a datagrid to highlight an error. The problem I have is that everytime I call tooltip.Show("You have an error", datagrid, 0, 0), The tooltip is confined within the datagrids boundaries and doesn't go outside, which ultimately means the tooltip itself covers up the actual row where the error occurs.
I thought about tooltip.Show("You have an error", Form1, ?, ?) but I don't see an easy way to compute the offset of the datagrid on the form. Since all controls are docked, depending on how the user resizes the form, the location will change.
There is a caveat, the datagrid itself is not a Forms.DataGrid, instead it is an Infragistics UltraGrid which may do funny things itself, which are outside of my ability to alter.
It turns out that it's easy enough to get the location for the Show command from the UltraGrid by querying the UIElement associated with it. Here's what I'm doing:
private void ultraGrid1_BeforeCellUpdate(object sender, BeforeCellUpdateEventArgs e)
{
if (!DataFormat.CanEdit(e.Cell.Row.ListObject, e.Cell.Column.PropertyDescriptor))
{
var tip = new System.Windows.Forms.ToolTip();
tip.BackColor = Color.Orange;
tip.Show("unable to edit", this, e.Cell.GetUIElement().Rect.Left, e.Cell.GetUIElement().Rect.Top, 500);
e.Cancel = true;
}
}
Have you looked at these:
HOWTO:Create Advanced ToolTips For The WinGrid
BeforeDisplayDataErrorTooltip Event

Resources