What is the proper way to manage the focus when you write your own control which has controls inside its control template?
Let's say we have a CustomControl with two TextBoxes in its ControlTemplate. As user and developer I would expect following behaviour:
When Calling customControl.Focus(), then the first TextBox in the template should receive focus.
Moving focus forward and backward using Tab / Shift+Tab is expected to work which means:
2.1 If the user presses Tab having focus in a control before the customControl, then focus should move to the first textbox of customControl.
2.2 If the user presses Shift+Tab having focus in the first textbox of custom control, then focus should move to any control before the customControl.
Following I tried already:
a. Setting the CustomControl to Focusable=false enables condition 2. but disables condition 1.
b. On StackOverflow the question how to move focus to a control inside the control template appears many times. Always proposing following solution:
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="PART_TextBox1" Property="FocusManager.FocusedElement" Value="{Binding ElementName=PART_TextBox1}" />
</Trigger>
This solution enables condition 1 and 2.1 but destroys condition 2.2.
Thanks for your help :)
You could go with the second option (the trigger on IsFocused property) and keep condition 2.2 satisfied by simply setting the IsTabStop property of your CustomControl to False. The big flaw in this solution though is that it can be easily undone by setting the IsTabStop property back to True.
The other way to achieve Your goal would be to drop the trigger and deal with focus changes in the control's code-behind like this:
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
if (e.NewFocus == this)
{
if (object.ReferenceEquals(e.OldFocus, Template.FindName("PART_TextBox1", this)))
MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
else
MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
I'd also suggest setting properties IsTabStop={TemplateBinding IsTabStop} and TabIndex={TemplateBinding TabIndex} on any focusable control in Your template, so that when any of these properties is set on your control, it still maintains the inner navigation order and prevents/allows navigating with TAB key to each of the inner controls.
Related
I have a ComboBox which uses ListBox to show items. ListBox’s ItemSource is bound with a CollectionViewSource.
Issue: Once I open ComboBox and scroll through the items and leave it in middle or at bottom. Once I reopen ComboBox, even though I refresh or reload ItemSource (CollectionViewSource), the Scrollbar remains at the same place where I left it last time. I want it to be as default (at top) each and every time I reload ItemSource.
Is there is any way of doing this in XAML itself? Another thing, I cannot use Behavior or Attach property. I want any template or style for this.
In order for a Style on a ListBox to embody some behavior for the <ScrollViewer>, you would need to use an Attached Property / Attached Behavior to control the ScrollViewer's "grabber" position. This is because your collection is bound to your ListBox and notifying when it is updated needs to drive a behavior that isn't natively on the ListBox. It may be possible to reset the scroll position with a <ControlTemplate> for the <ScrollViewer> itself, but I imagine it would be difficult as it would likely involve manipulating Transforms / StoryBoards based on DataTriggering with your ItemsSource, but again that may cause a dependency on needing to use an Attached property, which for some reason you can't use...
If you simply want to get a result now, and you don't care about testability or re-usability, I would handle the TargetUpdated event in the code-behind. It's ultimately what the Attached Behavior would end up doing. On the other hand, if you do care about re-usability then you need to evaluate and challenge why you can't use an Attached Behavior (they are also testable, too); an Attached Behavior would be also easier than trying to edit a ControlTemplate.
Here is the code-behind approach:
.xaml:
<ListBox x:Name="myListBox"
ItemsSource="{Binding MyItemsSource, NotifyOnTargetUpdated=True}"
TargetUpdated="ListBox_TargetUpdated"/>
.xaml.cs:
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
if (myListBox.Items.Count > 0)
myListBox.ScrollIntoView(myListBox.Items[0]);
}
Edit: On the flip-side, if you are using MVVM, you can do something like this SO post suggests and set IsSynchronizationWithCurrentItem="True" and when you refresh your ItemsSource, simply set your SelectedItem to the first in the list and handle the SelectionChanged event in your vm.
I`m developing a wizard application which has a side menu with 5 borders and a content control that contain application screens (user controls).
The borders styles are suppose to give to user an indication where he is at the wizard steps.
I wrote 2 border styles - the first one is the defult style which applied on all borders by default.
The second one (isFoucusedStyle) need to be applied by the border that suitable to the current screen.
For example when the wizard is showing the first screen: first border need to use the isFoucusedStyle and the others need to use defult style. When the user continues to next screen, the first border need get back to default style and the second border now will apply isFoucusedStyle.
I create the pages instances via xaml at the mainWindow under resources at the next way:
xmlns:view="clr-namespace:App.View"
xmlns:ViewModel="clr-namespace:App.ViewModel"
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:OpeningViewModel}">
<view:OpeningView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:PersonalDataViewModel}">
<view:PersonalDataView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:BusinessDataViewModel}">
<view:BusinessDataView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:BusinessDataViewModel}">
<view:BusinessDataView/>
</DataTemplate>
I also have a property - CurrentPage which binded to ContentControl - when the user clicks "next page button" CurrentPage updates and the ContentControl switch UserControl.
There is no any binding between the borders to User Controls, in my current state the borders are just visual graphics without any features.
How can i implement it?
Thanks
I took the "isFoucusedStyle" and config it to baseOn defultstyle.
I added triger to isFoucusedStyle which turns on when Border.Focusable is true.
I Created a converter which has access to current page number.
At every border I bounded focusable property to the converter and sent it "converter parameter" with the suitible page number (page number which represented by current border)
The converter is checking for equility between currentPageNumber to converterParameter and returns boolean result.
The result is turning on (or not) the trigger and set the needed border style.
Thanks anyway.
Firstly I would strongly suggest you base your wizard on the NavigationWindow (or containing a NavigationFrame), this will give you all your back and forwards navigation for free, and if you want you can always re-style your NavigationWindow to match a more wizard like interface (see WPF Wizards). NavigationWindow/Frame also supply you with Navigate() methods that handle the transition between pages.
In order to handle the navigation links (your five side menu items) I bind each link to a View level ICommand which tests to see if we need are already on the correct page in CanExecute. Setting ths borders is then just a case of {Binding CanExecute, Converter={BoolToColorConverter}}.
In your case, you can simply do the same thing. Setup your command to check if we have the right CurrentPage, and Bind as above using a Converter.
I have a rather interesting case with ComboBox control - CustomComboBox;
In the style of this ComboBox, Popup contains one custom control that requests a DataContext;
<ctrl:CustomGrid DataContext="{TemplateBinding DataContext}" GridName="{Binding Preferences.CurrentGridName}"/>
The idea:
to use this control several times on one page
to use it in a masterpage container
the masterpage control needs to have different DataContexts regarding the Page it is on
The logic:
In the overriden OnApplyTemplate I am getting the grid and connecting few eventhandlers
The problem:
The masterpage control is triggering OnApplyTemplate only once
The first appearance of the CustomComboBox is as expected.
However, every next apearance is with same DataContext, even when changing the datacontext of the CustomComboBox
These changes don't reach to change my CustomGrid DataContext
I am sure that something on the bindings or the presentation logic is bad...
Please throw some thoughts on, I would appreciate a hint here
Thanks
OnApplyTemplate is called when a ControlTemplate is applied to the control that overrides the method (neither its parent, nor children). If OnApplyTemplate is entered once, the overriding control must also be created once. I mean you simply have a single masterpage instance. This shouldn't be unexpected.
Speaking about Popups and DataContext, there often are issues with bindings from a Popup to outside it. So, I would rather write some code-behind to deliver correct context to Popups, instead of relying on Bindings. There sure is a problem of DataContextChanged event absence prior to SL5. To workaround this one, you should define your custom DependencyProperty on your CustomComboBox, bind it to the CustomComboBox's context and assign its value to the Popup in the PropertyChangedCallback.
Is it possible under any circumstances to get MouseEnter event bubbling?
MSDN says it's an attached event with Direct routing strategy, which technically excludes such possibility. I've a fairly complex control (in essence a hierarchy consisting of grids, stackpanels and content controls). I seem to get MouseEnter event propagated from bottom up, here's the debug dump taken from OnMouseEnter handler (I've the same custom control included at different levels of the hierarchy, which handles MouseEnter, so I have a central place for listening that event):
In: parent:s7b, timestamp:37989609
In: parent:s2, timestamp:37989609
In: parent:Root, timestamp:37989609
s7b, s2 and Root are FrameworkElement names and timestamp is e.Timestamp from MosueEnter event.
Provided that the Routing Strategy is Direct, how does WPF decide on event originator? Does it traverse the visual tree until the first FrameworkElement with attached MouseEnter event is found?
While I'm working on a minimalistic repro set for the problem, could anyone suggest what could cause the behaviour?
And here's the repro:
Create two custom controls, one is a contant control, another is event receiver.
1.1. MyContentControl
Code:
public class MyContentControl : ContentControl
{
static MyContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyContentControl),
new FrameworkPropertyMetadata(typeof(MyContentControl)));
}
protected override void OnMouseEnter(MouseEventArgs e)
{
if (e.Source == e.OriginalSource
&& e.Source is MyContentControl)
{
Debug.Write(string.Format("mouseenter:{0}, timestamp:{1}\n",
(e.Source as MyContentControl).Name,
e.Timestamp));
}
base.OnMouseEnter(e);
}
}
XAML:
<Style TargetType="{x:Type local:MyContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyContentControl}">
<StackPanel Orientation="Horizontal">
<local:MouseEventReceiver />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
1.2 MouseEventReceiver
Code:
public class MouseEventReceiver : Control
{
static MouseEventReceiver()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MouseEventReceiver),
new FrameworkPropertyMetadata(typeof(MouseEventReceiver)));
}
}
XAML:
<Style TargetType="{x:Type local:MouseEventReceiver}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="LightGray" Width="20" Height="20" Margin="5"></Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Finally the markup of my test harness:
XAML:
<Window x:Class="MouseTricks.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MouseTricks"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:MyContentControl x:Name="c1">
<local:MyContentControl x:Name="c2">
<local:MyContentControl x:Name="c3" />
</local:MyContentControl>
</local:MyContentControl>
</Grid>
</Window>
In order to reproduce the problem just hover over the right most gray square and watch the Debug Output window, you'll see three entries in there, while I'm expecting just one.
Cheers.
Perhaps a more detailed description will help. In the MSDN article on Mouse.MouseEnter the following quote is made:
Although this event is used to track when the mouse enters an element, it is also reporting the IsMouseOver property has changed from false to true on this element
MSDN says that Mouse.MouseEnter fires when IsMouseOver changes from false to true. Looking at the MSDN article for IsMouseOver the following quote is made:
Gets a value that indicates whether the mouse pointer is located over this element (including visual children elements that are inside its bounds)
As we both agree, the null background does not support interaction. There are a lot of caveats to the null background issue with regards to IsMouseOver, but it is obvious from practical application that this value does not get switched for a null background. However, the definition does say that if the mouse is "located over" any visual child within the bounds of the element then IsMouseOver will change barring several strange caveats. However, the null background is not one of these caveats.
A quick look at the visual tree of your control using the snoop utility or VisualTreeHelper shows that all three gray grids are the visual children of c1, the two rightmost grids are visual children of c2, and the rightmost grid is a visual child of c3. This is as would be expected since all of your content controls are nested within each other.
By monitoring the IsMouseOver for c1 property you can easily see that when the mouse touches a gray square the property value changes to true. You can verify this by adding a callback to the main window's mouse move event. I used the following callback:
private void MouseMove_Callback(Object sender, MouseEventArgs e)
{
if (c1.IsMouseOver)
MessageBox.Show("Mouse is Over c1!");
}
You will notice that no matter which of the three gray squares that you are over the IsMouseOver for c1 is set to true. This indicates that IsMouseOver changes to true for c1 when it is over any of the three squares and so the claims that MSDN makes are true. MouseEnter should and does get fired for c1 no matter which gray square you touch since all three gray squares are in c1s visual tree and are not eliminated from mouse hit testing by a caveat (such as the null background caveat).
The MouseEnter event is responding as a direct event in your application just as MSDN claims.
Since this is a complex control it seems likely that when you are entering the Root element with the mouse you are also entering s7b and s2 at the same time. Since all three elements are registered for the MouseEnter event they should all respond at exactly the same time if it is possible for the mouse to enter all three elements simultaneously.
It probably appears that the event is bubbling up the visual tree because you happen to have MouseEnter registered for a line of visual parents of similar size. If I define a Button in a StackPanel with the button stretching to fill the StackPanel and register both for the MouseEnter event then whenever the mouse enters the Button it will by default also be entering the parent (the StackPanel). In this case it may look like the event is bubbling up the visual tree when in fact it is just a direct event for two separate elements that is occurring simultaneously.
If you are creating a complex control then usually you would want one MouseEnter callback for the entire control or specific MouseEnter callbacks for specific pieces of the control. Are you sure that you need callbacks for the entire control as well as individual parts of the control?
-Edit
Just saw your new post. I tried your code and I noticed that the content MyContentControl instances are all nested. Since the MyContentControl class derives from content control the controls are being stretched to fit the available space. You can see this by adding a border property to the MyContentControl class. Since the background of MyContentControl is null by default MouseEnter only gets fired when one of the gray boxes is touched.
The first MyContentControl creates a horizontal Stackpanel and adds the gray box and then a content presenter. Anything to the right of the grid with the first gray box will automatically be in c2 and/or c3 because the content presenter from c1 will be stretched to fit the size of the window which has a fixed height and width. This is why when you hover over c2 you get the MouseEnter for c1 and c2 because when the gray box is touched the mouse has entered the content presenter of c1 and the mouse has also entered the gray box of c2. Similar logic can be used to understand the case for c3.
Mouse transparent controls (MTC) (I'd tend to call them layout controls) having mouse opaque children (MOC) can't handle mouse events correctly.
I could be wrong, but it looks like a bug to me. I can guess that the culprit is the fact that MTCs are incapable of handling mouse input but pretend to do so rather inconsistantly.
Due to the virtue of attached events, MTCs become Source & OriginalSource of mouse events, also their IsMouseOver gets set to true, which doesn't get on well with other parts of the system.
The workaround is - do subscribe only mouse opaque parts of your controls to mouse events. Sounds horrible at first glance, but at you shouldn't lose much of flexibility provided you use commands.
Any suggestions are highly appreciated.
I have a few usercontrols loaded into a tabcontrol via MVVM in WPF.
Within the XAML for the usercontrol I am setting focus to a textbox using the FocusManager, however this appears to only work when the first instance of the usercontrol is created.
Just to test I added a loaded event handler to the usercontrol - this is only called on the first instance.
I'm using data templates for the user controls as follows:
<DataTemplate DataType="{x:Type local:UserTypeViewModel}">
<local:UserTypeView />
</DataTemplate>
The textbox is focused as follows:
FocusManager.FocusedElement="{Binding ElementName=txtName}"
Additionally I'm using a global event handler (for the textbox GotFocus event) which selects all the text using a dispatcher.
If anyone has any tips on how to achieve focus with every usercontrol I'd be very grateful.
Thanks, Ben.
Remember that a visual element can only receive focus, if:
It is visible (in a TabControl only one tabitem can be visible at a time
IsFocusable must be set to true (is default false for UserControls)
It has finished loading (as you write - do it in the Loaded event))
I think the first reason is why it only works for the first element.
As for how to achieve it for all controls - you can use a style with an EventSetter for the Loaded event. You need to make a style per type of control though to avoid having to set it for each control.