Text coloring while searching - wpf

I would like to create a search control so that while I search for some text it will color the text accordingly
private void Search_OnTyping(object sender, System.Windows.Input.KeyEventArgs e)
{
if (ObjectToSearch is FrameworkElement fe)
{
foreach (var control in fe.ChildrenOfType<TextBlock>())
{
if (control.Text.IndexOf(TextBoxSearch.Text, StringComparison.OrdinalIgnoreCase) >= 0 || string.IsNullOrWhiteSpace(TextBoxSearch.Text))
{
control.Background = Brushes.Yellow;
}
}
}
}
I'm checking if it's a FrameworkElement because I want to be able to go through the Grid, StackPanel etc.
But I want to color only the exact text and not the entire TextBlock that contains the text.
I would appreciate help on how to do this and if there is a better way to do it

Related

WPF TextBox and preserving tabs

Is there some elegant way how to allow pasting into WPF TextBox that has AcceptsTab set to false while preserving tabs in the pasted text ?
When AcceptsTab is true, then user can't use tabs to move to next control, which isn't desired by my users. But they want to have tabs that are pasted, which currently are replaced by spaces.
Thank you
I am not sure this qualifies as elegant but it works but might not be as complete as you want (e.g. when right-clicking in the textbox and selecting Paste from the context menu).
See Paste Event in a WPF TextBox
Set the AcceptsTab to true just before Ctrl-V is processed and restore it after:
XAML:
<TextBox AcceptsTab="False"
Height="200"
PreviewKeyDown="TextBox_PreviewKeyDown"
KeyUp="TextBox_KeyUp"/>
C#:
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!(sender is TextBox textbox))
{
return;
}
if (e.Key == Key.V && (e.KeyboardDevice.IsKeyDown(Key.LeftCtrl) || e.KeyboardDevice.IsKeyDown(Key.RightCtrl)))
{
textbox.AcceptsTab = true;
}
}
private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
if (!(sender is TextBox textbox))
{
return;
}
textbox.AcceptsTab = false;
}
This could be turned into a behavior so it would be easier to apply it to more textboxes without writing code behind.
Another approach is by setting AcceptsTab to true and moving the focus when (Shift) Tab is pressed.
The nice side effect is that all the copy/paste scenarios will still function but the user will not be able to type a tab.
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
var textBox = sender as TextBox;
if (textBox != null)
{
var direction =
e.KeyboardDevice.IsKeyDown(Key.LeftShift) || e.KeyboardDevice.IsKeyDown(Key.RightShift)
? FocusNavigationDirection.Previous
: FocusNavigationDirection.Next;
textBox.MoveFocus(new TraversalRequest(direction));
}
}
}

Content of TabItem stays visibile when set to Collapsed/Hidden

I have a TabControl that displays default information for objects that can be selected by the user in a list.
Unfortunately not all of the TabItems apply to all types of objects. That's why I decided to use DataTriggers to hide some of them in such cases. When testing however I noticed that when the a TabItem is already selected while being set to collapsed then only the header vanishes, but the content of the TabItems stays visible.
When searching for a solution I only found this very old thread here:
WPF - TabItem Contents still visible when tabitem.visibility=hidden
I was wondering if there was is nicer solution today. The only thing that I could think of would be a custom TabControl that would look somehow like this:
public class MyTabControl : System.Windows.Controls.TabControl
{
public MyTabControl() : base()
{
var view = CollectionViewSource.GetDefaultView(this.Items);
view.CollectionChanged += TabControl_CollectionChanged;
}
private void TabControl_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (TabItem tabItem in e.NewItems)
{
tabItem.IsVisibleChanged += TabItem_IsVisibleChanged;
}
}
else if (this.Items != null)
{
foreach (TabItem tabItem in this.Items)
{
tabItem.IsVisibleChanged += TabItem_IsVisibleChanged;
}
}
}
private void TabItem_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TabItem tabItem = sender as TabItem;
if (tabItem != null && tabItem.IsSelected && tabItem.Visibility != Visibility.Visible)
{
this.SelectedIndex = 0;
}
}
}
However this means that I have to use my own TabControl over the default one. Can anyone think of an even nicer solution?

Textbox in ContextMenu loses focus on mouse move

I have a Textbox withing the ControlTemplate of a MenuItem, which is inside a ContextMenu. The Textbox works well and I can type in it properly. But if I move the mouse over any of the other menu items in the context menu, they claim focus and I lose focus from the textbox. At this point I have to click back into the textbox to continue typing.
Is there a pattern or accepted method of resolving this issue?
Thanks
If you want to take back focus, you can type as below.
textBox.CaptureMouse();
textBox.ReleaseMouseCapture();
I think it can catch focus to textbox.
Well after trying a few different things, I got something to work:
For all other menu items that can capture focus (on mouse enter), set e.Handled = true for the PriviewGoTKeyboardFocus event:
void menuItem_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
One can do this automatically from a window base class by looping through all menu items in a context menu. This requires hijacking the tag for those menuitems in which you insert the textbox.
void contextMenu_Opened(object sender, RoutedEventArgs e)
{
ContextMenu contextMenu = sender as ContextMenu;
foreach (FrameworkElement frameworkElement in contextMenu.Items)
{
if (frameworkElement is MenuItem)
{
MenuItem menuItem = (frameworkElement as MenuItem);
if (!(menuItem.Tag != null && menuItem.Tag.ToString() == "MaintainFocus"))
menuItem.PreviewGotKeyboardFocus += new KeyboardFocusChangedEventHandler(menuItem_PreviewGotKeyboardFocus);
}
}
}
void contextMenu_Closed(object sender, RoutedEventArgs e)
{
ContextMenu contextMenu = sender as ContextMenu;
foreach (FrameworkElement frameworkElement in contextMenu.Items)
{
if (frameworkElement is MenuItem)
{
MenuItem menuItem = (frameworkElement as MenuItem);
if (!(menuItem.Tag != null && menuItem.Tag.ToString() == "MaintainFocus"))
menuItem.PreviewGotKeyboardFocus -= menuItem_PreviewGotKeyboardFocus;
}
}
}

How to deselect all selected items in a WPF treeview when clicking on some empty area?

I've got a rather funny problem with WPF. I have a tree-view, and selecting items works fine so far. The problem is, I want to unselect the currently selected item when the user clicks inside the blank area of the treeview. By default, the treeview keeps the current item selected, and I have added a context-menu option to deselect it, which is rather hardcore:
// Note: This is done recursivly from the start, so it
// works for child items as well
treeView.ItemContainerGenerator.ContainerFromItem(treeView.SelectedItem) as TreeViewItem).IsSelected = false;
Moreover, this is counter-intuitive, as it requires the user to right-click first, and second, after deselecting it with this way, the user cannot select it any more by clicking on the item. How is this supposed to work?
Edit: Some more information: I've added a handler to the TreeView to handle mouse click events, but the sender is always a TreeView instance, even if I click directly on a TreeViewItem. If I add a handler to my TreeView.ItemTemplate instead (i.e. the first child in the template), I never get events when I click on the empty area (which is rather logical). The code looks like this:
private void MyTreeView_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if ((sender as TreeViewItem) == null)
{
// Always triggered
System.Diagnostics.Trace.Write("Empty area clicked");
}
}
And the XAML for this is:
<TreeView x:Name="MyTreeView" Margin="3" MouseUp="MyTreeView_MouseUp">
I found this to work much better for me. I check the originalsource which for me if it comes form a treeviewitem will be an image or a textblock. I also use a view object with a HierarchicalDataTemplate and the BasicTreeViewBase is the base class for all of my different objects. Here is the code.
private void TemplateTreeView_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Right && !(e.OriginalSource is Image) && !(e.OriginalSource is TextBlock))
{
BasicTreeViewBase item = TemplateTreeView.SelectedItem as BasicTreeViewBase;
if (item != null)
{
TemplateTreeView.Focus();
item.IsSelected = false;
}
}
}
The un-selectable problem can be solved with a call to Focus on the TreeView after setting TreeViewItem.IsSelected.
There can be two more problem :
The treeview is binded so the SelectedItem is an item of the binded collection.
There is many levels so ItemContainerGenerator do not contain deepest level objects
for all this reason i use this function, but the selection must not fire any events.
private void UnselectTreeViewItem(TreeView pTreeView)
{
if(pTreeView.SelectedItem == null)
return;
if(pTreeView.SelectedItem is TreeViewItem)
{
(pTreeView.SelectedItem as TreeViewItem).IsSelected = false;
}
else
{
TreeViewItem item = pTreeView.ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
if (item != null)
{
item.IsSelected = true;
item.IsSelected = false;
}
}
}
I implemented a general selection control once, and required this behaviour.
This is how my method looked (adapted for treeview):
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
DependencyObject dpSource = e.OriginalSource as DependencyObject;
if (dpSource.FindVisualAncestor(o => typeof(TreeViewItem).IsAssignableFrom(o.GetType())) == null)
UnselectAll();
}
Basically, walk up the tree from the source. If a TreeViewItem was not found, than the user clicked empty space.
Use the extension class below
public static class TreeViewExtensions
{
public static TreeViewItem ContainerFromItem(this TreeView treeView, object item)
{
TreeViewItem containerThatMightContainItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(item);
if (containerThatMightContainItem != null)
return containerThatMightContainItem;
else
return ContainerFromItem(treeView.ItemContainerGenerator, treeView.Items, item);
}
private static TreeViewItem ContainerFromItem(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, object item)
{
foreach (object curChildItem in itemCollection)
{
TreeViewItem parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);
TreeViewItem containerThatMightContainItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);
if (containerThatMightContainItem != null)
return containerThatMightContainItem;
TreeViewItem recursionResult = ContainerFromItem(parentContainer.ItemContainerGenerator, parentContainer.Items, item);
if (recursionResult != null)
return recursionResult;
}
return null;
}
}
Then in MouseDown event of treeview use the extension method as below:
private void trview_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if ((sender as TreeViewItem) == null)
{
if (this.trview.ContainerFromItem(trview.SelectedItem) != null)
{
this.trview.ContainerFromItem(trview.SelectedItem).IsSelected = false;
}
}
this.trview.Focus();
}
Hope it works for you. I have it working in this way...
I was running into this situation myself with a custom Tree List View implementation after looking for a long time I finally found a solution that worked for me.
The full explanation can be found at http://social.msdn.microsoft.com/Forums/vstudio/en-US/36aca7f7-0b47-488b-8e16-840b86addfa3/getting-treeviewitem-for-the-selected-item-in-a-treeview
The basic idea is you capture the TreeViewItem.Selected event and save the source of the event into the Tag attribute on your TreeView. Then when you need to clear it, you can access the Tag attribute on your control and set the IsSelected value to False. This works for me with 2 levels of nested children. Hopefully it will work for you.
For persistence sake:
TreeView declaration
<TreeView Name="myTreeView" TreeViewItem.Selected="OnItemSelected"
ItemsSource="{Binding Source={StaticResource myHierarchicalData}}"/>
Event Handler
private void OnItemSelected(object sender, RoutedEventArgs e)
{
myTreeView.Tag = e.OriginalSource;
}
Clear selection logic
if (myTreeView.SelectedItem != null)
{
TreeViewItem selectedTVI = myTreeView.Tag as TreeViewItem;
// add your code here mine was selectedTVI.IsSelected = false;
}
This will deselect the currently selected TreeViewItem if none were clicked:
private void MyTreeView_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
if ((sender as TreeViewItem) == null) {
TreeViewItem item = MyTreeView.SelectedItem as TreeViewItem;
if(item != null){
item.IsSelected = false;
}
}
}
Hope this is what you were looking for!
MVVM: Call this method in the eventhandler righ/left mouse click:
private void SetTreeViewSelection(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
var treeView = (TreeView)sender;
if (treeView.SelectedItem == null)
{
return;
}
IInputElement dropNode = treeView.InputHitTest(mouseButtonEventArgs.GetPosition(treeView));
if (dropNode is ScrollViewer)
{
var myBindableObject = (MyBindableObject)treeView.SelectedItem;
myBindableObject.IsSelected = false;
}
}
For a C# treeview you use treeview.SelectedNode = null; I'm not sure if this works for WPF.

hiding grids using ESC key

I'm new to WPF.
I have like 15 grids on my Window and I have a small menu on which I can click and choose which grid to show up or hide. One grid at a time only. I would like that grid to hode (fade out) when I hit Esc. I have all the animations already, I just need to know what grid is visible (active) at the moment.
I don't know how to get current topmost control of my Window.
My solution is when KeyDown event is triggered on my Window to:
private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Escape)
{
//check all grids for IsVisible and on the one that is true make
BeginStoryboard((Storyboard)this.FindResource("theVisibleOne_Hide"));
}
}
By active, I assume that means the one that has keyboard focus. If so, the following will return the control that currently has keyboard input focus:
System.Windows.Input.Keyboard.FocusedElement
You could use it like this:
if (e.Key == System.Windows.Input.Key.Escape)
{
//check all grids for IsVisible and on the one that is true make
var selected = Keyboard.FocusedElement as Grid;
if (selected == null) return;
selected.BeginStoryboard((Storyboard)this.FindResource("HideGrid"));
}
An approach that would be more decoupled would be to create a static attached dependency property. It could be used like this (untested):
<Grid local:Extensions.HideOnEscape="True" .... />
A very rough implementation would look like:
public class Extensions
{
public static readonly DependencyProperty HideOnEscapeProperty =
DependencyProperty.RegisterAttached(
"HideOnEscape",
typeof(bool),
typeof(Extensions),
new UIPropertyMetadata(false, HideOnExtensions_Set));
public static void SetHideOnEscape(DependencyObject obj, bool value)
{
obj.SetValue(HideOnEscapeProperty, value);
}
public static bool GetHideOnEscape(DependencyObject obj)
{
return (bool)obj.GetValue(HideOnEscapeProperty);
}
private static void HideOnExtensions_Set(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as Grid;
if (grid != null)
{
grid.KeyUp += Grid_KeyUp;
}
}
private static void Grid_KeyUp(object sender, KeyEventArgs e)
{
// Check for escape key...
var grid = sender as Grid;
// Build animation in code, or assume a resource exists (grid.FindResource())
// Apply animation to grid
}
}
This would remove the need to have code in codebehind.

Resources