Wednesday, 20 October 2010

Converting WPF UserControls to Controls with a theme

I was working on this application and so I found it easy to create some controls as UserControl classes. However, I realised that if I wish to centralize the styling of the application or even move some of the controls in their own control library with a Themes folder, I would need to transform them into Control classes.

I found that there are two major problems that must be overcome:
  1. the named controls in a user control can be accessed as fields in the code behind; a theme style does not allow such direct access to the elements in the control template.
  2. the controls that expose an event may not expose an associated command. In a user control a simple code behind handler method can be attached to a child control event, but in a theme a command must be available in order to be bound.


Granted, when the first problem is solved, it is easy to attach events in the code of the control to the child elements, but this presents two very ugly problems: the template of the control will need to contain the child elements in question and the event will not be declared in the theme, so the behaviour would be fixed as well.

I will solve the handling of events as commands by using a solution from Samuel Jack. The article is pretty detailed, but to make the story short, one creates an attached property for each of the handled events by using a helper class:

public static class TextBoxBehaviour
{
public static readonly DependencyProperty TextChangedCommand =
EventBehaviourFactory.CreateCommandExecutionEventBehaviour(
TextBox.TextChangedEvent, "TextChangedCommand", typeof (TextBoxBehaviour));

public static void SetTextChangedCommand(DependencyObject o, ICommand value)
{
o.SetValue(TextChangedCommand, value);
}

public static ICommand GetTextChangedCommand(DependencyObject o)
{
return o.GetValue(TextChangedCommand) as ICommand;
}
}
then by using a very simple syntax on the control that fires the event:

<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}"/>


The problem regarding access to the elements in the template is solved by reading the elements by name from the template. In some situations, like when one uses a control as a source for a data control (like using a TreeView as the first item in a ComboBox), the approach will have to be more complicated, but considering the element is stored in the template of the control, something like this replaces the work that InitializeComponent does inside a UserControl:

[TemplatePart(Name = "PART_textbox", Type = typeof (TextBox))]
public class MyThemedControl : Control, ITextControl
{
private TextBox textbox;

public override void OnApplyTemplate()
{
base.OnApplyTemplate();
textbox = Template.FindName("PART_textbox", this) as TextBox;
}
...

The code is pretty straight forward: use the FrameworkTemplate.FindName method to find the elements in the OnApplyTemplate override and remember them as fields that you can access. The only weird part is the use of the TemplatePartAttribute, which is not mandatory for this to work, but is part of a pattern recommended by Microsoft. Possibly in the future tools will check for the existence of named elements in the templates and compare them against the ones declared in the control source.

The code of a demo project can be downloaded here.

Some other technologies I have used in the project:
  • the RelayCommand class, to make it easier to defined ICommand objects from code without declaring a type for each.
  • the AccessKeyScoper class that allows an IsDefault button to act locally in a panel.

Replacing BitmapEffect in WPF 4 as they are now obsolete

If you google the net for using visual effects in WPF you are very likely to hit BitmapEffects. Well, bad news, BitmapEffect is obsolete and broken in WPF4. Instead you have Effect. The idea is to write (or download) a custom Effect for the things one would normally do using the slow (but easy to use) BitmapEffects. Also, the BitmapEffectGroup doesn't work anymore and has no alternative in WPF4. Bummer! According to Dr. WPF, the framework will try to automatically translate the older BitmapEffects to the new ones. That applies for BlurBitmapEffect and for DropShadowBitmapEffect with a Noise level of 0, for the others... you are on your own.

There were 5 default BitmapEffect classes in WPF3:
  1. BlurBitmapEffect - the WPF4 alternative is BlurEffect
  2. OuterGlowBitmapEffect - the WPF4 alternative in the Microsoft article is BlurEffect, but I have found that it can be replaced by DropShadowEffect with ShadowDepth set to 0
  3. DropShadowBitmapEffect - the WPF4 alternative is DropShadowEffect
  4. BevelBitmapEffect - the WPF4 alternative is a custom class inheriting from Effect.
  5. EmbossBitmapEffect - the WPF4 alternative is a custom class inheriting from Effect.


I found this project when googling to a simpler way of building ShaderEffects: WPF ShaderEffect Generator. Also, a discussion about a Bevel ShaderEffect here led me to this WPF shader library, but it's last release date is somewhere in March 2009.

Monday, 18 October 2010

Tab order navigation in WPF

I am using this WPF control that looks like a headered panel. On the header there is a menu, a button and then, underneath, some content that could be anything. I had this request that, when using the Tab key to navigate, the focus should first come to the button, which is not the first control in the header, then jump to the controls in the content and then jump back to the menu. In other words, to make the menu the last controls that can be reached via the Tab key inside this WPF control.

Well, in WPF there is this class called KeyboardNavigation which has some very useful attached properties: TabIndex (which defaults to int.MaxValue) and TabNavigation (which defaults to Continue). As in Windows Forms, one needs to set the TabIndex to control the navigation order, but the TabNavigation property makes it a lot more versatile and clear as well. In my case, I had to do the following:
  1. Set the entire panel control to KeyboardNavigation.TabNavigation=Local. That allowed me to control the tab index inside the control, otherwise the TabIndex value would have been global to the window
  2. Set the TabIndex of the button to 1. That made the button the first thing to be selected in case I use Tab to navigate in the panel
  3. Set the TabIndex of the content to 2. This set the controls in the content as the next controls to be accessed via the Tab key
  4. Set the TabNavigation value of the content to Local. Without this, the TabIndex value would have made no sense. First of all the content panel has IsTabStop to false and, second, any control inside it would have int.MaxValue set as TabIndex which would work globally in the main control.


This example should make it clear how to use the KeyboardNavigation properties. There is one more, called ControlTabNavigation, which has a misleading name and an ambiguous description. It is not related to a control, the Control in the name comes from the Ctrl key. In WPF one can use Ctrl-Tab to navigate an alternative order thus allowing, in this case, to go directly to the menu, then the button and then the controls in the content area.

Friday, 15 October 2010

Explicitly use Path when binding to Attached properties

I was trying to use a static class of attached properties that I would use inside my WPF control templates, so that every time I need to change them I would only use a setter, not copy paste the entire template and make minute changes. It worked great and I recommend this solution to everybody. So after the refactoring of my control I was shocked to see that in the production application, all my attached properties were throwing binding errors!

The solution was to explicitly use the Path= syntax in the bindings.

{Binding Path=(namespace:ClassName.Property)...
not
{Binding (namespace:ClassName.Property)...

The strange thing is that in the test application everything worked great, so what went different? Apparently the problem is that IXamlTypeResolver (used in PropertyPath) is not finding namespaces unless already registered, as explained here by Rob Relyea. The dude with that Lovecraftian name is the program manager for the WPF & Xaml Language Team.

So if one uses the namespace of the attached property before in some other context or by using the verbose syntax before, it works even with the Path keyword omitted. Not really sure why the verbose syntax works differently though.

Thursday, 14 October 2010

Determining if a TextBlock has been trimmed in WPF

Many a time I want that textblocks that get trimmed to display their text as a tooltip. For that, I would make a trigger to set the Tooltip value to the Text value if the textblock has been trimmed. The problem is that there is no property that shows if this is the case.

Googling a bit, I found this article, which apparently works, but also has some problems, as reported by many commenters. Even if it would have worked perfectly, my scenario is too simple to need all that code.

The idea of the original post is simple and I like it, the problem being that convoluted method that computes the width of the text in the textblock. Why would I want to redo all the work that is being done by the framework itself? The width of a textblock with TextWrapping NoWrap should be textBlock.Measure(Size(double.MaxValue,double.MaxValue)). I am sure a more complex scenario can also be serviced by this method, if the height is taken from the TextBlock, but I don't need it.

So here is the entire class, using my measuring method:
public class TextBlockService
{
static TextBlockService()
{
// Register for the SizeChanged event on all TextBlocks, even if the event was handled.
EventManager.RegisterClassHandler(typeof (TextBlock),
FrameworkElement.SizeChangedEvent,
new SizeChangedEventHandler(OnTextBlockSizeChanged),true);
}

public static readonly DependencyPropertyKey IsTextTrimmedKey =
DependencyProperty.RegisterAttachedReadOnly(
"IsTextTrimmed",
typeof (bool),
typeof (TextBlockService),
new PropertyMetadata(false)
);

public static readonly DependencyProperty IsTextTrimmedProperty =
IsTextTrimmedKey.DependencyProperty;

[AttachedPropertyBrowsableForType(typeof (TextBlock))]
public static Boolean GetIsTextTrimmed(TextBlock target)
{
return (Boolean) target.GetValue(IsTextTrimmedProperty);
}

public static void OnTextBlockSizeChanged(object sender, SizeChangedEventArgs e)
{
TextBlock textBlock = sender as TextBlock;
if (null == textBlock)
{
return;
}
textBlock.SetValue(IsTextTrimmedKey, calculateIsTextTrimmed(textBlock));
}

private static bool calculateIsTextTrimmed(TextBlock textBlock)
{
double width = textBlock.ActualWidth;
if (textBlock.TextTrimming == TextTrimming.None)
{
return false;
}
if (textBlock.TextWrapping != TextWrapping.NoWrap)
{
return false;
}
textBlock.Measure(new Size(double.MaxValue, double.MaxValue));
double totalWidth = textBlock.DesiredSize.Width;
return width < totalWidth;
}
}


This would be used with a trigger, as I said at the beginning of the post:
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Style.Triggers>
<Trigger Property="Controls:TextBlockService.IsTextTrimmed" Value="True">
<Setter Property="ToolTip" Value="{Binding Text,RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>

Thursday, 7 October 2010

A problem occurred while trying to set the "Sources" parameter for the IDE's in-process compiler

Today a power outage screwed something in my Visual Studio installation. For the life of me I couldn't figure out what went wrong and I also didn't have the time to properly investigate. The issue appeared when I restarted the computer, ran Visual Studio 2010, loaded a project (any project) and tried to compile. Invariably, an error would prevent me from building the project:Error 22 A problem occurred while trying to set the "Sources" parameter for the IDE's in-process compiler. Error creating instance of managed object 'Microsoft.VisualStudio.CSharp.Services.Language.Features.EditAndContinue.EncState' (Error code is 0x80131604). Application.

I have tried disabling Edit and Continue, I've tried to disable the Exception Assistant and also I've re-registered the dlls in the Visual Studio 10.0/Common7/IDE folder, all to no avail. Even worse, it seemed as I am the only person on Google (yay!) that got this error so I couldn't find a quick no effort solution (boo!). The error code 0x80131604 stands for HRESULT COR_E_TARGETINVOCATION, or a TargetInvocationException, which is thrown by methods invoked through reflection. So that is that.

The solution was to reinstall Visual Studio. It took half a day, but it fixed it. If you have met the same issue and you found a quicker way, please leave a comment.

Sunday, 3 October 2010

Melissa Auf Der Maur is out of her mind; Linkin Park turn Emo

I was posting a while ago about the first album from Hole's bassist, Melissa Auf Der Maur. I found that the music had a haunting femaleness in it and sounded pretty cool. She has recently released her second album Out of Our Minds and my opinion is... that they were! The songs are lame, like a really wattered out version of the first. Lady, if you don't feel like it, wait until the muse graces you with her presence, don't just write crap because you have a deadline. (Let me do that, ahem...)

Moving on to Linkin Park. A refreshing mixture of hip hop and rock, their first albums (Hybrid Theory and Meteora) made them famous. If you don't count the collection of remixes of their previous songs, their third album, Minutes to Midnight, was released after some time had passed (period during which Mike Shinoda was writing hip hop like crazy and the other guy... well, nobody knows what he did), had some environmental messages, some slow music, maybe some Michael Jacksony save the world songs... It pretty much sucked, but it was also ok. I mean, if you want to mellow down a little, just to try it on, why not? So now I got reminded of them when I accidentally saw Transformers and the theme of the film was sang by Linkin Park and called New Divide. It sounded kind of cool, something that resembled their first albums, so I got their latest album, A Thousand Suns, and tried it on. Long story short: it sucked. There were some cool songs, like Wretches and Kings, or Blackout, but overall, it was a whiny piece of crap. Dude! It's called ROCK, you're letting a Japanese hip hopper make you look like an emo kid trying to sing for the highschool prom.

Ok, now for some of the better songs on these albums:

Melissa auf der Maur - Out of Our Minds



Linkin Park - Wretches and Kings