Scott Solmer
Scott Solmer

Reputation: 3897

RequestNavigate to Hyperlink with page anchor

I need to open a local .HTM file and navigate to a specific anchor name.
In this case it is an alarm information file with over 1,000 alarms / anchors.

In my test example (full code below) the Uri Fragment doesn't make it into the browser.
I have tried other ways of creating the hyperlink but this is as close as I could get.

The Test App:
Test-App

Result:
Test-Result

MainWindow.xaml

<Window x:Class="HyperlinkWithPageAnchor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="100" Width="200">
    <Grid>
        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
            <Hyperlink NavigateUri="{Binding HyperlinkNavUri}" RequestNavigate="Hyperlink_RequestNavigate">
                <TextBlock Text="Link Text"/>
            </Hyperlink>
        </TextBlock>
    </Grid>
</Window>

MainWindow.xaml.cs

namespace HyperlinkWithPageAnchor
{
    using System;
    using System.Windows;
    using System.ComponentModel;
    using System.Windows.Navigation;

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private Uri _hyperlinkNavUri;
        public Uri HyperlinkNavUri
        {
            get { return _hyperlinkNavUri; }
            set { _hyperlinkNavUri = value; OnPropertyChanged(nameof(HyperlinkNavUri)); }
        }

        public MainWindow()
        {
            InitializeComponent(); DataContext = this;

            // Desired Address: file:///C:/OSP-P/P-MANUAL/MPA/ENG/ALARM-A.HTM#1101

            UriBuilder uBuild = new UriBuilder(new Uri("file://"));
            uBuild.Path = @"C:\OSP-P\P-MANUAL\MPA\ENG\ALARM-A.HTM";
            uBuild.Fragment = "1101"; 
            HyperlinkNavUri = uBuild.Uri; 
        }

        private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            try { string link = e.Uri.ToString();  MessageBox.Show(link); System.Diagnostics.Process.Start(link); }
            catch (Exception ex) { MessageBox.Show(ex.Message); }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
    }
}

Upvotes: 2

Views: 745

Answers (1)

Arnaud Develay
Arnaud Develay

Reputation: 3970

It seems that if you let the OS identify the default browser itself, it will remove the anchor from the URI.

You need to use the following overload of Process.Start which allows to specify the executable and the parameters:

ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = @"C:\Program Files\Internet Explorer\iexplore.exe";
processStartInfo.Arguments = "file:///C:/OSP-P/P-MANUAL/MPA/ENG/ALARM-A.HTM#1101";
Process.Start(processStartInfo);

If you want to use browser defined by the user instead of an hard-coded one, you will have to query the Windows Registry in order to retrieve it.

For example on old version of windows (before Vista I think), you have to use the following registry key: HKEY_CLASSES_ROOT\http\shell\open\command. On later release, this key contains the default browser (if the browser does not made any change).

private string GetDefaultBrowser()
{
    string regKey = @"HTTP\shell\open\command";
    using (RegistryKey registrykey = Registry.ClassesRoot.OpenSubKey(regKey, false))
    {
        return ((string)registrykey.GetValue(null, null)).Split('"')[1];
    }
}

On Windows 10, it is a bit more complex due to the application launcher that allows to select the default application. To retrieve the browser chosen by the user, you have to query the following registry key: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.html\UserChoice. It the key does not exist you have to fallback on the previously mentioned key: HKEY_CLASSES_ROOT\http\shell\open\command.

private string GetDefaultBrowserOnWin10()
{
    string execPath;
    try
    {
        string extension = ".htm"; // either .htm or .html
        RegistryKey propertyBag = Registry.CurrentUser.OpenSubKey($@"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{extension}\UserChoice", false);
        var browserProgId = propertyBag.GetValue("ProgId").ToString(); ;
        using (RegistryKey execCommandKey = Registry.ClassesRoot.OpenSubKey(browserProgId + @"\shell\open\command", false))
        {
            execPath = execCommandKey.GetValue(null).ToString().ToLower().Replace("\"", "");
            if (IsDefaultLauncherApp(execPath))
            {
                System.Diagnostics.Debug.WriteLine("No user-defined browser or IE selected; anchor will be lost.");
            }
        }

        if (!execPath.EndsWith("exe"))
        {
            execPath = execPath.Substring(0, execPath.LastIndexOf(".exe") + 4);
        }
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.Message);
        execPath = GetDefaultBrowser();
    }

    return execPath;
}

private bool IsDefaultLauncherApp(string appPath)
{
    return appPath.Contains("launchwinapp.exe");
}

This will work for all browsers except Microsoft Edge, which do not allow that at the moment. You can use in you program like this:

private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
    try {
        string link = e.Uri.ToString();
        System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo();
        processStartInfo.FileName = GetDefaultBrowserOnWin10();
        processStartInfo.Arguments = link;
        System.Diagnostics.Process.Start(processStartInfo);
    } catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}

Some additional answers: How to find default web browser using C#?

Upvotes: 7

Related Questions