19

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.

David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
John Noonan
  • 941
  • 3
  • 11
  • 19

7 Answers7

31

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
  • 2,217
  • 1
  • 20
  • 24
  • 6
    this has the unforunate side effect of making the text unselectable for copy and paste purposes. – Julien Dec 17 '12 at 15:40
  • To clarify what's going on: The document must be enabled to click on the hyperlink, and it will default to disabled. But if it's not read-only, you have to hold down the control key for it to raise the event, because if you are allowed to edit the text, you don't want to accidentally navigate. This behavior is the same as MS Word when editing a document: in readonly mode you just click the URL, but while editing you have to hold the control key. – Bryce Wagner Aug 22 '19 at 17:53
16

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.

Community
  • 1
  • 1
hillin
  • 1,603
  • 15
  • 21
  • I don't get the hand, but whatever. It works, and even better, gives me access to the hyperlink's navigation code - so if a directory doesn't exist, my app doesn't crash! – Darkhydro Nov 12 '14 at 00:22
  • Handling the click event worked for me. It might be a Windows 10 / WPF 4.5 thing but it worked. Thanks. – CAD bloke Apr 18 '16 at 10:17
5

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.

John Noonan
  • 941
  • 3
  • 11
  • 19
1

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());
}
choi
  • 19
  • 2
1

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>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Great answer, unfortunately writing the RequestNavigate event handler is not that trivial. I hope you don't mind that I took your answer and added the event handler code, which cannot be nicely done in a comment. – Peter Huber Sep 16 '22 at 09:27
  • Why isn't it trivial? You mean the actual code that navigates to the link target? (You should know that this is not subject of the question. the Question is about invoking the link without pressing the Control key and not about handling the link). – BionicCode Sep 16 '22 at 10:14
  • It took me some time to figure out how to solve the error message and I assume if someone comes here, he does not really care what is the original question, but he appreciates if he gets all the information needed to make 'Click on the link' work, which includes opening the browser. One answer here recommended to use `Process.Start(e.Uri.ToString())`, which didn't work in my case. So I felt it is best to combine everything in one answer. Although I feel bad about copying so much from you. – Peter Huber Sep 16 '22 at 15:43
1

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:

  1. 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.
  2. 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.'

Peter Huber
  • 3,052
  • 2
  • 30
  • 42
  • `Hyperlink.NavigateUri` is a dependency property. That means you can bind an URL/URI to it. This will simplify your code and allows to separate the data source (source that provides the link) from the actual link logic (how to execute the/any link). The actual URI does not have to be static (hardcoded in XAML or C#). – BionicCode Sep 16 '22 at 10:29
  • Good point. In my case it is a HelpWindow, where I wrote the help text into the Window.xaml file. In such a case no data binding is needed. The complete help has only 6 lines of text. Of course, if the help text would be complicated, editable and stored in a database, your suggestion would make a lot of sense. – Peter Huber Sep 16 '22 at 15:49
-1

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;
        }
AZ_
  • 21,688
  • 25
  • 143
  • 191