Archive for August 2011

How to authenticate and access the registry remotely using C#

Connecting to the remote registry of another workstation or server can be done as long as authentication has been done first.

Here is an example for connecting to remote registry.

  1. Create a new C# Console project in Visual Studio.
  2. Add a new class called NetworkShare. I know I got this from another site sometime last year and added to it, but I didn’t document the source web site.
    using System;
    using System.Runtime.InteropServices;
    
    using BOOL = System.Int32;
    
    namespace NetworkConnection
    {
        public class NetworkShare
        {
            #region Member Variables
            [DllImport("mpr.dll")]
            private static extern int WNetAddConnection2A(ref NetResource refNetResource, string inPassword, string inUsername, int inFlags);
            [DllImport("mpr.dll")]
            private static extern int WNetCancelConnection2A(string inServer, int inFlags, int inForce);
    
            private String _Server;
            private String _Share;
            private String _DriveLetter = null;
            private String _Username = null;
            private String _Password = null;
            private int _Flags = 0;
            private NetResource _NetResource = new NetResource();
            BOOL _AllowDisconnectWhenInUse = 0; // 0 = False; Any other value is True;
            #endregion
    
            #region Constructors
            /// <summary>
            /// The default constructor
            /// </summary>
            public NetworkShare()
            {
            }
    
            /// <summary>
            /// This constructor takes a server and a share.
            /// </summary>
            public NetworkShare(String inServer, String inShare)
            {
                _Server = inServer;
                _Share = inShare;
            }
    
            /// <summary>
            /// This constructor takes a server and a share and a local drive letter.
            /// </summary>
            public NetworkShare(String inServer, String inShare, String inDriveLetter)
            {
                _Server = inServer;
                _Share = inShare;
                DriveLetter = inDriveLetter;
            }
    
            /// <summary>
            /// This constructor takes a server, share, username, and password.
            /// </summary>
            public NetworkShare(String inServer, String inShare, String inUsername, String inPassword)
            {
                _Server = inServer;
                _Share = inShare;
                _Username = inUsername;
                _Password = inPassword;
            }
    
            /// <summary>
            /// This constructor takes a server, share, drive letter, username, and password.
            /// </summary>
            public NetworkShare(String inServer, String inShare, String inDriveLetter, String inUsername, String inPassword)
            {
                _Server = inServer;
                _Share = inShare;
                _DriveLetter = inDriveLetter;
                _Username = inUsername;
                _Password = inPassword;
            }
            #endregion
    
            #region Properties
            public String Server
            {
                get { return _Server; }
                set { _Server = value; }
            }
    
            public String Share
            {
                get { return _Share; }
                set { _Share = value; }
            }
    
            public String FullPath
            {
                get { return string.Format(@"\\{0}\{1}", _Server, _Share); }
            }
    
            public String DriveLetter
            {
                get { return _DriveLetter; }
                set { SetDriveLetter(value); }
            }
    
            public String Username
            {
                get { return String.IsNullOrEmpty(_Username) ? null : _Username; }
                set { _Username = value; }
            }
    
            public String Password
            {
                get { return String.IsNullOrEmpty(_Password) ? null : _Username; }
                set { _Password = value; }
            }
    
            public int Flags
            {
                get { return _Flags; }
                set { _Flags = value; }
            }
    
            public NetResource Resource
            {
                get { return _NetResource; }
                set { _NetResource = value; }
            }
    
            public bool AllowDisconnectWhenInUse
            {
                get { return Convert.ToBoolean(_AllowDisconnectWhenInUse); }
                set { _AllowDisconnectWhenInUse = Convert.ToInt32(value); }
            }
    
            #endregion
    
            #region Functions
            /// <summary>
            /// Establishes a connection to the share.
            ///
            /// Throws:
            ///
            ///
            /// </summary>
            public int Connect()
            {
                _NetResource.Scope = (int)Scope.RESOURCE_GLOBALNET;
                _NetResource.Type = (int)Type.RESOURCETYPE_DISK;
                _NetResource.DisplayType = (int)DisplayType.RESOURCEDISPLAYTYPE_SHARE;
                _NetResource.Usage = (int)Usage.RESOURCEUSAGE_CONNECTABLE;
                _NetResource.RemoteName = FullPath;
                _NetResource.LocalName = DriveLetter;
    
                return WNetAddConnection2A(ref _NetResource, _Password, _Username, _Flags);
            }
    
            /// <summary>
            /// Disconnects from the share.
            /// </summary>
            public int Disconnect()
            {
                int retVal = 0;
                if (null != _DriveLetter)
                {
                    retVal = WNetCancelConnection2A(_DriveLetter, _Flags, _AllowDisconnectWhenInUse);
                    retVal = WNetCancelConnection2A(FullPath, _Flags, _AllowDisconnectWhenInUse);
                }
                else
                {
                    retVal = WNetCancelConnection2A(FullPath, _Flags, _AllowDisconnectWhenInUse);
                }
                return retVal;
            }
    
            private void SetDriveLetter(String inString)
            {
                if (inString.Length == 1)
                {
                    if (char.IsLetter(inString.ToCharArray()[0]))
                    {
                        _DriveLetter = inString + ":";
                    }
                    else
                    {
                        // The character is not a drive letter
                        _DriveLetter = null;
                    }
                }
                else if (inString.Length == 2)
                {
                    char[] drive = inString.ToCharArray();
                    if (char.IsLetter(drive[0]) && drive[1] == ':')
                    {
                        _DriveLetter = inString;
                    }
                    else
                    {
                        // The character is not a drive letter
                        _DriveLetter = null;
                    }
                }
                else
                {
                    // If we get here the value passed in is not valid
                    // so make it null.
                    _DriveLetter = null;
                }
            }
            #endregion
    
            #region NetResource Struct
            [StructLayout(LayoutKind.Sequential)]
            public struct NetResource
            {
                public uint Scope;
                public uint Type;
                public uint DisplayType;
                public uint Usage;
                public string LocalName;
                public string RemoteName;
                public string Comment;
                public string Provider;
            }
            #endregion
    
            #region Enums
            public enum Scope
            {
                RESOURCE_CONNECTED = 1,
                RESOURCE_GLOBALNET,
                RESOURCE_REMEMBERED,
                RESOURCE_RECENT,
                RESOURCE_CONTEXT
            }
    
            public enum Type : uint
            {
                RESOURCETYPE_ANY,
                RESOURCETYPE_DISK,
                RESOURCETYPE_PRINT,
                RESOURCETYPE_RESERVED = 8,
                RESOURCETYPE_UNKNOWN = 4294967295
            }
    
            public enum DisplayType
            {
                RESOURCEDISPLAYTYPE_GENERIC,
                RESOURCEDISPLAYTYPE_DOMAIN,
                RESOURCEDISPLAYTYPE_SERVER,
                RESOURCEDISPLAYTYPE_SHARE,
                RESOURCEDISPLAYTYPE_FILE,
                RESOURCEDISPLAYTYPE_GROUP,
                RESOURCEDISPLAYTYPE_NETWORK,
                RESOURCEDISPLAYTYPE_ROOT,
                RESOURCEDISPLAYTYPE_SHAREADMIN,
                RESOURCEDISPLAYTYPE_DIRECTORY,
                RESOURCEDISPLAYTYPE_TREE,
                RESOURCEDISPLAYTYPE_NDSCONTAINER
            }
    
            public enum Usage : uint
            {
                RESOURCEUSAGE_CONNECTABLE = 1,
                RESOURCEUSAGE_CONTAINER = 2,
                RESOURCEUSAGE_NOLOCALDEVICE = 4,
                RESOURCEUSAGE_SIBLING = 8,
                RESOURCEUSAGE_ATTACHED = 16,
                RESOURCEUSAGE_ALL = 31,
                RESOURCEUSAGE_RESERVED = 2147483648
            }
    
            public enum ConnectionFlags : uint
            {
                CONNECT_UPDATE_PROFILE = 1,
                CONNECT_UPDATE_RECENT = 2,
                CONNECT_TEMPORARY = 4,
                CONNECT_INTERACTIVE = 8,
                CONNECT_PROMPT = 16,
                CONNECT_NEED_DRIVE = 32,
                CONNECT_REFCOUNT = 64,
                CONNECT_REDIRECT = 128,
                CONNECT_LOCALDRIVE = 256,
                CONNECT_CURRENT_MEDIA = 512,
                CONNECT_DEFERRED = 1024,
                CONNECT_COMMANDLINE = 2048,
                CONNECT_CMD_SAVECRED = 4096,
                CONNECT_CRED_RESET = 8192,
                CONNECT_RESERVED = 4278190080
            }
            #endregion
        }
    }
    

    Note: This class actually is to map a drive, but authenticating to a share and authenticating to connect to remote registry are done the same way.

  3. Now here is a Program.cs that shows how to use the NetworkShare class to connect to a device and access the remote registry.
    using System;
    using Microsoft.Win32;
    using NetworkConnection;
    using System.Management;
    
    namespace RemoteRegistryExample
    {
        class Program
        {
            static void Main(string[] args)
            {
                String ServerName = "Server1";
    
                // Create an object that can authenticate to a network share when you
                // already have credentials
                NetworkShare share = new NetworkShare(ServerName, "ipc$");
    
                // Create an object that can authenticate to a network share when you
                // do NOT already have credentials
                //NetworkShare share = new NetworkShare(ServerName, "C$", "User", "SomePasswd");
    
                // Connect to the remote drive
                share.Connect();
    
                // Note: another connection option is to add a reference to System.Management,
                // Then add a using statement for System.Management and use ConnectionOptions
                // and ManagementScope objects. For more information see this link:
                // http://msdn.microsoft.com/en-us/library/system.management.managementscope%28v=VS.100%29.aspx
    
                // If these same credentials allow remote registry, you are now authenticated
                // Get the Windows ProductName from HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
                String ProductName = string.Empty;
                RegistryKey key = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ServerName);
                if (key != null)
                {
                    key = key.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
                    if (key != null)
                        ProductName = key.GetValue("ProductName").ToString();
                }
    
                // Display the value
                Console.WriteLine("The device " + ServerName + " is running " + ProductName + ".");
    
                // Disconnect the share
                share.Disconnect();
            }
        }
    }
    

    You now are reading from remote registry.

Why Technical Support Engineers are not all the same!

Technical Support Engineers are not all the same. There is an inclination in the industry to look down on Technical Support Engineers.

Recently the following article was published:
10 IT positions ranked by prestige

This article didn’t exactly identify the Technical Support Engineer role, but it was unfortunately encompassed in the bottom two positions with the lowest prestige, Technical and Help Desk Analyst.

Should a Technical Support Engineer have the lowest prestige of all technical jobs in the industry? If you think so, you might want to reconsider after read this.

There are multiple levels of technical support and you should know what level of technical support a person is in because that should significantly change your view of this persons technical skills and ability.

What they support and to what level they support it makes a major difference in how to view a Technical Support Engineers background.

Obviously there is a difference between someone who does tech support for a company like Cisco, Microsoft, LANDesk than someone who does technical support for a BowFlex. But this is an obvious difference. A chart that is more of gradient is needed.

Here is some information to help guide you in determining what experience a Technical Support Engineer really has in the technology industry.

1 – Complex product that requires knowledge of an entire area of technology, including both software and hardware environments

These engineers are often not just support engineers. Along with being an expert on their product, they must understand many other concepts such as Networking, Servers and server-side software such as Web Servers, Database Servers, DNS servers, DHCP servers and more. It is not enough to just know how to set up their software, they have to know how to set up the environment around it. They also have to know how to troubleshoot to deep levels both their software and the environment around it.

Usually these engineers practice during portions of their job being Systems Analysts, Consultants, Sales Engineers, IT administrators, Change Controls administrators, developers, and more. They deal will the full gamut of technology and all the areas around it.

Example companies

Desktop Management companies such as LANDesk, SCCM, Kace.
Network Manager Software such as HP OpenView

2 – Specialized product that requires knowledge of one major portion of an IT or Software Environment

These engineers are often not just support engineers. Along with being an expert on their product, they must understand many other concepts in the technology world. It is not enough to just know how to set up their software, they have to know how to set up some portion, though not all, of the environment around it. They also have to know how to troubleshoot to deep levels both their software and their portion of the environment around it.

Like above, these engineers have some limited consulting experience and are gaining understanding of change control and IT administrative processes along with being technology experts.

Example companies

Support for Network Equipment such as Cisco, Juniper Networks, etc…

3 – Specialized product that requires knowledge of a single area of an IT or Software Environment

This person is an expert on their software or hardware product as well as an expert in one or more areas surrounding it.

Individuals who excel here are usually are ready to explode into a new technology after a little as one year in this position.

Example Companies

Business intelligence software, such as QlikView.
Dell, HP and other computer resellers.
Simple Appliances, such as a NAS.
Any company’s internal Computer Help Desk (but be aware of glorified password resetters)

4- Specialized product that requires technical knowledge but only for that exact product

This person is an expert on their software or hardware product but there is not indication they know anything else about technology from this position, which doesn’t mean they don’t, just that this position doesn’t indicate it.

Example Companies

Home consumer products such as Wireless Routers from D-Link, Linksys, etc.

5 – Specialized product that requires knowledge in an area outside of IT but still somewhat technical

This person is an expert on their software or hardware product as well as an expert in one or more areas surrounding it.
Usually this product has interfaces into other technology that not used commonly but Technical Support Engineers usually don’t take the common calls for things that just work, they learn the tough issues, which usually involves integrating with something else. However, they don’t always know that area of technology, just the minimal knowledge to make their product work with it.

Example companies

Software Applications outside of IT: Microsoft Word, Excel.

6 – Specialized product that requires knowledge in an area outside of IT but not exactly technical

This person is an expert on their software or hardware product as well as an expert in one or more areas surrounding it. This expertise is beneficial outside the position but only in limited areas.

Example companies

Software Applications outside of IT:
Adobe Photoshop
Dentrix
gaming software

7 – Specialized product that requires knowledge that is technical but not really related to software or IT at all

This person is an expert product but it is just a simple product that being an expert on it really has no value anywhere else.

Example companies

Television
Cable box companies
radios and sound systems
Cell-phones

8 – Generic product that requires little technical knowledge

This person usually supports something that is sold on a made-for-tv ad, such as an exercise appliance. They usually have a script they follow and this position can be filled by almost anyone who can read and speak.

Example companies

BowFlex
Clock Radios

C# Class to establish a network connection with credentials

using System;
using System.Runtime.InteropServices;

using BOOL = System.Int32;

namespace NetworkConnection
{
    public class NetworkShare
    {
        #region Member Variables
        [DllImport("mpr.dll")]
        private static extern int WNetAddConnection2A(ref NetResource refNetResource, string inPassword, string inUsername, int inFlags);
        [DllImport("mpr.dll")]
        private static extern int WNetCancelConnection2A(string inServer, int inFlags, int inForce);

        private String _Server;
        private String _Share;
        private String _DriveLetter = null;
        private String _Username = null;
        private String _Password = null;
        private int _Flags = 0;
        private NetResource _NetResource = new NetResource();
        BOOL _AllowDisconnectWhenInUse = 0; // 0 = False; Any other value is True;
        #endregion

        #region Constructors
        /// <summary>
        /// The default constructor
        /// </summary>
        public NetworkShare()
        {
        }

        /// <summary>
        /// This constructor takes a server and a share.
        /// </summary>
        public NetworkShare(String inServer, String inShare)
        {
            _Server = inServer;
            _Share = inShare;
        }

        /// <summary>
        /// This constructor takes a server and a share and a local drive letter.
        /// </summary>
        public NetworkShare(String inServer, String inShare, String inDriveLetter)
        {
            _Server = inServer;
            _Share = inShare;
            DriveLetter = inDriveLetter;
        }

        /// <summary>
        /// This constructor takes a server, share, username, and password.
        /// </summary>
        public NetworkShare(String inServer, String inShare, String inUsername, String inPassword)
        {
            _Server = inServer;
            _Share = inShare;
            _Username = inUsername;
            _Password = inPassword;
        }

        /// <summary>
        /// This constructor takes a server, share, drive letter, username, and password.
        /// </summary>
        public NetworkShare(String inServer, String inShare, String inDriveLetter, String inUsername, String inPassword)
        {
            _Server = inServer;
            _Share = inShare;
            _DriveLetter = inDriveLetter;
            _Username = inUsername;
            _Password = inPassword;
        }
        #endregion

        #region Properties
        public String Server
        {
            get { return _Server; }
            set { _Server = value; }
        }

        public String Share
        {
            get { return _Share; }
            set { _Share = value; }
        }

        public String FullPath
        {
            get { return string.Format(@"\\{0}\{1}", _Server, _Share); }
        }

        public String DriveLetter
        {
            get { return _DriveLetter; }
            set { SetDriveLetter(value); }
        }

        public String Username
        {
            get { return String.IsNullOrEmpty(_Username) ? null : _Username  ; }
            set { _Username = value; }
        }

        public String Password
        {
            get { return String.IsNullOrEmpty(_Password) ? null : _Username; }
            set { _Password = value; }
        }

        public int Flags
        {
            get { return _Flags; }
            set { _Flags = value; }
        }

        public NetResource Resource
        {
            get { return _NetResource; }
            set { _NetResource = value; }
        }

        public bool AllowDisconnectWhenInUse
        {
            get { return Convert.ToBoolean(_AllowDisconnectWhenInUse); }
            set { _AllowDisconnectWhenInUse = Convert.ToInt32(value); }
        }

        #endregion

        #region Functions
        /// <summary>
        /// Establishes a connection to the share.
        /// 
        /// Throws:
        /// 
        /// 
        /// </summary>
        public int Connect()
        {
            _NetResource.Scope = (int)Scope.RESOURCE_GLOBALNET;
            _NetResource.Type = (int)Type.RESOURCETYPE_DISK;
            _NetResource.DisplayType = (int)DisplayType.RESOURCEDISPLAYTYPE_SHARE;
            _NetResource.Usage = (int)Usage.RESOURCEUSAGE_CONNECTABLE;
            _NetResource.RemoteName = FullPath;
            _NetResource.LocalName = DriveLetter;

            return WNetAddConnection2A(ref _NetResource, _Password, _Username, _Flags);
        }

        /// <summary>
        /// Disconnects from the share.
        /// </summary>
        public int Disconnect()
        {
            int retVal = 0;
            if (null != _DriveLetter)
            {
                retVal = WNetCancelConnection2A(_DriveLetter, _Flags, _AllowDisconnectWhenInUse);
                retVal = WNetCancelConnection2A(FullPath, _Flags, _AllowDisconnectWhenInUse);
            }
            else
            {
                retVal = WNetCancelConnection2A(FullPath, _Flags, _AllowDisconnectWhenInUse);
            }
            return retVal;
        }

        private void SetDriveLetter(String inString)
        {
            if (inString.Length == 1)
            {
                if (char.IsLetter(inString.ToCharArray()[0]))
                {
                    _DriveLetter = inString + ":";
                }
                else
                {
                    // The character is not a drive letter
                    _DriveLetter = null;
                }
            }
            else if (inString.Length == 2)
            {
                char[] drive = inString.ToCharArray();
                if (char.IsLetter(drive[0]) && drive[1] == ':')
                {
                    _DriveLetter = inString;
                }
                else
                {
                    // The character is not a drive letter
                    _DriveLetter = null;
                }
            }
            else
            {
                // If we get here the value passed in is not valid
                // so make it null.
                _DriveLetter = null;
            }
        }
        #endregion

        #region NetResource Struct
        [StructLayout(LayoutKind.Sequential)]
        public struct NetResource
        {
            public uint Scope;
            public uint Type;
            public uint DisplayType;
            public uint Usage;
            public string LocalName;
            public string RemoteName;
            public string Comment;
            public string Provider;
        }
        #endregion

        #region Enums
        public enum Scope
        {
            RESOURCE_CONNECTED = 1,
            RESOURCE_GLOBALNET,
            RESOURCE_REMEMBERED,
            RESOURCE_RECENT,
            RESOURCE_CONTEXT
        }

        public enum Type : uint
        {
            RESOURCETYPE_ANY,
            RESOURCETYPE_DISK,
            RESOURCETYPE_PRINT,
            RESOURCETYPE_RESERVED = 8,
            RESOURCETYPE_UNKNOWN = 4294967295
        }

        public enum DisplayType
        {
            RESOURCEDISPLAYTYPE_GENERIC,
            RESOURCEDISPLAYTYPE_DOMAIN,
            RESOURCEDISPLAYTYPE_SERVER,
            RESOURCEDISPLAYTYPE_SHARE,
            RESOURCEDISPLAYTYPE_FILE,
            RESOURCEDISPLAYTYPE_GROUP,
            RESOURCEDISPLAYTYPE_NETWORK,
            RESOURCEDISPLAYTYPE_ROOT,
            RESOURCEDISPLAYTYPE_SHAREADMIN,
            RESOURCEDISPLAYTYPE_DIRECTORY,
            RESOURCEDISPLAYTYPE_TREE,
            RESOURCEDISPLAYTYPE_NDSCONTAINER
        }

        public enum Usage : uint
        {
            RESOURCEUSAGE_CONNECTABLE = 1,
            RESOURCEUSAGE_CONTAINER = 2,
            RESOURCEUSAGE_NOLOCALDEVICE = 4,
            RESOURCEUSAGE_SIBLING = 8,
            RESOURCEUSAGE_ATTACHED = 16,
            RESOURCEUSAGE_ALL = 31,
            RESOURCEUSAGE_RESERVED = 2147483648
        }

        public enum ConnectionFlags : uint
        {
            CONNECT_UPDATE_PROFILE = 1,
            CONNECT_UPDATE_RECENT = 2,
            CONNECT_TEMPORARY = 4,
            CONNECT_INTERACTIVE = 8,
            CONNECT_PROMPT = 16,
            CONNECT_NEED_DRIVE = 32,
            CONNECT_REFCOUNT = 64,
            CONNECT_REDIRECT = 128,
            CONNECT_LOCALDRIVE = 256,
            CONNECT_CURRENT_MEDIA = 512,
            CONNECT_DEFERRED = 1024,
            CONNECT_COMMANDLINE = 2048,
            CONNECT_CMD_SAVECRED = 4096,
            CONNECT_CRED_RESET = 8192,
            CONNECT_RESERVED = 4278190080
        }
        #endregion
    }
}

Hopefully I will get time to explain what is going on here.

Loading rich text file links in a browser from a WPF Navigation Application

Previously, I discussed loading a rich text file in a regular WPF Application in this post.
Loading a RichTextBox from an RTF file using binding or a RichTextFile control

UPDATE: If you use the new version of RichTextFile located at this link, then you don’t even need to do this.

The following use cases must be met:

  1. Load and display a rich text file in read only mode
  2. The links must open inside a browser and not in the application

We created a RichTextFile control that inherits RichTextBox and configured it to support binding. Now we are going to use this same object in a WPF Navigation Application.

A WPF Navigation Application is going to react differently. Links are going work by default, so the Hyperlink.Click event doesn’t have to be used. However, there is a problem, the links open in the actual application’s window and not in a browser. Lets fix this.

Part 2 – Using the RichTextFile class in a WPF Navigation Application

Use Case 1 – Loading a rich text file

Create a new WPF Application in Visual Studio.

Add a Frame element and set the source to RTF.xaml, which we will create next.

<Window x:Class="RichTextFileTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Frame Source="RTF.xaml" />
    </Grid>
</Window>

There is no code behind for this, yet.

Create a new WPF Page called RTF.xaml.

Add the above RichTextFile object to the project.

Add an xmlns reference to our new object. Then add our new object. Notice in our new object that we set IsReadOnly=”True” but we also set IsDocumentEnabled=”True”. This allows for clicking a link even though the document is read only.

<Page x:Class="RichTextFileTest.RTF"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      Title="RTF"
      xmlns:controls="clr-namespace:System.Windows.Controls">
    <Grid>
        <controls:RichTextFile File="{Binding File}" IsReadOnly="True" IsDocumentEnabled="True"/>
    </Grid>
</Page>

Code Behind

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace RichTextFileTest
{
    /// <summary>
    /// Interaction logic for RTF.xaml
    /// </summary>
    public partial class RTF : Page
    {
        public RTF()
        {
            InitializeComponent();
            Example example = new Example() { File = "File.rtf" };
            DataContext = example;
        }
    }

    public class Example
    {
        public String File { get; set; }
    }
}

Now the first use case is complete, the rich text file is loading into the RichTextFile control and is visible in the application. However, the second use case is incomplete, but not because the links aren’t loading, but because they are not loading in a browser. Instead they are loading inside the Frame element.

Use Case 2 – Getting the links to open in a browser

Getting the links to open in a browser is not straight forward and it took me quite some time to find an easy solution. Somehow, the link must be open in a browser and then the Navigation event must be canceled.

The easiest way to do this is to implement the Nagivating function on the Frame element in our MainWindow.

<Window x:Class="RichTextFileTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Frame Source="RTF.xaml" Navigating="Frame_Navigating"/>
    </Grid>
</Window>

Now implement the code behind for the Frame_Navigating method.

using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;

namespace RichTextFileTest
{
    /// <summary> 
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Frame_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            Frame frame = sender as Frame;
            if (frame != null && frame.Source != null)
            {
                // See if we are hitting a link using HTTP or HTTPS
                if (frame.Source.ToString().StartsWith("http://", System.StringComparison.CurrentCultureIgnoreCase)
                    || frame.Source.ToString().StartsWith("https://", System.StringComparison.CurrentCultureIgnoreCase))
                {
                    // Open the URL in a browser
                    Process.Start(frame.Source.ToString());

                    // Cancel the Navigation event
                    e.Cancel = true;
                }
            }
        }
    }
}

The links should now be opening in your browser and not in your application.

Here is the sample project that demonstrates this.
RichTextFileNavigation.zip

Loading a RichTextBox from an RTF file using binding or a RichTextFile control

Sometimes you have a rich text document that exists as an actual file and you simply want to load the file and display it.

My exact use cases are these:

  1. Load and display a rich text file in read only mode
  2. The links must open inside a browser and not in the application

I like to use MVVM, so my goal is to use binding to pass in the file name. Unfortunately the RichTextBox class is not designed to bind to a file name. However, extending this control to have this functionality is quite easy.

RichTextFile extending RichTextBox

Here is my new class. I have extended RichTextBox with four additional items in a new derived class called RichTextFile.

  1. Added a String Property called File.
  2. Added a DependecyProperty called FileProperty
  3. Added a PropertyChangedCallback function called OnFileChanged
  4. Added a ReadFile function that reads a .rtf file into a FlowDocument
  5. Added a Constructor that adds a handler for the Hyperlink.RequestNavigateEvent.
  6. Added a Property called OpenLinksInBrowser.
using System.Diagnostics;
using System.IO;
using System.Windows.Documents;

namespace System.Windows.Controls
{
    internal class RichTextFile : RichTextBox
    {

        public RichTextFile()
        {
            AddHandler(Hyperlink.RequestNavigateEvent, new RoutedEventHandler(HandleHyperlinkClick));
        }

        private void HandleHyperlinkClick(object inSender, RoutedEventArgs inArgs)
        {
            if (OpenLinksInBrowser)
            {
                Hyperlink link = inArgs.Source as Hyperlink;
                if (link != null)
                {
                    Process.Start(link.NavigateUri.ToString());
                    inArgs.Handled = true;
                }
            }
        }

        #region Properties
        public bool OpenLinksInBrowser { get; set; }

        public String File
        {
            get { return (String)GetValue(FileProperty); }
            set { SetValue(FileProperty, value); }
        }

        public static DependencyProperty FileProperty =
            DependencyProperty.Register("File", typeof(String), typeof(RichTextFile),
            new PropertyMetadata(OnFileChanged));

        private static void OnFileChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RichTextFile rtf = d as RichTextFile;
            if (rtf == null)
                return;

            ReadFile(rtf.File, rtf.Document);
        }
        #endregion

        #region Functions
        private static void ReadFile(string inFilename, FlowDocument inFlowDocument)
        {
            if (System.IO.File.Exists(inFilename))
            {
                TextRange range = new TextRange(inFlowDocument.ContentStart, inFlowDocument.ContentEnd);
                FileStream fStream = new FileStream(inFilename, FileMode.Open, FileAccess.Read, FileShare.Read);

                range.Load(fStream, DataFormats.Rtf);
                fStream.Close();
            }
        }
        #endregion
    }
}

You can use this new object to load a .rtf file quite easily. I am going to show you how I am succeeding in my two use cases using both a regular WPF Application and a WPF Navigation Application.

Part 1 – Using the RichTextFile class in a WPF Application

Use Case 1 – Loading a rich text file

Create a new WPF Application in Visual Studio.

Add the above RichTextFile object to the project.

Add an xmlns reference to our new object. Then add our new object. Notice in our new object that we set IsReadOnly=”True” but we also set IsDocumentEnabled=”True”. This allows for clicking a link even though the document is read only.

<Window x:Class="RichTextFileTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:controls="clr-namespace:System.Windows.Controls">
    <Grid>
        <controls:RichTextFile File="{Binding File}" IsReadOnly="True" IsDocumentEnabled="True" />
    </Grid>
</Window>

Code Behind

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Documents;

namespace RichTextFileTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Example example = new Example() { File = "File.rtf" };
            DataContext = example;
        }
    }

    public class Example
    {
        public String File { get; set; }
    }
}

Now the first use case is complete, the rich text file is loading into the RichTextFile control and is visible in the application. However, the second use case is incomplete as clicking the link does nothing.

Use Case 2 – Getting the links to open in a browser

The links can easily made to open in a browser now by simply setting OpenLinksInBrowser=”True” on the RichTextFile object.

<Window x:Class="RichTextFileTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:controls="clr-namespace:System.Windows.Controls">
    <Grid>
        <controls:RichTextFile File="{Binding File}" IsEnabled="True" IsReadOnly="True" IsDocumentEnabled="True" OpenLinksInBrowser="True"/>
    </Grid>
</Window>

The links should now be opening in your browser.

Here is the sample project that demonstrates this.
RichTextFileTest.zip

Part 2 – Using the RichTextFile class in a WPF Navigation Application