I want to add a simple (at least I thought it was) behaviour to my WPF TextBox.
When the user presses Escape I want the TextBox he is editing to have the text it had when the user started editing, AND I want to remove the focus from the TextBox.
I don't have any problem setting the text for the value it had in the beginning of the edit.
The problem is to remove the focus of the element. I don't want to move the focus to any other component, I just want the TextBox to lose focus. Will I have to have an invisible element to set the focus so my TextBox can lose focus?
in .NET Framework 4 just Keyboard.ClearFocus();
The code I have been using :
// Move to a parent that can take focus
FrameworkElement parent = (FrameworkElement)textBox.Parent;
while (parent != null && parent is IInputElement && !((IInputElement)parent).Focusable)
{
parent = (FrameworkElement)parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(textBox);
FocusManager.SetFocusedElement(scope, parent as IInputElement);
Since none of the above answers worked for me and the accepted answer does work only for a keyboard focus, I came to the following approach:
// Kill logical focus
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(textBox), null);
// Kill keyboard focus
Keyboard.ClearFocus();
Kills both, logical as well as the keyboard focus.
A bit late to the party, but it was helpful to me so here it goes.
Since .Net 3.0, FrameworkElement has a MoveFocus function which did the trick for me.
You can set the focus to a focusable ancestor. This code will work even if the textbox is inside a template with no focusable ancestors inside that same template:
DependencyObject ancestor = textbox.Parent;
while (ancestor != null)
{
var element = ancestor as UIElement;
if (element != null && element.Focusable)
{
element.Focus();
break;
}
ancestor = VisualTreeHelper.GetParent(ancestor);
}
AFAIK, it is not possible to completely remove the focus. Something in your Window will always have the focus.
For me, it's quite tricky, especially when using with LostFocus binding.
However, my workaround is to add an empty label and focus on it.
<Label Name="ResetFocusArea" Focusable="True" FocusVisualStyle="{x:Null}" />
...
OnKeyDown(object sender, RoutedEventArgs e)
{
//if is Esc
ResetFocusArea.Focus();
}
Using LPL's answer worked for me, but it would also make me unable to select any options in dropdown menues. To combat this, I added a check to see if the focused element was a textbox.
Doing the same check for when pressing enter, my final code looked like this:
public Menu()
{
InitializeComponent();
this.PreviewMouseDown += PreviewMouseDownEventHandler;
this.KeyDown += WindowKeyDownHandler;
}
void ClearFocus()
{
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
if (elementWithFocus is System.Windows.Controls.TextBox tb)
{
if (Keyboard.FocusedElement != null)
{
Keyboard.FocusedElement.RaiseEvent(new RoutedEventArgs(UIElement.LostFocusEvent));
Keyboard.ClearFocus();
}
}
}
private void PreviewMouseDownEventHandler(object sender, MouseButtonEventArgs e)
{
ClearFocus();
}
private void WindowKeyDownHandler(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
ClearFocus();
}
}
With this, I didn't need to add a focuslost to every textbox, and it can easily extend to other elements without breaking compatability with other parts of the program.
In Windows Phone Development, I just did Focus() or this.Focus() in the PhoneApplicationPage and it worked like a charm.
My answer does not adress the above question directly, however, I feel that the wording of it has caused it to become "The Question" about programmatically getting rid of focus. A common scenario where this is needed is for the user to be able to clear focus upon left-clicking the background of a root control, like window.
So, to achieve this, you can create an Attached Behavior that will switch focus to a dynamically created control (in my case, an empty label). It is preferrable to use this behavior on the highest-level elements like windows, as it iterates through it's children to find a panel it can add a dummy label to.
public class LoseFocusOnLeftClick : Behavior<FrameworkElement>
{
private readonly MouseBinding _leftClick;
private readonly Label _emptyControl = new Label() { Focusable = true, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top };
public LoseFocusOnLeftClick()
{
_leftClick = new MouseBinding(new RelayCommand(LoseFocus), new MouseGesture(MouseAction.LeftClick));
}
protected override void OnAttached()
{
AssociatedObject.InputBindings.Add(_leftClick);
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{
AssociatedObject.InputBindings.Remove(_leftClick);
AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
AssociatedObject.Loaded -= AssociatedObject_Loaded;
AttachEmptyControl();
}
private void AttachEmptyControl()
{
DependencyObject currentElement = AssociatedObject;
while (!(currentElement is Panel))
{
currentElement = VisualTreeHelper.GetChild(currentElement, 0);
}
((Panel)currentElement).Children.Add(_emptyControl);
}
private void LoseFocus()
{
_emptyControl.Focus();
}
}
If you want to remove focus from a certain TextBox, just add this line..
textBox.Focusable = false;
Related
I am trying to implement very simple text formatting functionality for a RichTextBox in WPF. This just consists of a few bold, italic, etc ToggleButtons just above the RichTextBox. See image below, but ignore the top TextBox - the RichTextBox is the bigger one at the bottom.
Toggling formatting for either a selection or at the caret position (for text that will be typed in) is not a problem, as I'm doing this:
private void BoldButton_Checked(object sender, RoutedEventArgs e)
{
this.SetSelectionBold(true);
}
private void BoldButton_Unchecked(object sender, RoutedEventArgs e)
{
this.SetSelectionBold(false);
}
private void SetSelectionBold(bool isBold)
{
var selection = this.RichText.Selection;
if (selection != null)
{
selection.ApplyPropertyValue(TextElement.FontWeightProperty, isBold ? FontWeights.Bold : FontWeights.Normal);
}
}
However, if the user moves the caret somewhere else (e.g. from bold text to normal text) then I'd like the ToggleButtons to reflect that state, in much the same way as it works in Word. Is it possible to detect when the caret position changes, and take action accordingly?
Hook yourself into SelectionChanged event and get current caret position, and test if the property exists on that selection?
In the event, probably you want something like:
var selection = richTextBox.Selection;
if(selection != null)
{
if(selection.GetPropertyValue(TextElement.FontWeightProperty) == FontWeights.Bold)
// todo; enable your button
}
If that event is not triggered by caret positioning(the document doesn't say anything about that),
you probably need to inherit from RichTextBox and override OnSelectionChanged, after that you need to actually generate your own Caret, eg:
var currentCaretPlusOne = new TextRange(richTextBox.CaretPosition,
richTextBox.CaretPosition+1);
if(currentCaretPlusOne != null)
{
if(currentCaretPlusOne.GetPropertyValue(TextElement.FontWeightProperty)
== FontWeights.Bold)
// todo; enable your button
}
I need to add some decoration to the contents of a WPF TextBox control. That works fine basically, I can get the position of specified character indices and layout my other elements accordingly. But it all breaks when the TextBox is scrolled. My layout positions don't match with the displayed text anymore because it has moved elsewhere.
Now I'm pretty surprised that the TextBox class doesn't provide any information about its scrolling state, nor any events when the scrolling has changed. What can I do now?
I used Snoop to find out whether there is some scrolling sub-element that I could ask, but the ScrollContentPresenter also doesn't have any scrolling information available. I'd really like to put my decoration elements right into the scrolled area so that the scrolling can affect them, too, but there can only be a single content control and that's one of the TextBox internals already.
I'm not sure how to capture an event when the textbox has been scrolled (probably use narohi's answer for that), but there is a simple way to see what the current scroll position is:
// Gets or sets the vertical scroll position.
textBox.VerticalOffset
(From http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.textboxbase.verticaloffset(v=vs.100).aspx)
I'm using it to see if the textbox is scrolled to the end, like this:
public static bool IsScrolledToEnd(this TextBox textBox)
{
return textBox.VerticalOffset + textBox.ViewportHeight == textBox.ExtentHeight;
}
You can get the ScrollViewer with this method by passing in your textbox as the argument and the type ScrollView. Then you may subscribe to the ScrollChanged event.
public static T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
{
if (obj == null) return default(T);
int numberChildren = VisualTreeHelper.GetChildrenCount(obj);
if (numberChildren == 0) return default(T);
for (int i = 0; i < numberChildren; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
return (T)(object)child;
}
}
for (int i = 0; i < numberChildren; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
var potentialMatch = FindDescendant<T>(child);
if (potentialMatch != default(T))
{
return potentialMatch;
}
}
return default(T);
}
Example:
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ScrollViewer s = FindDescendant<ScrollViewer>(txtYourTextBox);
s.ScrollChanged += new ScrollChangedEventHandler(s_ScrollChanged);
}
void s_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// check event args for information needed
}
I am trying to set the tab index of two UIElements within a user control. The user control contains a text box and button. I have focus currently being applied to the textbox via an attached property however I would like to have the ability to press the tab key and navigate from the textblock to the button or detect the key press (Enter key) and trigger the command on the button(I know separate question)
The main focus is accomplishing the tab index first.
Thanks for any pointers or suggestions.
UPDATE
I've since tried to employ an attached property to handle the tabbing order
public static DependencyProperty TabIndexProperty = DependencyProperty.RegisterAttached("TabIndex", typeof(int), typeof(AttachedProperties), null);
public static void SetTabIndex(UIElement element, int value)
{
Control c = element as Control;
if (c != null)
{
RoutedEventHandler loadedEventHandler = null;
loadedEventHandler = new RoutedEventHandler(delegate
{
HtmlPage.Plugin.Focus();
c.Loaded -= loadedEventHandler;
c.Focus();
});
c.Loaded += loadedEventHandler;
}
}
However when this I attempt to compile I receive errors that the TabIndex property does not exist for the button control. Any ideas why this is failing?
This is a view specific concern and, as such, even in MVVM should be handled at the ViewLevel. MVVM doesn't stipulate that you remove all code from code behind. It simply means you should have a view specific concern when you do put code there. This is one of those cases, imo.
It is late in the day... I resolved this using an attached property. in the above solution I had copied an earlier DP that I created and did not change the code before I tested.
Below is the working solution
I created a attached properties class and then added the following code:
#region Search Field Focus
public static DependencyProperty InitialFocusProperty = DependencyProperty.RegisterAttached("InitialFocus", typeof(bool), typeof(AttachedProperties), null);
public static void SetInitialFocus(UIElement element, bool value)
{
Control c = element as Control;
if (c != null && value)
{
RoutedEventHandler loadedEventHandler = null;
//set focus on control
loadedEventHandler = new RoutedEventHandler(delegate
{
HtmlPage.Plugin.Focus();
c.Loaded -= loadedEventHandler;
c.Focus();
});
c.Loaded += loadedEventHandler;
}
}
public static bool GetInitialFocus(UIElement element)
{
return false;
}
#endregion
#region Tabbing Order of Elements
public static DependencyProperty TabIndexProperty = DependencyProperty.RegisterAttached("TabIndex", typeof(int), typeof(AttachedProperties), null);
public static void SetTabIndex(UIElement element, int value)
{
element.SetValue(TabIndexProperty, value);
}
public static int GetTabIndex(UIElement element)
{
return (int)element.GetValue(TabIndexProperty);
}
#endregion
The first DP sets the focus of a textblock so that when the user control is loaded you see the cursor placed within the text field.
DP 2 sets the tabbing order. Since the focus is already applied to the current control tabbing falls into place normally. If you did not have focus on the control you would need to set this first.
then finally within the xaml declare your class in the xmlns and add away to the controls.
I have a Silverlight 4.0 datagrid, which has the SelectionMode set to Single. The problem with this is that users need to CTRL+Click on an already-selected row in order to deselect it (and have nothing selected in the grid). I'd like for them to be able to simply left-click on the already-selected row to have it deselected.
I tried doing this with a SelectionChanged event (inspecting the added items in the event arguments), however it didn't work because the event isn't thrown when the user clicks on the same row twice.
Any advice?
There is no way to capture the second event because it is never fired. What you could do is apply the type of customization used in this project to one that does capture the second click and fire the event a second time should you wish:
http://www.codeproject.com/KB/silverlight/doubleClickDataGridSL.aspx
I have the same task, so here is my solution:
attach handler for datagrid's MouseLeftButtonDown event using AddHandler dataGrid.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(DataGrid_MouseLeftButtonDown), true);
, save SelectedIndex in private variable
private int prevSelectedIndex;
void DataGrid_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (prevSelectedIndex != -1 && prevSelectedIndex == dataGrid.SelectedIndex)
{
dataGrid.SelectedIndex = -1;
}
prevSelectedIndex = dataGrid.SelectedIndex;
}
if you want reuse this logic you can create Behavior for DataGrid type
Add System.Windows.Interactivity assembly reference, add class DataGridSecondClickUnselectBehavior
public class DataGridSecondClickUnselectBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown), true);
}
private int prevSelectedIndex;
void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (prevSelectedIndex != -1 && prevSelectedIndex == AssociatedObject.SelectedIndex)
{
AssociatedObject.SelectedIndex = -1;
}
prevSelectedIndex = AssociatedObject.SelectedIndex;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown));
}
}
Now after you compile solution in blend you can add this behavior simply Drag'n'drop from Assets->Behaviors to DataGrid control
I need to change the order of the TabItem.
I've tried with Remove / Insert and it doesn't works.
void UserControl_Loaded(object sender, RoutedEventArgs e) {
if(condition) {
TabControl.Items.Remove(TabItem);
TabControl.Items.Insert(0, TabItem);
}
}
InvalidOperationException:
Element already has a logical parent. It must be detached from the old parent before it is attached to a new one.
How to solve this?
Solved using the "for" instead of "foreach".
if(condition) {
var tabItem = Tab.Items[index];
Tab.Items.RemoveAt(index);
Tab.Items.Insert(0, tabItem);
((TabItem)tabItem).IsSelected = true;
}