John Noonan
John Noonan

Reputation: 951

Clicking HyperLinks in a RichTextBox without holding down CTRL - 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.

Upvotes: 20

Views: 17324

Answers (7)

BionicCode
BionicCode

Reputation: 28968

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>

Upvotes: 1

Peter Huber
Peter Huber

Reputation: 3312

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

Upvotes: 1

AZ_
AZ_

Reputation: 21899

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;
        }

Upvotes: -1

choi
choi

Reputation: 19

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());
}

Upvotes: 1

hillin
hillin

Reputation: 1779

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.

Upvotes: 16

JHubbard80
JHubbard80

Reputation: 2277

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.

Upvotes: 33

John Noonan
John Noonan

Reputation: 951

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.

Upvotes: 5

Related Questions