Main | February 2008 »

January 22, 2008

Using Process.Start to link to the Internet

The easiest way to navigate the user's default Internet browser to a specified URL is to call System.Diagnostics.Process.Start(string).

Process.Start("http://www.logos.com/");

The MSDN documentation seems inaccurate on a few points. It suggests that this method should only be called from an STA thread, which isn't true – it will create a new STA thread and run from there if necessary. Worse, a comment in the example suggests that this method doesn't work with a URL to launch an Internet browser – obviously, it works just fine.

We have found that, in some circumstances, with some browsers, Process.Start will raise an exception, even though it might actually succeed, so we wrap the call in a try/catch block and hope for the best.

try

{

    Process.Start("http://www.logos.com/");

}

catch (Exception)

{

}

Interestingly, when used to launch the default Internet browser in this way, Process.Start "waits" until the Internet browser is displayed before returning. I must use quotation marks here, because when Process.Start is called from a UI thread (e.g., the main STA thread of a WPF application), it pumps messages while it waits, which means that the user can still interact with the application, and events like mouse clicks and keystrokes will be processed by the application while it is "waiting". So, make sure that you don't do any work after calling Process.Start that could fail due to user activity in the meantime. Process.Start is probably being called from an event handler, so the best thing to do after calling Process.Start is nothing else.

One thing that bothers me about most applications that link to the Internet is that the Internet browser can take a while to appear. You click on a link, but nothing happens, and you wonder if you actually clicked the link, so you click again, and ultimately end up opening the Web site twice. In a WPF application, a nice way to deal with this problem is to set Mouse.OverrideCursor to Cursors.AppStarting while Process.Start is running. (Fortunately, since Process.Start pumps messages, it doesn't "hang" the application while you wait.)

try

{

    Mouse.OverrideCursor = Cursors.AppStarting;

    Process.Start("http://www.logos.com/");

}

catch (Exception)

{

}

finally

{

    Mouse.OverrideCursor = null;

}

Wrap that up in a utility method and you're good to go.

Posted by Ed Ball at 9:09 AM | Comments (2) | TrackBack

January 17, 2008

Hyperlinks to the Web in WPF

The obvious choice for implementing a link to a Web site in WPF is the Hyperlink class. Note, however, that the Hyperlink class is a FrameworkContentElement, which means it doesn't know how to render itself, but must be hosted by a TextBlock or a FlowDocument viewer like FlowDocumentScrollViewer or RichTextBox. So the easiest way to display a hyperlink is to wrap it in a TextBlock.

<TextBlock>

    <Hyperlink NavigateUri="http://code.logos.com/blog/">Blog</Hyperlink>

</TextBlock>

If your application is hosted in Internet Explorer (XBAP or loose XAML), the browser will be navigated accordingly. If the Hyperlink is in the Page of a NavigationWindow or Frame, that window or frame will be navigated.

The Hyperlink doesn't work at all from a standalone Window, however. Lauren Lavoie discusses the workaround -- your application needs to intercept the navigation and use Process.Start with the URL to launch the default browser. The best way to do that is to handle the Hyperlink.RequestNavigate routed event on the Hyperlink or one of its ancestors. (You could also just handle the Hyperlink.Click event, but Lauren's solution seems better.) And the easiest way to do that is with an attached dependency property:

<TextBlock lbx:HyperlinkUtility.LaunchDefaultBrowser="True">

    <Hyperlink NavigateUri="http://code.logos.com/blog/">Blog</Hyperlink>

</TextBlock>

And how is that dependency property implemented? We have some really cool utility code for registering dependency properties in a type-safe manner, but I'll use traditional methods for now:

public static class HyperlinkUtility

{

    public static readonly DependencyProperty LaunchDefaultBrowserProperty = DependencyProperty.RegisterAttached("LaunchDefaultBrowser", typeof(bool), typeof(HyperlinkUtility), new PropertyMetadata(false, HyperlinkUtility_LaunchDefaultBrowserChanged));

 

    public static bool GetLaunchDefaultBrowser(DependencyObject d)

    {

        return (bool) d.GetValue(LaunchDefaultBrowserProperty);

    }

 

    public static void SetLaunchDefaultBrowser(DependencyObject d, bool value)

    {

        d.SetValue(LaunchDefaultBrowserProperty, value);

    }

 

    private static void HyperlinkUtility_LaunchDefaultBrowserChanged(object sender, DependencyPropertyChangedEventArgs e)

    {

        DependencyObject d = (DependencyObject) sender;

        if ((bool) e.NewValue)

            ElementUtility.AddHandler(d, Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler(Hyperlink_RequestNavigateEvent));

        else

            ElementUtility.RemoveHandler(d, Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler(Hyperlink_RequestNavigateEvent));

    }

 

    private static void Hyperlink_RequestNavigateEvent(object sender, RequestNavigateEventArgs e)

    {

        Process.Start(e.Uri.ToString());

        e.Handled = true;

    }

}

But wait, what is ElementUtility? I guess I couldn't avoid our utility code entirely – ElementUtility.AddHandler calls UIElement.AddHandler, ContentElement.AddHandler, or UIElement3D.AddHandler, depending on the type of the element. Similarly with ElementUtility.RemoveHandler. This dependency property should be supported on the TextBlock (a UIElement) or the Hyperlink (a ContentElement), so we need to support both types of elements. The implementation of AddHandler and RemoveHandler is left as an exercise for the reader.

I also want to talk more about Process.Start, but that will have to wait until my next post.

Posted by Ed Ball at 10:52 AM | Comments (1) | TrackBack

January 14, 2008

Introductions

Hello, my name is Jacob Carpenter. I'm another software developer here at Logos, and this is my obligatory introduction post:

My first "job" for Logos, back in 1999, involved cleaning up latitude/longitude data for the world time application, ActiveEarth (web archive). I was paid in pizza and carbonated beverages, good wages for a high school student.

Following graduation, I was hired full-time to the Electronic Text Development (ETD) department working for Eli and Rick. My programming experience at the time was limited to typing in BASIC programs from the back of Boys' Life magazine. Despite my "limited experience," they taught me regular expressions and Perl.

I've grown a lot as a developer—and have learned many more languages—since then, and I look forward to sharing some of the great code we write here at Logos. I also maintain a blog at http://jacobcarpenter.wordpress.com/.

Posted by Jacob Carpenter at 12:29 PM | Comments (0) | TrackBack

Null-propagating extension method

Brad Wilson recently discovered that C# extension methods work on null references. We also discovered this interesting feature while developing a null-propagating extension method that we call IfNotNull:

public static class IfNotNullExtensionMethod

{

    public static U IfNotNull<T, U>(this T t, Func<T, U> fn)

    {

        return t != null ? fn(t) : default(U);

    }

}

Since this is an extension method on T, where T has no constraints, it is always available to any class or struct instance, much like Equals and GetHashCode. Therefore, we put it in its own namespace so that it only appears when requested.

You'll notice that the IfNotNull method is only an obscure way to call the specified delegate on the instance, except that we check to see whether the instance is null, and don't call the delegate in that case, returning null (or the default value in the case of a struct).

How do we use this method? Well, suppose you have an item with a Parent property and you want to get the Name of its parent.

string parentName = item.Parent.Name;

But suppose that item might be null, or the item's parent might be null, and you'd like the name variable to be null in either case.

string parentName = null;

if (item != null)

{

    Item parent = item.Parent;

    if (parent != null)

        parentName = parent.Name;

}

Or, more concisely:

Item parent = item == null ? null : item.Parent;

string parentName = parent == null ? null : parent.Name;

It would be nice to eliminate the temporary 'parent' variable, but we'd have to call the Parent property twice:

string parentName = item == null ? null : item.Parent == null ? null : item.Parent.Name;

The IfNotNull extension method allows us to eliminate the temporary 'parent' variable without calling the Parent property twice:

string parentName = item.IfNotNull(x => x.Parent).IfNotNull(x => x.Name);

The use of lambda expressions makes using IfNotNull the least efficient of all of the possibilities, so only use it when you believe the increased code clarity outweighs the performance loss.

Some programmers may legitimately feel that it doesn't improve code clarity at all. This was the best we could come up with in lieu of the ?. operator proposed by Miral, which would allow this:

string parentName = item?.Parent?.Name;

Update: Renamed the extension method from "To" to "IfNotNull"; the new name makes the semantics of the method clearer. We still have a To extension method, but it's even simpler:

public static U To<T, U>(this T t, Func<T, U> fn)

{

    return fn(t);

}

What possible use could this method have? Eliminating temporary variables. Here's a simple example:

IList<int> list = BuildListOfNumbers();

int nFirstOrDefault = list.Count == 0 ? 0 : list[0];

becomes

int nFirstOrDefault =

    BuildListOfNumbers().To(list => list.Count == 0 ? 0 : list[0]);

For what it's worth.

Posted by Ed Ball at 8:30 AM | Comments (1) | TrackBack

January 11, 2008

Data binding in a FlowDocument or Text Block

One of the great features of WPF is the flow content model for text. The TextBlock element can be used to display inline elements that allow bold, italic, hyperlinks, etc. The FlowDocument content element can represent entire documents of rich content -- paragraphs, tables, figures, etc. FlowDocumentScrollViewer and similar elements can be used to display a FlowDocument.

However, one of the most annoying things about working with the flow content model is the poor support for data binding. In particular, the Text property of the Run element is not a dependency property and thus is not bindable, which means that simple data-bound placeholders in the content aren't feasible.

<!-- this doesn't work -->

<TextBlock>Name: <Run Text="{Binding Name}" />

</TextBlock>

A common workaround for this problem is to replace the Run with a TextBlock, since the Text property of a TextBlock is bindable. This workaround is unsatisfying, however, because the text in the TextBlock does not wrap properly with surrounding text, selection doesn't work properly, etc.

<!-- this isn't ideal -->

<TextBlock>Name: <TextBlock Text="{Binding Name}" />

</TextBlock>

A better workaround is provided by Filipe Fortes and Paul Stovell. Filipe proposes a BindableRun class that derives from Run and provides a BoundText dependency property that sets the Text property whenever it changes. Paul proposes an attached dependency property BindableText that has the same effect. We use a similar attached property at Logos, but here's an example of Filipe's solution:

<!-- this usually works -->

<TextBlock>Name: <bt:BindableRun BoundText="{Binding Name}" />

</TextBlock>

Either solution is great -- except for the error. In comments to both Filipe's and Paul's posts, as well as in an MSDN forum post, you'll find that users of this technique sometimes encounter a confusing error: Collection was modified; enumeration operation may not execute.

A reply to the MSDN forum post by Ifeanyi Echeruo provides a workaround for the error -- delay the setting of the Text property by using Dispatcher.BeginInvoke. This workaround is effective, but it can result in annoying flickering -- the user will probably be able to see the text of the Run change from the empty string to the bound value.

Fortunately, we've found an improved workaround. Here's the short version: set the DataContext property on the BindableRun as follows (but read the update below):

<!-- this always works -->

<TextBlock>Name: <bt:BindableRun BoundText="{Binding Name}"

    DataContext="{Binding DataContext, RelativeSource=

    {RelativeSource AncestorType=FrameworkElement}}" />

</TextBlock>

The error occurs when the binding is updated because of a change to an inherited dependency property. The most common scenario is when the inherited DataContext changes. I don't have access to the WPF source code, but, based on the call stack, it appears that an inherited properly like DataContext is propagated to its descendants. When the enumeration of descendants gets to the BindableRun, the BindableText properly changes according to the new DataContext, which sets the Run property. However, for some reason, changing the flow content invalidates the enumeration and raises an exception.

Direct binding doesn't cause the enumeration of descendants. So, to avoid the error, don't allow your binding to depend on any inherited dependency properties. The most commonly used inherited dependency property is the DataContext, which is used implicitly by data bindings that don't specify an explicit source. You can avoid using the inherited DataContext by setting the Source or RelativeSource on the binding. Easier still, you can set the DataContext of the BindableRun directly, which updates your DataContext without relying on the enumeration described above. The very simplest way to solve the problem, then, as shown above, is to bind the DataContext to that of its FrameworkElement ancestor, which is outside of the flow content and thus doesn't update while the flow content is being enumerated.

Update: In some circumstances, the use of RelativeSource for the DataContext binding can result in the DataContext being null when the UI element is first displayed, only to be corrected after a moment. To avoid this behavior, bind directly to the ancestor by name.

<!-- this works even better -->

<TextBlock x:Name="tb">Name: <bt:BindableRun

    BoundText="{Binding Name}"

    DataContext="{Binding DataContext, ElementName=tb}}" />

</TextBlock>

We also experienced printing problems when using the RelativeSource solution, so I highly recommend the ElementName solution.

Posted by Ed Ball at 12:35 PM | Comments (3) | TrackBack

January 4, 2008

Welcome to code.logos.com!

The software developers at Logos Bible Software have decided to start a blog here at code.logos.com. Our target audience is other software developers; we get a lot of value from reading the blogs of other software developers, so we hope to return the favor in some small way. (Along the way, we're also hoping to convince you that Logos is a great place to work; check out http://www.logos.com/jobs for openings!)

My name is Ed Ball. I have been a software developer at Logos since 1995, not including my summer internship in 1994. Over the years, we've used a wide variety of computer languages and technologies. We've got experience with C++, JavaScript, C#, SQL, and even Perl. We've created products with Win32, MFC, ATL, HTML, ASP.NET, Windows Forms, and, most recently, WPF and WCF. Since most of our development is currently using C# and the latest .NET Framework technologies, I imagine that many of our posts will reflect that.

We've got lots of code to write, so this won't be the busiest blog you're subscribed to, but we hope to post enough to make it worth your while. Stay tuned!

Posted by Ed Ball at 11:06 AM | Comments (0) | TrackBack