Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF Resources.resx string with hyperlink?

Tags:

c#

wpf

xaml

I want to have a string resource, which contains a hyperlink in it. I guess this isn't possible, unless I had 4 resource strings:

Pre-hyperlink text hyperlink href hyperlink text Post-hyperlink text.

Then construct it in the xaml via:

<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right">
    <TextBlock Grid.Column="1" Text="{x:Static p.Resources.PreHText}" />
    <Hyperlink Grid.Column="1" NavigateUri="{x:Static p.Resources.HHref}">
    <TextBlock Text="{x:Static p.Resources.HText}" /></Hyperlink></TextBlock>
    <TextBlock Grid.Column="1" Text="{x:Static p.Resource.PostHText}" />
</StackPanel>

Which is just awful, for many many reasons (Styling, not very dynamic etc etc). Bar creating my own render and string format, such as "Please email {[email protected]|the helpdesk} for further assistance". Is there any other way to achieve this? (Doesn't have to use the resources.resx file)

like image 218
Psytronic Avatar asked Jan 28 '11 13:01

Psytronic


1 Answers

In the end I just made my own textblock control for it (Imaginitively named AdvancedTextBlock):

    public class AdvancedTextBlock : TextBlock {
        new private String Text { get; set; } //prevent text from being set as overrides all I do here.
        private String _FormattedText = String.Empty;
        public String FormattedText {
            get { return _FormattedText; }
            set { _FormattedText = value; AssignInlines(); }
        }
        private static Regex TagRegex = new Regex(@"\{(?<href>[^\|]+)\|?(?<text>[^}]+)?}", RegexOptions.Compiled);

        public AdvancedTextBlock() : base() { }
        public AdvancedTextBlock(System.Windows.Documents.Inline inline) : base(inline) { }

        public void AssignInlines(){
            this.Inlines.Clear();
            Collection<Hyperlink> hyperlinks = new Collection<Hyperlink>();
            Collection<String> replacements = new Collection<String>();
            MatchCollection mcHrefs = TagRegex.Matches(FormattedText);
            foreach (Match m in mcHrefs) {
                replacements.Add(m.Value);
                Hyperlink hp = new Hyperlink();
                hp.NavigateUri = new Uri(m.Groups["href"].Value);
                hp.Inlines.Add(m.Groups["text"].Success ? m.Groups["text"].Value : m.Groups["href"].Value);
                hp.RequestNavigate += new RequestNavigateEventHandler(hp_RequestNavigate);
                hyperlinks.Add(hp);
            }
            String[] sections = FormattedText.Split(replacements.ToArray(), StringSplitOptions.None);
            hyperlinks.DefaultIfEmpty(null);
            for (int i = 0, l = sections.Length; i < l; i++) {
                this.Inlines.Add(sections.ElementAt(i));
                if (hyperlinks.ElementAtOrDefault(i) != null) {
                    this.Inlines.Add(hyperlinks[i]);
                }
            }
        }

        void hp_RequestNavigate(object sender, RequestNavigateEventArgs e) {
            RequestNavigate(sender, e);
        }

        //
        // Summary:
        //     Occurs when navigation events are requested.
        public event RequestNavigateEventHandler RequestNavigate;
    }

The only two things I'm not too happy about with my implementation is that:

A) I had to hack-hide the existing Text property, because I didn't know how to prevent that property from overriding the stuff I do

B) (Related to A) I have to call AssignInlines everytime the FormattedText field is set (which should only be once admittidly), but this is again, down to not knowing how to hook into the method which actually does the displaying stuff (Expecting to find a PreRender, Render event or similar, however I could not), so if anyone knows how to, that'd be awesome :).

like image 177
Psytronic Avatar answered Oct 16 '22 12:10

Psytronic