Is it possible to make WPF 4.0 RichTextEdit display it's Selection while not focused?
By default, the selection highlighting rectangle disappears when RichTextEdit loses focus.
The following natually does not work.
<RichTextBox>
<RichTextBox.Style>
<Style TargetType="{x:Type RichTextBox}">
<Style.Triggers>
<Trigger Property="IsFocused" Value="false">
<Setter Property="SelectionOpacity" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</RichTextBox.Style>
<FlowDocument>
<Paragraph>
<Run>Example Text</Run>
</Paragraph>
</FlowDocument>
</RichTextBox>
You have to handle the LostFocus event to prevent the selection to be removed.
xaml:
<RichTextBox LostFocus="RichTextBox_OnLostFocus" />
code behind:
void RichTextBox_OnLostFocus(object sender, RoutedEventArgs e)
{
var rtb = e.Source as RichTextBox;
if (rtb == null)
return;
if (!rtb.Selection.Start.Equals(rtb.Selection.End))
{
e.Handled = true;
}
}
Old question, but for anyone looking for an answer in future - any controls inheriting from TextBoxBase have a IsInactiveSelectionHighlightEnabled property.
If you set this property as true, selected text will remain highlighted when the containing control has lost focus (though the colour brush used is different).
This should cover WPF RichTextBox or FlowDocumentReader.
Related
I have a ListBox bound to an ObservableCollection, with a canvas as an ItemsPanel. Everything works as expected - I've implemented dragging of the items succesfuly - but the problem is that I can't set the ZIndex of the clicked item. Debuging shows that all the items have a ZIndex of 0, which looks strange to me. What I want is to bring the item to front when clicked and send to back when released. Could someone give me any ideas? Please feel free to ask for any code that might be useful.
Update: This is the ItemsContainerStyle, defined as a Window Resource
<Style x:Key="MediaContainerStyle" TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding MediaPosition.X,UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Canvas.Top" Value="{Binding MediaPosition.Y,UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Panel.ZIndex" Value="{Binding ZIndex,UpdateSourceTrigger=PropertyChanged}"/>
</Style>
and the template for the item
<DataTemplate x:Key="MediaDataTemplate">
<views:MediaItemView MouseDown="OnMediaItemMouseDown"
MouseMove="OnMediaItemMouseMove"/>
</DataTemplate>
where MediaItemView is a user control.
In the code behind, I do
void OnMediaItemMouseDown(Object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == Pressed)
{
FrameworkElement feItem = sender as FrameworkElement;
MediaViewModel vmItem = feItem.DataContext as MediaViewModel;
vmItem.ZIndex = vm.MainMedia.Count;
// Keep the click point
pClick = e.GetPosition(feItem);
}
}
where vm is an instance of my underlying viewmodel, containing a Double property ZIndex
And, of course, it was right before my eyes! The answer, (taken from Modify ZIndex of an Items in an ItemsControl) was to add a trigger to my ItemContainerStyle (which has a ListBoxItem as TargetType) for IsSelected property. So...
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="99"/>
</Trigger>
</Style.Triggers>
will do!
In my app (C# WPF) I have about 30 or 40 textBoxes in more grids and I want to change their foreground color in a loop. I use the code below and it works. But I want to use it for the whole project, not only for concrete grid
xaml code
<grid x:Name"stk">
.... some textBoxes ...
</grid>
*.cs code
foreach (TextBox item in this.stk.Children.OfType<TextBox>())
{
if (item.Name.StartsWith("txt"))
item.Foreground = Brushes.Orange;
}
So, when I have more grids, I have to put x:Name="..." into each one and this implies more foreach loops.
Much Simpler Way
Define a Style with TargetType set to Textbox and with no Key. This way this style will be applied to all textbox in the application without the need to bind the style or the foreground for each textbox.
<Application.Resources>
<SolidColorBrush Color="Red" x:Key="txtColor" />
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="{DynamicResource txtColor}" />
</Style>
</Application.Resources>
To change the Foreground Color.
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Application.Current.Resources.Contains("txtColor"))
{
Application.Current.Resources["txtColor"] = new SolidColorBrush(Colors.Blue);
}
}
Bind all your Textbox's Foreground to a common Brush Resource. Define the brush resource common to Project and access it everywhere.
In App.XML declare the brush resource so that you can access it anywhere from your project. [Note : You can also define it resource Dictionary and refer it]
<Application.Resources>
<SolidColorBrush Color="Red" x:Key="txtColor" />
</Application.Resources>
In All your textbox bind the foreground to the "txtColor" brush resource.
<TextBox Foreground="{DynamicResource txtColor}" Text="TextBox" />
To change the Foreground color of all textbox's, then change the commonly defined resource's color. Below I changed the color in button click. Access th resource using the key and set the new brush which you want to set.
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Application.Current.Resources.Contains("txtColor"))
{
Application.Current.Resources["txtColor"] = new SolidColorBrush(Colors.Blue);
}
}
Ignore my code and have a look at this answer
Find all controls in WPF Window by type
So ... To solve my problem where I couldn't change foreground color of textBoxes when some textBox is disabled ... I used code below ...
<Application.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Orange"/>
</Trigger>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
What about creating a "usercontrol" based on the standard textbox where you control the appearance of the foreground. This way you have a reusable control that you can use anywhere you want and have "full control" over it's appearance and behaviour. Take a look at this article, or this one for some examples that might help you go the right way ;)
Is it possible to have a TextBox that is disabled by default, but becomes enabled when a user double-clicks it?
you can place your TextBox inside StackPanel like this:
<StackPanel MouseLeftButtonDown="StackPanel_MouseDown">
<TextBox Name="textBox1"/>
</StackPanel>
Then in StackPanel event handler:
private void StackPanel_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount >= 2)
{
textBox1.IsEnabled = true; //only hit here on DoubleClick
}
}
you can also simulate StackPanel DoubleClick as described on this question:
WPF StackPanel with Click AND DoubleClick
That's very unusual, also when a control is disabled it is not expected to get input. Users seeing a disabled control normally would not even try to click/double click on it.
Maybe you can add a check box to enable it (or the function belonging to it), or show a message box when double clicking it when it is not allowed/meant to. In this case you also can clearly add a reason why it cannot be double clicked.
What I have seen before is a checkbox without text right before the control. When you click the checkbox it enables the control (text box in your case) after it. You can even use a tooltip for the check box to provide help information what the checkbox is doing.
I would try attaching to the PreviewMouseDown event and enable/disable there.
Otherwise you will have to do the old VB6 trick of having a transparent control above the textbox to receive the click event.
This question is old, but maybe I can help someone that finds a solution for this.
In a recent project I've needed to simulate two states: view and edit. I've do this using a textbox. In view state, value is displayed but you cannot got focus clicking on the control. To enable editing mode you need to double click the control. To avoid the control getting focus by clicking on it and also the disadvantages of disable it, I've used two preview events for control the behaviour of the textbox and adapt it's responses to application needs and state. One of the events is PreviewMouseDown:
private void tbxVariable_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if(!tbxVariable.IsFocused && e.ChangedButton == MouseButton.Left)
e.Handled = true;
}
In this event we will block the mouse down button if our textbox isn't focused yet. This prevents textbox to get focus. So it will behave like a label. When the control is focused, this event isn't blocked and is propagated towards the control. Note that maybe you need to change the cursor because editing cursor is used when the mouse is over the control. Note also, that we are blocking only the left button.
The second event looks like:
private void tbxVariable_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left && !tbxVariable.IsFocused)
tbxVariable.Focus();
}
In the second event we will bring the focus to the control on double left click if it isn't focused yet. If control has focus so we will let the event propagate and the control will behave normally.
In my case I've created a special style for the textbox leaving borders, backgrounds, and all style behaviours. This is the XAML code:
<Style x:Key="InlineEditorTextBox" TargetType="{x:Type TextBox}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="MinWidth" Value="5" />
<Setter Property="MaxHeight" Value="16" />
<Setter Property="AllowDrop" Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<ScrollViewer Margin="0" x:Name="PART_ContentHost" VerticalScrollBarVisibility="Disabled" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have a TabControl on a UserControl backed by a ViewModel, and the Visibility of one of the tab items is bound to a property on the ViewModel.
<TabControl x:Name="myTabControl">
<TabItem Header="Tab 1" />
<TabItem Header="Tab 2" Visibility="{Binding HasData, Converter={StaticResource boolToVisibilityConverter}}"/>
</TabControl>
When the Visibility of the TabItem changes, it collapses (hides) the TabItem header, but it continues displaying its content.
I want the TabControl to switch to the visible tab when the other tab is hidden, and was a little surprised to find out it doesn't happen automatically.
Attaching an event handler to the SelectionChanged event of the TabControl shows that TabItem.IsSelected (and TabControl.SelectedItem) is not even affected when the TabItem.Visibility changes (is this a bug?!).
I've tried both a property trigger:
<!-- This doesn't compile because of TargetName on the Setter, think you can only use it in Control Templates.
I don't know how to refer to the parent TabControl from within the TabItem style. -->
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Collapsed">
<Setter TargetName="myTabControl" Property="SelectedIndex" Value="0" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
and a data trigger:
<!-- This doesn't quite work, it affects the Visibility of the TabItem's content too -->
<TabControl.Style>
<Style TargetType="{x:Type TabControl}" BasedOn="{StaticResource {x:Type TabControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem.Visibility, ElementName=tabControl}"
Value="Collapsed">
<Setter Property="SelectedIndex" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
I can't get the triggers to work, and there's no VisibilityChanged event I can handle, so I'm kind of stuck and would appreciate some help.
The TabItem class has an IsVisibleChanged event that you can use.
Bind SelectedIndex of TabControl to a property. And change the value of this property to the index of tab which you want to display whenever you change the visibility to collapse of tab item.
You could add this event handler to the code behind. It will test your control in the first place and on changes to tab visibility due to bindings.
Instead of doing this OnLoaded of course it makes total sense to put this into an attached Property. (AutoSelect?) . The code is the same. You get called in the first place and attach events to IsVisibleChanged. Then the only trick is to use a lambda (Parameter binding) to get the TabControl instance into the event callback. I am posting this solution, because it is shorter.
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var tabControl = (TabControl) sender;
// register visibility changed to react on changes
foreach (TabItem item in tabControl.Items)
{
item.IsVisibleChanged += (mSender, ev) => item_IsVisibleChanged(mSender, ev, tabControl);
}
// if current selected tab is invisible, find and select first visible one.
if (!((TabItem) tabControl.SelectedItem).IsVisible)
{
foreach (TabItem item in tabControl.Items)
{
if (item.IsVisible)
{
tabControl.SelectedItem = item;
return;
}
}
}
}
private static void item_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e, TabControl tabControl)
{
// just became IsVisible = false
if ((bool)e.NewValue == false)
{
if (tabControl == null) return;
ItemCollection items = tabControl.Items;
foreach (UIElement item in items)
{
if (item.IsVisible)
{
tabControl.SelectedItem = item;
return;
}
}
}
}
I have a style defined for my ListBoxItems with a trigger to set a background color when IsSelected is True:
<Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Padding="0" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This style maintains the selected item even when the ListBox and ListBoxItem loses focus, which in my case is an absolute must.
The problem is that I also want the ListBoxItem to be selected when one of its TextBox's child gets focused. To achieve this I add a trigger that sets IsSelected to true when IsKeyboardFocusWithin is true:
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected.
How can I keep both behaviours?
When your listbox looses focus, it will set selected item to null because of your trigger. You can select on focus using some code behind that will not unselect when you loose focus.
XAML:
<Window x:Class="SelectedTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel>
<TextBox Text="Loose focus here" />
<ListBox Name="_listBox" ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" GotFocus="OnChildGotFocus">
<TextBox Text="{Binding .}" Margin="10" />
<TextBox Text="{Binding .}" Margin="10" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" SnapsToDevicePixels="true" Background="Transparent">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</StackPanel>
</Window>
Code behind:
private void OnChildGotFocus(object sender, RoutedEventArgs e)
{
_listBox.SelectedItem = (sender as StackPanel).DataContext;
}
"When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected."
Actually, I don't think it has lost that original behavior. What I suspect is happening is you're clicking directly in the textbox from somewhere else so the underlying ListBoxItem never actually became selected. If it did however, you'd see the selection would still remain after you left as you want.
You can test this by forcing the ListBoxItem to be selected by clicking directly on it (side-note: you should always give it a background, even if just 'transparent' so it can receive mouse clicks, which it won't if it's null) or even just hitting 'Shift-Tab' to set the focus there, back from the textbox.
However, that doesn't solve your issue, which is that the TextBox gets the focus but doesn't let the underlying ListBoxItem know about it.
The two approaches you can use for that are an event trigger or an attached behavior.
The first is an event trigger on the IsKeyboardFocusWithinChanged event where you set 'IsSelected' to true if the keyboard focus changed to true. (Note: Sheridan's answer does a faux-change-notification but it should not be used in cases where you can multi-select in the list because everything becomes selected.) But even an event trigger causes issues because you lose the multi-select behaviors such as toggling or range-clicking, etc.
The other (and my preferred approach) is to write an attached behavior which you set on the ListBoxItem, either directly, or via a style if you prefer.
Here's the attached behavior. Note: You again would need to handle the multi-select stuff if you want to implement that. Also note that although I'm attaching the behavior to a ListBoxItem, inside I cast to UIElement. This way you can also use it in ComboBoxItem, TreeViewItem, etc. Basically any ContainerItem in a Selector-based control.
public class AutoSelectWhenAnyChildGetsFocus
{
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
"Enabled",
typeof(bool),
typeof(AutoSelectWhenAnyChildGetsFocus),
new UIPropertyMetadata(false, Enabled_Changed));
public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); }
public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); }
private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var attachEvents = (bool)e.NewValue;
var targetUiElement = (UIElement)sender;
if(attachEvents)
targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged;
else
targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged;
}
static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var targetUiElement = (UIElement)sender;
if(targetUiElement.IsKeyboardFocusWithin)
Selector.SetIsSelected(targetUiElement, true);
}
}
...and you simply add this as a property setter in your ListBoxItem's style
<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />
This of course assumes you've imported an XML namespace called 'behaviors' that points to the namespace where the class is contained. You can put the class itself in a shared 'Helper' library, which is what we do. That way, everywhere we want it, its a simple property set in the XAML and the behavior takes care of everything else.
I figured out that IsKeyboardFocusWithin is not the best solution.
What I did in this case was to set the style on all of the controls used as DataTemplate to send the GotFocus-event to be handled in code behind. Then, in code behind, I searched up the visual tree (using VisualTreeHelper) to find the ListViewItem and set IsSelected to true. This way it does not "touch" the DataContext and works just with the View elements.
<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle">
...
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/>
...
private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
FocusParentListViewItem(control);
}
private void FocusParentListViewItem(Control control)
{
var listViewItem = FindVisualParent<ListViewItem>(control);
if (listViewItem != null)
listViewItem.IsSelected = true;
}
public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
var correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}