Clicking HyperLinks in a RichTextBox without holding down CTRL - WPF - wpf

I have a WPF RichTextBox with isReadOnly set to True. I would like users to be able to click on HyperLinks contained within the RichTextBox, without them having to hold down Ctrl.
The Click event on the HyperLink doesn't seem to fire unless Ctrl is held-down, so I'm unsure of how to proceed.

I found a solution. Set IsDocumentEnabled to "True" and set IsReadOnly to "True".
<RichTextBox IsReadOnly="True" IsDocumentEnabled="True" />
Once I did this, the mouse would turn into a 'hand' when I hover over a text displayed within a HyperLink tag. Clicking without holding control will fire the 'Click' event.
I am using WPF from .NET 4. I do not know if earlier versions of .NET do not function as I describe above.

JHubbard80's answer is a possible solution, it's the easiest way if you do not need the content to be selected.
However I need that :P here is my approach: set a style for the Hyperlinks inside the RichTextBox. The essential is to use a EventSetter to make the Hyperlinks handling the MouseLeftButtonDown event.
<RichTextBox>
<RichTextBox.Resources>
<Style TargetType="Hyperlink">
<Setter Property="Cursor" Value="Hand" />
<EventSetter Event="MouseLeftButtonDown" Handler="Hyperlink_MouseLeftButtonDown" />
</Style>
</RichTextBox.Resources>
</RichTextBox>
And in codebehind:
private void Hyperlink_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
var hyperlink = (Hyperlink)sender;
Process.Start(hyperlink.NavigateUri.ToString());
}
Thanks to gcores for the inspiaration.

Managed to find a way around this, pretty much by accident.
The content that's loaded into my RichTextBox is just stored (or inputted) as a plain string. I have subclassed the RichTextBox to allow binding against it's Document property.
What's relevant to the question, is that I have an IValueConverter Convert() overload that looks something like this (code non-essential to the solution has been stripped out):
FlowDocument doc = new FlowDocument();
Paragraph graph = new Paragraph();
Hyperlink textLink = new Hyperlink(new Run(textSplit));
textLink.NavigateUri = new Uri(textSplit);
textLink.RequestNavigate +=
new System.Windows.Navigation.RequestNavigateEventHandler(navHandler);
graph.Inlines.Add(textLink);
graph.Inlines.Add(new Run(nonLinkStrings));
doc.Blocks.Add(graph);
return doc;
This gets me the behavior I want (shoving plain strings into RichTextBox and getting formatting) and it also results in links that behave like a normal link, rather than one that's embedded in a Word document.

My answer is based on #BionicCode's answer, which I wanted to extend with the event handler code, which I had some difficulties to get it working.
<RichTextBox IsDocumentEnabled="True" IsReadOnly="True">
<FlowDocument>
<Paragraph>
<Run Text="Some editable text" />
<Hyperlink x:Name="DuckduckgoHyperlink"
NavigateUri="https://duckduckgo.com">
DuckDuckGo
</Hyperlink>
</Paragraph>
</FlowDocument>
</RichTextBox>
I changed his code slightly:
I wanted the RichTextBox to be readonly. When the RichTextBox is readonly, it is not necessary to put the HyperLink into a TextBlock. However, using TextBlock in a RichTextBlock where the user can make changes is a great suggestion.
In my programming style, code related stuff belongs in the code behind file. Event handlers are code and I prefer to even add the event handler to its control from code behind. To do that, it is enough to give the Hyperlink a name.
Code behind
I needed to display some rich text with links in a HelpWindow:
public HelpWindow() {
InitializeComponent();
DuckduckgoHyperlink.RequestNavigate += Hyperlink_RequestNavigate;
}
private void Hyperlink_RequestNavigate(object sender,
RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) {
UseShellExecute = true,
});
e.Handled = true;
}
Note that the same event handler can be used by any HyperLink. Another solution would be not to define the URL in XAML but hard code it in the event handler, in which case each HyperLink needs its own event handler.
In various Stackoverflow answers I have seen the code:
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
Which resulted in the error message:
System.ComponentModel.Win32Exception: 'An error occurred trying to start process 'https://duckduckgo.com/' with working directory '...\bin\Debug\net6.0-windows'. The system cannot find the file specified.'

Do not handle any mouse events explicitly and do not force the cursor explicitly - like suggested in every answer.
It's also not required to make the complete RichTextBox read-only (as suggested in another answer).
To make the Hyperlink clickable without pressing the Ctrl key, the Hyperlink must be made read-only e.g., by wrapping it into a TextBlock (or alternatively by making the complete RichTextBox read-only, of course).
Then simply handle the Hyperlink.RequestNavigate event or/and attach an ICommand to the Hyperlink.Command property:
<RichTextBox IsDocumentEnabled="True">
<FlowDocument>
<Paragraph>
<Run Text="Some editable text" />
<TextBlock>
<Hyperlink NavigateUri="https://duckduckgo.com"
RequestNavigate="OnHyperlinkRequestNavigate">
DuckDuckGo
</Hyperlink>
</TextBlock>
</Paragraph>
</FlowDocument>
</RichTextBox>

I changed EventSetter from #hillin's answer.
MouseLeftButtonDown didn't work in my code (.Net framework 4.5.2).
<EventSetter Event="RequestNavigate" Handler="Hyperlink_RequestNavigate" />
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
Process.Start(e.Uri.ToString());
}

If you want to turn Arrow into a Hand cursor always without default system navigation, below is the approach.
<RichTextBox>
<RichTextBox.Resources>
<Style TargetType="{x:Type Hyperlink}">
<EventSetter Event="MouseEnter" Handler="Hyperlink_OnMouseEnter"/>
</Style>
</RichTextBox.Resources>
</RichTextBox>
private void Hyperlink_OnMouseEnter(object sender, MouseEventArgs e)
{
var hyperlink = (Hyperlink)sender;
hyperlink.ForceCursor = true;
hyperlink.Cursor = Cursors.Hand;
}

Related

User Control events when "splitting" a XAML file

Several people have asked how to split up large XAML files into smaller more manageable or readable chunks. In my case I have a XAML file with 10 tabs and each tab has a lot of complex controls. So the XAML file for this is huge and hard to read.
The "standard" answer for this seems to be User Controls.
I'm sure this is a real noob question, but if all you're trying to do is split up the XAML file, how do you do it without splitting up the C#, too? When you create a WPF user control Visual Studio creates a new XAML file plus new code-behind file to go with it and handle the events.
What I really wanted was the equivalent of a "C# partial" directive for XAML so I could just split it up among multiple files but have the events handled in one place. How close can I get to that?
Thanks in advance!
There is no way to do as you describe (to allow multiple xaml files to share the same c# code). Also from what you describe there isn't an easy way to quickly abstract that code without having to make some changes. Since wpf events are typically driven by commands, the best solution would probably be to change your events to fire commands rather than putting the logic within the actual event handler itself, then calling it from the user control would be trivial.
But before you go changing all your code, you may be able to abstract out a lot of the long stuff using styles, which are way easier to abstract out and shouldn't mess with your events. So if you notice repeating how things are setup across lots of controls, just declare all of it as a style and you can move it into a resource dictionary elsewhere to remove some clutter.
To elaborate a bit you can use styles not just to stop repetition, but also to abstract out how you define your controls (much like you are trying to use user controls for, you can also define events there). For example...
<Style TargetType="TabItem" x:Key="Tab1Style">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<!--Note even if this style is defined in a resource file the
events will still be tied to the class of the control
using the style-->
<Button Click="Button_Click"/>
<Button Click="Button_Click_1" />
<Button Click="Button_Click_2" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then simplify one of your tabitems to simply...
<TabControl>
<TabItem Style="{StaticResource Tab1Style}" />
</TabControl>
If you really have your heart set on user controls you could also just route all the events out. Something like this...
<UserControl ...>
<Button Click="OnClick"/>
</UserControl>
public partial class UserControl1 : UserControl
{
public static readonly RoutedEvent ButtonClick = EventManager.RegisterRoutedEvent(
"ButtonClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl1));
public event RoutedEventHandler ButtonClickHandler
{
add { AddHandler(ButtonClick, value); }
remove { RemoveHandler(ButtonClick, value); }
}
private void OnClick(object sender, RoutedEventArgs e)
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(UserControl1.ButtonClick);
RaiseEvent(newEventArgs);
}
public UserControl1()
{
InitializeComponent();
}
}
<Window>
<local:UserControl1 ButtonClickHandler="Button_Click" />
</Window>
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Click");
}
(like I said lots of plumbing code)

Binding Setting Property but UI not updating. Can I debug within referenced project/control?

I have a custom control with bindings like below
<DataTemplate DataType="{x:Type vm:EditorTabViewModel}">
<me:MarkdownEditor
Options="{Binding Path=Options, RelativeSource={RelativeSource AncestorType=Window}}" />
</DataTemplate>
I find that binding (Window1.Options) is being set (after stepping through code in debug mode), the markdown editor options (supposed to set Fonts, Colors etc) does not get set, or at least the UI does not update. I want to bug whats happening with in the MarkdownEditor.xaml.cs but thats another (referenced) project. How can I verify that the MarkdownEditor.Options is being set at least?
I have actually tested that the MarkdownEditor side is working by the below
<Window ...>
<Grid>
<Button Content="Options" Click="Button_Click" Grid.Row="0" />
<me:MarkdownEditor Options="{Binding Options, RelativeSource={RelativeSource AncestorType=Window}}" Grid.Row="1" />
</Grid>
</Window>
So the difference is the latter is a MarkdownEditor just in a Grid in a Window. The one failing is a MarkdownEditor within a TabControl bound to a ObservableCollection<TabViewModel>
Visual Studio Solution Replicating The Problem
I am not really good at explaining things, so a simple project I made up minus all the unnecessary noise uploaded to media fire so you can take a look at whats wrong
The video showing the problem on Screenr
With just a simple usage, editor in a window/grid.
the binding works ok
Then when used in conjunction with TabControl bound to ObservableCollection<EditorTabViewModel>, the binding works as shown in the 2 TextBoxes updating its values. but the editor does not update
After reading Kent Boogaart's answer to this question I think that the right place to change SetValue to SetCurrentValue isn't in the CLR Property but in the constructor for MarkDownEditor.
public MarkdownEditor()
{
InitializeComponent();
//Options = new MarkdownEditorOptions();
this.SetCurrentValue(OptionsProperty, new MarkdownEditorOptions());
DataContext = this;
}
In fact, this will work just as good without this.SetCurrentValue also since Options will be set through the Binding.
To verify that your Binding has in fact been overwritten by SetValue you can add this code in some event for TabUsage (e.g PreviewMouseRightButtonDown for the FontSize TextBox) and the Binding will start to work again.
private void TextBox_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
MarkdownEditor.MarkdownEditor editor = VisualTreeHelpers.GetVisualChild<MarkdownEditor.MarkdownEditor>(this);
Binding binding = new Binding();
binding.Path = new PropertyPath("Options");
binding.Source = this;
binding.Mode = BindingMode.TwoWay;
editor.SetBinding(MarkdownEditor.MarkdownEditor.OptionsProperty, binding);
}

WPF custom command in context menu are disabled until any button clicked

I have a custom command and I try to execute them from the context menu, but they are always displayed as disabled unless I click any button on the UI (buttons do not have anything to do with commands).
After clicking a button, commands start to be displayed correctly (when they are unavailable they get disabled and enabled if available).
Edit: it turns out that it is not the button click which makes command work correctly, but button or other controls in focus (e.g. if I tab into a control this also enables my commands).
Here is the code for commands:
<Window.InputBindings>
<KeyBinding Command="{x:Static local:MainWindow.Quit}" Key="Q" Modifiers="Ctrl"/>
<KeyBinding Command="{x:Static local:MainWindow.Disconnect}" Key="D" Modifiers="Ctrl"/>
</Window.InputBindings>
<Window.ContextMenu>
<ContextMenu Opacity="95">
<MenuItem Header="Quit Application Ctrl + Q" Command="{x:Static local:MainWindow.Quit}"/>
<MenuItem Header="Disconnect from the pump Ctrl + D" Command="{x:Static local:MainWindow.Disconnect}"/>
</ContextMenu>
</Window.ContextMenu>
Here is the commands CanExecuteMethod:
public static RoutedCommand Quit = new RoutedCommand();
private void QuitCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
This issue is due to the ContextMenu being on a separate Visual and Logical Tree to that of the Window and its Controls.
For anyone still looking for an answer to this issue - After trawling the internet I have found the most effective answer to be to include the following in any declaration of a MenuItem that needs its commands to be heard by it's "owner".
In layman's terms; if you want the commands of your context menu to be heard by the thing you're right clicking on. Add this code:
CommandTarget="{Binding Path=PlacementTarget,
RelativeSource={RelativeSource AncestorType=ContextMenu}
}"
Example:
<ContextMenu>
<MenuItem Header="Close" Command="Application.Close"
CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
This will also work within Templates (something I found a lot of another solutions not to support). Here is an explanation of the meaning of the statement taken from elsewhere (I'm appalling at explaining things):
Every FrameworkElement has a DataContext that is an arbitrary object. The default source for a data binding is that DataContext. You can use RelativeSource.Self to change the source for a binding to the FrameworkElement itself instead of its DataContext. So the RelativeSource part just moves you "up one level" from the DataContext of the FrameworkElement to the FrameworkElement itself. Once you are at the FrameworkElement you can specify a path to any of its properties. If the FrameworkElement is a Popup, it will have a PlacementTarget property that is the other FrameworkElement that the Popup is positioned relative to.
In short, if you have a Popup placed relative to a TextBox for example, that expression sets the DataContext of the Popup to the TextBox and as a result {Binding Text} somewhere in the body of the Popup would bind to the text of the TextBox.
I honestly hope that this information saves someone who's new to WPF the headache I've gone through this weekend... though it did teach me a lot!
Completely different track, now:
there is indeed something special about the ContextMenu as the carrier for commands:
the menu is not regarded as part of the window and therefore does not behave like an element in its visual tree would.
There are different solutions for your problems defined here:
http://www.wpftutorial.net/RoutedCommandsInContextMenu.html
The easiest approach seems to be adding this to your XAML (for the window):
FocusManager.FocusedElement="{Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}"
I just ran into this while trying to implement a custom context menu for AvalonDock. None of the solutions suggested above worked for me.
I got the context menu working by explicitly registering my command handlers on the ContextMenu class in addition to the main widow. The function below is a helper I used for command registration.
void RegisterCmd(RoutedCommand command, ExecutedRoutedEventHandler handler, CanExecuteRoutedEventHandler canExecute)
{
var binding = new CommandBinding(command, handler, canExecute);
this.CommandBindings.Add(binding);
CommandManager.RegisterClassCommandBinding(typeof(ContextMenu), binding);
}
There is probably some change "behind the scenes" that would normally enable the commands, but the view is not aware of this change.
One would need to see the Command-implementations to give more precise hints.
You can either make anything that changes your command-enable-state notify the view or manually trigger a command-refresh via CommandManager.InvalidateRequerySuggested(), for example when the context-menu opens.
WPF ICommands work that way; they requery their CanExecute function whenever something in the view changes (e.g. PropertyChanged-event is fired or a button is clicked), but they don't requery if they have no reason to.
This is a known bug. If there is no focused element in the window's main focus scope, the CanExecute routing will stop at the ContextMenu, so it will not reach to the CommandBinding on the Window, one workaround is to bind MenuItem's CommandTarget to the main window, as following code demonstrates:
<Window.ContextMenu>
<ContextMenu >
<ContextMenu.Items>
<MenuItem Command="ApplicationCommands.Open"
CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu.Items>
</ContextMenu>
</Window.ContextMenu>

How to select ListBoxItem upon clicking on button in Template?

I have the following Data Template applied to a ListBox:
<DataTemplate x:Key="MyTemplate" DataType="{x:Type DAL:Person}">
<StackPanel Orientation="Horizontal">
<Button Content="X" Command="{x:Static cmd:MyCommands.Remove}"/>
<TextBlock Text="{Binding Person.FullName}" />
</StackPanel>
</DataTemplate>
When I click on the button the command gets fired but the ListBoxItem doesn't get selected. How do I force it to get selected, so that I can get the selected item in my "executed" method?
Thanks
A better way, since you're not really interested in selecting the item (because it will quickly get deleted anyway) would be to pass the item itself to the Command as a CommandParameter.
Alternatively, you can go about in a roundabout manner either with code-behind or with triggers, but I don't think it would be as to the point. For example:
you could handle the ButtonBase.Click event on your listbox, like
<ListBox ButtonBase.Click="lb_Click"
...
then in your code behind, do this:
private void lb_Click(object sender, RoutedEventArgs e)
{
object clicked = (e.OriginalSource as FrameworkElement).DataContext;
var lbi = lb.ItemContainerGenerator.ContainerFromItem(clicked) as ListBoxItem;
lbi.IsSelected = true;
}
That gets the clicked bound item, because the datacontext of the button is inherited from it's templated item, then the actual autogenerated ListBoxItem from the ListBox's ItemContainerGenerator, and sets the IsSelected property to true. I think that's one of the fastest and easiest ways. Also works with multiple ButtonBase-derived objects in the template.
Of course you can also more nicely encapsulate all this (more or less exactly the same) as a reusable Behavior:
public class SelectItemOnButtonClick : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(handler), true);
}
protected override void OnDetaching()
{
this.AssociatedObject.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(handler));
base.OnDetaching();
}
private void handler(object s, RoutedEventArgs e)
{
object clicked = (e.OriginalSource as FrameworkElement).DataContext;
var lbi = AssociatedObject.ItemContainerGenerator.ContainerFromItem(clicked) as ListBoxItem;
lbi.IsSelected = true;
}
}
You can use it like this:
<ListBox xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" ...>
<i:Interaction.Behaviors>
<local:SelectItemOnButtonClick />
</i:Interaction.Behaviors>
</ListBox>
Add error handling code like at least null checks, of course - wouldn't want a simple thing like this bombing your app.
To understand the problem, the button sets the Handled property to true for all the mouse events that act on it (MouseDown/Click) so they aren't being considered by the ListBoxItem. You could also attach the MouseDown event to the ListBox and walk the visual tree upwards until you reach the parent ListBoxItem but that's a lot more tricky... eh if you're curious, you can read this article to know why, basically you'll also encounter FrameworkContentElements (which also respond to MouseDown) so the code will get more complicated, with the upside that anything clicked inside the datatemplate will trigger the ListBoxItem to be selected, regardless of whether it marked the event as handled.
Heh, I also tried to do it exclusively with styles and triggers but it got ugly fast and I lost interest (and lost track of all the... err thingies). Basically it could be solved, I think, but I reaaaly don't think it's worth the bother. Maybe I overlooked something obvious though, don't know.
Make the underlying object expose a RemoveCommand property, and bind the button's Command property to it. This simplifies the data template; it also greatly simplifies the case where application logic may dictate that a specific item can't be removed.
Alex, thanks for answer. Your solution with Behavior is great. First solution is not so good because that will work only if you click on specific Button. Here is one more solution that will work on click on arbitrary control form ListBoxItem template:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem"
BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
That is XAML only approach. I also set BasedOn property just to be sure to not override the current ListBoxItem style.

Select the Initial Text in a Silverlight TextBox

I am trying to figure out the best way to select all the text in a TextBox the first time the control is loaded. I am using the MVVM pattern, so I am using two-way binding for the Text property of the TextBox to a string on my ViewModel. I am using this TextBox to "rename" something that already has a name, so I would like to select the old name when the control loads so it can easily be deleted and renamed. The initial text (old name) is populated by setting it in my ViewModel, and it is then reflected in the TextBox after the data binding completes.
What I would really like to do is something like this:
<TextBox x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}" SelectedText="{Binding NameViewModelProperty, Mode=OneTime}" />
Basically just use the entire text as the SelectedText with OneTime binding. However, that does not work since the SelectedText is not a DependencyProperty.
I am not completely against adding the selection code in the code-behind of my view, but my problem in that case is determining when the initial text binding has completed. The TextBox always starts empty, so it can not be done in the constructor. The TextChanged event only seems to fire when a user enters new text, not when the text is changed from the initial binding of the ViewModel.
Any ideas are greatly appreciated!
Dan,
I wrote a very simple derived class, TextBoxEx, that offers this functionality. The TextBoxEx class derives from TextBox, and can be referenced in XAML for any and all of your TextBox’s. There are no methods to call. It just listens for Focus events and selects it own text. Very simple.
Usage is as follows:
In XAML, reference the assembly where you implement the TextBoxEx class listed below, and add as many TextBoxEx elements as you need. The example below uses data binding to display a username.
<UserControl x:Class="MyApp.MainPage"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:ClassLibrary;assembly=ClassLibrary"
>
.
.
.
<c:TextBoxEx x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}" Width="120" />
This code below works with Silverlight 3.
using System.Windows;
using System.Windows.Controls;
namespace ClassLibrary
{
// This TextBox derived class selects all text when it receives focus
public class TextBoxEx : TextBox
{
public TextBoxEx()
{
base.GotFocus += OnGotFocus;
}
private void OnGotFocus(object sender, RoutedEventArgs e)
{
base.SelectAll();
}
}
}
Good luck.
I'm leaving Jim's solution as the answer, since calling SelectAll() on the GotFocus event of the TextBox did the trick.
I actually ended up making a Blend TriggerAction and an EventTrigger to do this instead of subclassing the TextBox or doing it in code-behind. It was really simple to do and nice to be able to keep the behavior logic encapsulated and just add it declaratively in XAML to an existing TextBox.
Just posting this in case anyone else comes across this thread and is interested:
XAML:
<TextBox x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<local:SelectAllAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
C#
public class SelectAllAction : TriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectAll();
}
}
}
Just wanna add a link I found pertaining to this - here is a fantastic discussion (read comments) on Behaviours vs subclassing vvs attached properties...

Resources