Visual Studio 2010 help

If you have installed Visual Studio 2010 and look for a decent help functionality, then you will probably be just as disappointed as I was. The new help system is back to the stone ages, because it doesn’t have a decent index or search functionality. Most people, including me, revert to Google to access the online MSDN system.

Fortunately, the API is much richer than the GUI that shows the help and Rob Chandler created Help Viewer for VS 2010 and it looks quite promising:

Help Viewer for VS 2010

Microsoft created a great product with VS 2010 and the .NET framework, so I don’t understand the philosophy behind this help viewer. Didn’t the team have enough time to create a decent help viewer or is it something else?

Organizing Generic.xaml

Creating controls in WPF seems difficult at first. The separation between code and the appearance of the control is one of the great WPF features, but it requires a different way of thinking about controls. The code is located in its own folder and namespace, but the control default style is defined in the Generic.xaml file (or one of its themed cousins) that is located in the project’s Themes directory.

Although the behaviour (code) and its appearance (style) are completely separated, you still work on both of them at the same time. So it would be convenient to have these files close together. Another problem of the Generic.xaml file is that it tends to get very large, when you have a lot of (complex) controls. Fortunately, there is a good way to solve this issue.

Default styles need to be defined in the Theme\Generic.xaml (or one of its themed cousins). Nothing you can do about that, but you can make the generic resource dictionary a merged dictionary that loads other dictionaries that you can put on arbitrary places.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/RamonDeKlein.Controls;component/AsyncLoadControl/AsyncLoadControl.xaml"/>
        <ResourceDictionary Source="/RamonDeKlein.Controls;component/AnimatedSizeControl/AnimatedSizeControl.xaml"/>
        <!-- include other definitions here -->
    </ResourceDictionary.MergedDictionaries>

</ResourceDictionary>

You can create separate resource dictionaries for your controls and put them in the same folder as your code’s control. Make sure you set the build action for the resource dictionary to ‘Resource’.

A drawback of this solution is that you need to specify the assembly name for each resource dictionary that you want to merge. This is a flaw in WPF when using merged dictionaries in Generic.xaml.

TabControl’s ContentTemplate cannot be changed dynamically

TabControl bug exampleI recently used a TabControl and required a different ContentTemplate per item, so I used datatriggers to change the ContentTemplate based on the current selected page. In theory this should work fine, but I found that changes to the ContentTemplate are not effectuated.

How to reproduce?
Use the following XAML code to illustrate the problem. The property trigger based on the ‘IsMouseOver’ property changes the content template, when you hover over it. This should make the text to be displayed in bold, but it doesn’t show until you change the page.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <Page.Resources>

    <DataTemplate x:Key="TabHeaderTemplate">
      <TextBlock Text="{Binding XPath=@header}"/>
    </DataTemplate>

    <DataTemplate x:Key="TabContent1">
      <TextBlock FontWeight="Normal" Text="{Binding XPath=@header}"/>
    </DataTemplate>

    <DataTemplate x:Key="TabContent2">
      <TextBlock FontWeight="Bold" Text="{Binding XPath=@header}"/>
    </DataTemplate>

    <Style x:Key="DynamicTabControl" TargetType="TabControl">
      <Setter Property="ContentTemplate" Value="{StaticResource TabContent1}"/>
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="ContentTemplate" Value="{StaticResource TabContent2}"/>
        </Trigger>
      </Style.Triggers>
    </Style>

    <XmlDataProvider x:Key="tabPages" XPath="/pages">
      <x:XData>
        <pages xmlns="">
          <page header="Page 1"/>
          <page header="Page 2"/>
          <page header="Page 3"/>
        </pages>
      </x:XData>
    </XmlDataProvider>

  </Page.Resources>

  <TabControl ItemsSource="{Binding Source={StaticResource tabPages}, XPath=*}"
              ItemTemplate="{StaticResource TabHeaderTemplate}"
              Style="{StaticResource DynamicTabControl}"/>

</Page>

What’s the cause?
I was wondering what was happening, so I started my favorite .NET Reflector tool and found out that the ContentTemplate is set in code instead of a binding. The following piece of code takes care of setting the content template and horizontal/vertical alignment (which will probably suffer from the same problem):

private void TabControl.UpdateSelectedContent()
{
  // Check if a tab is selected
  if (base.SelectedIndex < 0)
  {
    this.SelectedContent = null;
    this.SelectedContentTemplate = null;
    this.SelectedContentTemplateSelector = null;
    this.SelectedContentStringFormat = null;
  }
  else
  {
    // Obtain the selected tab item
    TabItem selectedTabItem = this.GetSelectedTabItem();
    if (selectedTabItem != null)
    {
      // Some keyboard stuff removed here...

      // Set the content
      this.SelectedContent = selectedTabItem.Content;

      // Obtain the content presenter for the selected item
      ContentPresenter selectedContentPresenter = this.SelectedContentPresenter;
      if (selectedContentPresenter != null)
      {
        // Set horizontal/vertical alignment
        selectedContentPresenter.HorizontalAlignment = selectedTabItem.HorizontalContentAlignment;
        selectedContentPresenter.VerticalAlignment = selectedTabItem.VerticalContentAlignment;
      }

      // Check if the selected tab item defines the content template
      if ((selectedTabItem.ContentTemplate != null) ||
          (selectedTabItem.ContentTemplateSelector != null) ||
          (selectedTabItem.ContentStringFormat != null))
      {
        // Use the content settings from the tab item
        this.SelectedContentTemplate = selectedTabItem.ContentTemplate;
        this.SelectedContentTemplateSelector = selectedTabItem.ContentTemplateSelector;
        this.SelectedContentStringFormat = selectedTabItem.ContentStringFormat;
      }
      else
      {
        // Use the content settings from the tab control
        this.SelectedContentTemplate = this.ContentTemplate;
        this.SelectedContentTemplateSelector = this.ContentTemplateSelector;
        this.SelectedContentStringFormat = this.ContentStringFormat;
      }
    }
  }
}

As you can see, the ContentTemplate is set directly, so updating it after this method has completed doesn’t do anything until the method is invoked again. This method is a private method that is called when one of the following events happen:

  • A new template is applied to the TabControl (TabControl.OnApplyTemplate).
  • Another tab page is selected (TabControl.OnSelectionChanged).
  • TabControl’s ItemGenerator status is updated (TabControl.OnGeneratorStatusChanged).

Changes made to the TabControl’s ContentTemplate will therefore only be propagated, when one of these events occur. If you change the ContentTemplate afterwards, then you are out of luck.

How to fix it?
The best way to solve this issue is to use binding, because the binding mechanism will ensure that all properties are updated properly. Unfortunately, you cannot do this, because some of the required properties are read-only and cannot be modified from your code.

The only way to fix it is to override the property metadata for these dependency properties and make sure that the TabControl.UpdateSelectedContent() method is invoked when the dependency property is changed. You cannot call this method directly, but you can call it by calling the public TabControl.OnApplyTemplate() method. The same fix should be made for the TabItem, because if the TabItem has its own template, then this template is used.

I have created a sample application that includes the MyTabControl and MyTabItem classes that contain this workaround.
Tab Control bug example

Publish SABnzbd via Bonjour

If you have AVAHI installed on your FreeBSD box, then create a file called sabnzbd.service in the /usr/local/etc/avahi/services directory. This will publish the server via Bonjour, so you can access it easily using a Bonjour enabled browser, such as Safari.

The sabnzbd.service file should have the following content:

<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">

<!-- See avahi.service(5) for more information about this configuration file -->

<service-group>

  <name replace-wildcards="yes">SABnzbd [%h]</name>

  <service>
    <type>_http._tcp</type>
    <port>8080</port>
    <txt-record>path=/queue</txt-record>
  </service>

</service-group>

Make sure you change the port number if you don’t use the standard SABnzbd port.

Install SABnzbd on FreeBSD

FreeBSD comes with an extensive ports collection that contains SABnzbd. Unfortunately, even in the latest FreeBSD 8.1 release, it contains SABnzbd v0.4.12 which is old and lacks a lot of cool features. There wasn’t a clear guide on how to upgrade SABnzbd to the latest version, so I decided to find out how it can be done.

What has changed?
I started out with the v0.4.12 port, but I have updated the Makefile, distinfo, so it will download the latest version of SABnzbd (currently v0.5.3). Because the new version uses a different set of files, the pkg-plist file has been updated to match the files in v0.5.3.

The v0.4.12 distribution installed some files in the Python directory, which I don’t like. It’s not a part of Python, so it shouldn’t install files in the Python directories. All SABnzbd related files are now stored in its own directory. On most installations this will be /usr/local/shared/sabnzbdplus. Even the SABnzbd.py file that is used to run the application is now stored inside this directory. A symbolic link will be created during installation from /usr/local/bin to make sure the program can be found.

Two patches are necessary to the SABnzbd.py file. First Python must be told to first look for packages in the SABnzbd directories, because SABnzbd comes with its own version of CherryPy. Another change is that the SABnzbd directory will be hardcoded to the correct path. The original version tries to detect the path automatically, but this fails when you start it from /usr/local/bin.

The last addition that I made was to include an RC-script that allows that SABnzbd can be started as a daemon during system startup.

How to install SABnzbd
The following steps should be performed to download and install SABnzbd on your FreeBSD installation:

  1. Log on with root privileges on your FreeBSD server.
  2. Go to the ports directory.
  3. Fetch the latest version of my distribution.
  4. Unpack the archive and remove the archive itself.
  5. Change to the new SABnzbd directory.
  6. Install the new version and clean up.

In code this looks like:

[root@freebsd /]# cd /usr/ports/news
[root@freebsd /usr/ports/news]# wget http://blog.ramondeklein.nl/wp-content/uploads/2010/07/sabnzbdplus-0.5.3.tar.gz
[root@freebsd /usr/ports/news]# tar -xvzf sabnzbdplus-0.5.3.tar.gz
[root@freebsd /usr/ports/news]# rm sabnzbdplus-0.5.3.tar.gz
[root@freebsd /usr/ports/news]# cd sabnzbdplus-0.5.3
[root@freebsd /usr/ports/news/sabnzbdplus-0.5.3]# make install && make clean

Now you can start SABnzbd by typing SABnzbd.py. Go to http://your-freebsd-server:8080 to check if the server is running correctly. Refer to the SABnzbd manuals for more information about configuring SABnzbd itself.

Read the next paragraphs if you want to run SABnzbd as a daemon with more restrictive privileges.

How to run SABnzbd as a daemon
Add the following lines to your /etc/rc.conf file to make sure it starts during system startup:

# SABnzbd settings
sabnzbd_enable="YES"

The default daemon runs as root, but it is better to run SABnzbd under a less-privileged account. Add the following lines to your /etc/rc.conf file to make sure the daemon runs under the ‘ramon’ account:

# SABnzbd settings
sabnzbd_enable="YES"
sabnzbd_user="ramon"

You don’t need to restart the system to test if the daemon can be started. Type the following command to start the SABnzbd daemon:

/usr/local/etc/rc.d/sabnzbd start

Stopping the daemon can be done using the following command:

/usr/local/etc/rc.d/sabnzbd stop

Always make sure you stop the daemon before you make any changes to the configuration files by hand.

Known issues

  • Make sure you run SABnzbd.py with root privileges at least once. This enables that the compiled versions can be stored. Other users probably don’t have the required privileges to store these files.
  • During deinstallation the compiled Python files are not removed. This result in some error messages. You can delete these files yourself when you deinstall the distribution.

SABnzbd distributions
The following list contains the SABnzbd distributions that I have created: