Custom Hyperlink Control for Windows(Windows 8.1, Windows Phone 8.1)

Ended up building a user control which behaves similarly in both Windows and Windows Phone environments. With this you’ll get the underline effect, stating of default color as well as the press color.
Designer code


<UserControl x:Class="MyApp.Architecture.Controls.CustomHyperlink" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:MyApp.Architecture.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Background="YellowGreen">
    <HyperlinkButton x:Name="innerHyperlinkCtrl" FontFamily="Segoe WP Light">
        <HyperlinkButton.Content>
            <TextBlock FontFamily="Segoe UI" x:Name="inlineText">
					<Underline>
						<Run x:Name="ulRHlink"/>
					</Underline>
            </TextBlock>
        </HyperlinkButton.Content>
    </HyperlinkButton>
</UserControl>

C# code


public sealed partial class CustomHyperlink : UserControl
{
	private HyperlinkButton innerCtrl;
	private TextWrapping textWrapping = TextWrapping.WrapWholeWords;

	#region Constructors

	public CustomHyperlink()
	{
		this.InitializeComponent();
		this.Loaded += CustomHyperlink_Loaded;
		this.InnerCtrl = innerHyperlinkCtrl;

		this.DefaultColor = "#000000";
		this.PressedColor = "#40ff00";
	}

	#endregion

	#region Getters, Setters

	public HyperlinkButton InnerCtrl
	{
		get { return this.innerCtrl; }
		private set { this.innerCtrl = value; }
	}

	#endregion

	#region Events

	void CustomHyperlink_Loaded(object sender, RoutedEventArgs e)
	{
		this.InnerCtrl.Style = (this.Style == null) ? GetDefaultStyle() : this.Style;
		this.inlineText.TextWrapping = this.textWrapping;

		this.InnerCtrl.VerticalAlignment = this.VerticalAlignment;
		this.InnerCtrl.HorizontalAlignment = this.HorizontalAlignment;
		this.InnerCtrl.FontSize = this.FontSize;
		this.InnerCtrl.FontWeight = this.FontWeight;
		this.InnerCtrl.Margin = this.Margin;
	}

	#endregion

	#region Dependency Properties

	public static readonly DependencyProperty LinkTextProperty = DependencyProperty.Register("LinkText", typeof(string), typeof(CustomHyperlink), new PropertyMetadata(null, new PropertyChangedCallback(OnContentChanged)));
	public string LinkText
	{
		get { return (string)GetValue(LinkTextProperty); }
		set { SetValue(LinkTextProperty, value); }
	}

	private static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
	{
		CustomHyperlink mhl = sender as CustomHyperlink;
		mhl.ulRHlink.Text = (e.NewValue != null) ? e.NewValue.ToString() : string.Empty;
	}

	public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register("TextWrapping", typeof(TextWrapping), typeof(CustomHyperlink), null);
	public TextWrapping TextWrapping
	{
		get { return (TextWrapping)GetValue(TextWrappingProperty); }
		set 
		{ 
			SetValue(TextWrappingProperty, value);
			this.inlineText.TextWrapping = value;
		}
	}

	public static readonly DependencyProperty DefaultColorProperty = DependencyProperty.Register("DefaultColor", typeof(string), typeof(CustomHyperlink), null);
	public string DefaultColor
	{
		get { return (string)GetValue(DefaultColorProperty); }
		set { SetValue(DefaultColorProperty, value); }
	}

	public static readonly DependencyProperty PressedColorColorProperty = DependencyProperty.Register("PressedColor", typeof(string), typeof(CustomHyperlink), null);
	public string PressedColor
	{
		get { return (string)GetValue(PressedColorColorProperty); }
		set { SetValue(PressedColorColorProperty, value); }
	}

	public static readonly DependencyProperty NavigateUriProperty = DependencyProperty.Register("NavigateUri", typeof(Uri), typeof(CustomHyperlink), null);
	public Uri NavigateUri
	{
		get { return (Uri)GetValue(NavigateUriProperty); }
		set 
		{ 
			SetValue(NavigateUriProperty, value); 
			this.InnerCtrl.NavigateUri = value; 
		}
	}

	#endregion

	#region Default Style

	private Style GetDefaultStyle()
	{
		StringBuilder styleBuilder = new StringBuilder();
		styleBuilder.Append("<Style xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" 
                      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" x:Key=\"CustomHyperlinkButtonStyle\" TargetType=\"HyperlinkButton\">");           
			styleBuilder.Append("<Setter Property=\"Template\">");
				styleBuilder.Append("<Setter.Value>");
					styleBuilder.Append("<ControlTemplate TargetType=\"HyperlinkButton\">");
						styleBuilder.Append("<Grid>");
						styleBuilder.Append("<VisualStateManager.VisualStateGroups>");
							styleBuilder.Append("<VisualStateGroup x:Name=\"CommonStates\">");
								styleBuilder.Append("<VisualState x:Name=\"Normal\">");
									styleBuilder.Append("<Storyboard x:Name=\"HyperLinkSBNormal\">");
										styleBuilder.Append("<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=\"Foreground\" Storyboard.TargetName=\"ContentPresenter\">");
											styleBuilder.Append("<DiscreteObjectKeyFrame KeyTime=\"0\" Value=\"" + DefaultColor + "\"/>");
										styleBuilder.Append("</ObjectAnimationUsingKeyFrames>");
									styleBuilder.Append("</Storyboard>");
								styleBuilder.Append("</VisualState>");
								styleBuilder.Append("<VisualState x:Name=\"Pressed\">");
									styleBuilder.Append("<Storyboard x:Name=\"HyperLinkSBPressed\">");
										styleBuilder.Append("<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=\"Foreground\" Storyboard.TargetName=\"ContentPresenter\">");
											styleBuilder.Append("<DiscreteObjectKeyFrame KeyTime=\"0\" Value=\"" + PressedColor + "\"/>");
										styleBuilder.Append("</ObjectAnimationUsingKeyFrames>");
									styleBuilder.Append("</Storyboard>");
								styleBuilder.Append("</VisualState>");
								styleBuilder.Append("<VisualState x:Name=\"Disabled\">");
									styleBuilder.Append("<Storyboard x:Name=\"HyperLinkSBDisabled\">");
										styleBuilder.Append("<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=\"Foreground\" Storyboard.TargetName=\"ContentPresenter\">");
											styleBuilder.Append("<DiscreteObjectKeyFrame KeyTime=\"0\" Value=\"{ThemeResource HyperlinkDisabledThemeBrush}\"/>");
										styleBuilder.Append("</ObjectAnimationUsingKeyFrames>");
									styleBuilder.Append("</Storyboard>");
								styleBuilder.Append("</VisualState>");
							styleBuilder.Append("</VisualStateGroup>");
						styleBuilder.Append("</VisualStateManager.VisualStateGroups>");
						styleBuilder.Append("<ContentPresenter x:Name=\"ContentPresenter\" AutomationProperties.AccessibilityView=\"Raw\" ContentTemplate=\"{TemplateBinding ContentTemplate}\" ContentTransitions=\"{TemplateBinding ContentTransitions}\" Content=\"{TemplateBinding Content}\" HorizontalAlignment=\"{TemplateBinding HorizontalContentAlignment}\" VerticalAlignment=\"{TemplateBinding VerticalContentAlignment}\">");
						styleBuilder.Append("</ContentPresenter>");
						styleBuilder.Append("</Grid>");
					styleBuilder.Append("</ControlTemplate>");
				styleBuilder.Append("</Setter.Value>");
			styleBuilder.Append("</Setter>");
		styleBuilder.Append("</Style>");

		return (Style)XamlReader.Load(styleBuilder.ToString());
	}

	#endregion
}

Typical Usage (XAML)


<custctrl:CustomHyperlink x:Name="GoogleLink" HorizontalAlignment="Stretch" VerticalAlignment="Center" FontSize="14" Margin="0,20,0,0" NavigateUri="www.google.com" LinkText="Link to Google"/>

Typical Usage(C# class)


CustomHyperlink customHyperlink = new CustomHyperlink();
customHyperlink.Name = standardControlName;
customHyperlink.DefaultColor = "#000000";
customHyperlink.PressedColor = "#40ff00";
customHyperlink.HorizontalAlignment = HorizontalAlignment.Left;
customHyperlink.VerticalAlignment = VerticalAlignment.Center;
customHyperlink.LinkText = "Hello There";

Happy coding…. 🙂

Custom Hyperlink Control for Android

I developed a custom control to mimic the Hyperlink behavior as Android doesn’t have a built-in/native Hyperlink Control.

Implementation

public class CustomHyperlink extends TextView {

    private int mDefaultColor = Color.BLACK;
    private int mPressedColor = Color.GREEN;

    //region #Constructors

    public CustomHyperlink(Context context) {
        super(context);

        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE)
                    setTextColor(mPressedColor);
                if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_OUTSIDE
                        || event.getAction() == MotionEvent.ACTION_CANCEL) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    setTextColor(mDefaultColor);
                }
                return false;
            }
        });
    }

    public CustomHyperlink(Context context, AttributeSet attrs) {
        super(context, attrs);
        setCustomAttrValues(context, attrs);

        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE)
                    setTextColor(mPressedColor);
                if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_OUTSIDE
                        || event.getAction() == MotionEvent.ACTION_CANCEL) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    setTextColor(mDefaultColor);
                }
                return false;
            }
        });
    }

    public CustomHyperlink(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setCustomAttrValues(context, attrs);

        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE)
                    setTextColor(mPressedColor);
                if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_OUTSIDE
                        || event.getAction() == MotionEvent.ACTION_CANCEL) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    setTextColor(mDefaultColor);
                }
                return false;
            }
        });
    }

    //endregion

    //region #Getters, Setters

    public int getDefaultColor() {
        return mDefaultColor;
    }

    public void setDefaultColor(int defaultColor) {
        this.mDefaultColor = defaultColor;
    }

    public int getPressedColor() {
        return mPressedColor;
    }

    public void setPressedColor(int pressedColor) {
        this.mPressedColor = pressedColor;
    }

    //endregion

    //region #Methods

    public void setLinkText(String value) {
        if (MetrixStringHelper.isNullOrEmpty(value)) return;

        SpannableString content = new SpannableString(value);
        content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
        setText(content);

        setTextColor(mDefaultColor);j
    }

    public void setLinkText(String value, int start, int end) throws Exception {
        if (MetrixStringHelper.isNullOrEmpty(value)) return;

        if (end > value.length())
            throw new Exception(AndroidResourceHelper.getMessage("EndValIsGreaterThan"));

        SpannableString content = new SpannableString(value);
        content.setSpan(new UnderlineSpan(), start, end, 0);
        setText(content);

        setTextColor(mDefaultColor);
    }

    public String getLinkText() {
        return this.getText().toString();
    }

    private void setCustomAttrValues(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomHyperlinkAttr);
		if (typedArray != null) {
			//region #setLinkText
			String s = typedArray.getString(R.styleable.CustomHyperlinkAttr_linkText);
			if (!MetrixStringHelper.isNullOrEmpty(s))
				setLinkText(s);
			//endregion
		}
    }

    //endregion
}

I hope you have some basic understanding about the android custom attributes. You can declare your own attributes in a xml file such as attrs.xml and it can be placed inside res\values\ folder.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomHyperlinkAttr">
        <attr name="linkText" format="string"></attr>
    </declare-styleable>
</resources>

Usage (Inside a XML layout)


<com.architecture.utilities.MetrixHyperlink
ndroid:id="@+id/web_url"
attr:linkText="www.abc.com"
android:gravity="center_horizontal"/>

Don’t forget to add the following line into the top most layout.
xmlns:attr=”http://schemas.android.com/apk/res-auto&#8221;

Ex:


<?xml version="1.0" encoding="utf-8"?> <LinearLayout style="@style/LinearBase.Normal.Vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns="http://schemas.android.com/tools"
xmlns:attr="http://schemas.android.com/apk/res-auto">

Usage (Inside a Class)


CustomHyperlink webUrlHyperLink = (CustomHyperlink) findViewById(R.id.web_url);
webUrlHyperLink.setLinkText("www.xyz.com");

Happy coding…. 🙂