views:

644

answers:

3

We have a WPF application that displays text containing the various corporate symbols; such as trademark, registered trademark, copyright, and service mark.

The database has some fields that include the standard corporate symbols. Initially, the data was marked as follows:

Example Corp(TM) or Example Plan (SM)

We can easily change the placeholders to their respective Unicode equivalents; and actually have in most cases.

The problem we have is that the font used by the application doesn't support the Service Mark symbol (which is just a superscripted SM). Chances are we can not replace the font or edit it.

The fields could be a simple product name with the symbol at the end, or a long description with a symbol contained 0 or more times. We bind TextBoxes or Labels directly to a ViewModel and/or the business object (usually through DataTemplates). All the data in the application is read-only.

So, assuming we have to tackle this via code (in C# and WPF), what are my options?

A: 

If your display element support multiple fonts like a RichTextbox control, then you can format the string with the appropriate fonts just around the (TM) and (SM) symbols.

Raj More
I think you just restated the problem.
SergioL
+2  A: 

Edit: I discovered in a different answer that the TextBlock also has an Inlines collection to which one can add Runs. Anvaka's answer ingeniously uses an attached property as a sort of converter.


I had a couple ideas about how to approach this. Only one would handle word wrap correctly and handle character runs with different fonts.

Use a FlowDocumentScrollViewer and bind the string to it using a ValueConverter that converts the string to a FlowDocument.

<FlowDocumentScrollViewer
    HorizontalScrollBarVisibility="Hidden"
    VerticalScrollBarVisibility="Hidden"
    Document="{Binding MyString, Converter={StaticResource MyConverter}}" />

You can create properties on the converter to set the regular Font properties, which can't be set on the FlowDocumentScrollViewer and has to be set on the FlowDocument that the converter creates. You'll probably also need some Font properties for the exception substrings that need a different Font (and possibly different size). Another option is to create Bindings on the FlowDocument for some of those properties (RelativeSource).

And here's how you create a FlowDocument in code:

FlowDocument doc = new FlowDocument();
doc.FontFamily = new FontFamily( "Our Own Font" );
Paragraph par = new Paragraph();
doc.Blocks.Add( par );

Then you'll need to split the incoming string on the special substrings while keeping those substrings intact. You'll have to roll your own splitter and have a collection of substrings in the converter or supplied to the converter.

Add a normal substring to the paragraph:

Run r = new Run( substring );
par.Inlines.Add( r );

Add a special substring to the paragraph with a different Font:

Run r = new Run( substring );
r.FontFamily = new FontFamily( "Arial" );
par.Inlines.Add( r );

The above are just little snippets. I don't know how you want to approach splitting the string or iterating over the substrings since I'm not familiar with the data, so I didn't supply the method I slapped together just to see if my idea would work. You could also use a Dictionary in order to detect one substring and use a replacement in the output, such as detecting "(SM)" and replacing it with "℠".

Let me know if you have any questions or if there's anything I can elaborate on.

(It's good that you said it is read-only. A RichTextBox would not work because its Document property is not a DependencyProperty and therefore can't be the target of a Binding. Although, one might be able to use Mode=OneWayToSource to reverse it, implementing ConvertBack instead of Convert.)


"I think what you left out (iterating over the string and creating those Runs) is the tricky part."

I was indeed very brief about splitting the string on the special substrings. When I said, "I don't know how you'll split the string," I was not saying that I had no idea how to do it at all (I rephrased it), but that I had no idea how you would want to handle it. I assumed it would not be hard for you to figure that part out because it's the kind of string manipulation problem I would have potential hires figure out. That, and you may discover edge cases amongst your data that require changes in how you handle it.

I'll describe to you a relatively crude version using IndexOf() and Substring().

So here's the problem-within-the-problem: You have many strings (e.g., "Company Name(R), makers of Product(TM)") that contain 0 or more special substrings. These substrings are few and known, and the input string needs to be split into several strings where the special and nonspecial substrings have been isolated from each other (e.g., {"Company Name", "(R)", ", makers of Product", "(TM)"}).

The special substrings are few and known, so you need an array of those. You know by the return value of .IndexOf() whether it found a substring or not. Looping over the known special substrings, you can find the first instance of any of them with an index comparison, setting aside the length of the substring, too.

Each time you find the earliest special substring in string S (if any), you make derived strings A, B, and C. B is the special substring, A and C are the before and after. Append A and B to a List and C becomes the new S and you do it again. Except when you find no special substrings in S and it isn't empty, in which case you just append it whole.

Now every odd-indexed string in the List is a special substring. You may want to refer to my mention of a Dictionary in the previous portion of this answer for use as a lookup to add a Run of "℠" when the substring you found was "(SM)".

Joel B Fant
Breaking the text into Run elements is probably the way to go; however, that's only part of the solution. I think what you left out (iterating over the string and creating those Runs) is the tricky part.
SergioL
I'm going to mark this as the correct answer, even though I was hoping for something a little more elegant. In actuality, it was a combination of Anvaka's answer and the brute-force string manipulation that I ended up implementing.
SergioL
Sounds good to me. Picking apart strings is always inelegant, in my opinion. The only thing that would tidy it up - simply by transferring the complexity from one place to another - would be to pick apart the string with Regex.
Joel B Fant
A: 

A somewhat ugly way to do it, but one option might be to place small images containing the pre-rendered symbol in appropriate places. Since the data isn't static (if I understand correctly), you'd need to calculate the position for the images dynamically, which would be difficult unless the font is fixed-width.

Dataflashsabot