WPF - RoutedCommand vs.RoutedUICommand

|

If you have done any work with commands in WPF you would have come across both of the RoutedCommand class and its descendent RoutedUICommand. Also, you might have wondered, as I have, what the difference between them is and when you should use which.

What RoutedUICommand brings to the party is the Text property. This property allows HeaderedItemsControl descendents, such as MenuItem, to use this property as the content value for their Header property. Therefore if you intend use your command for a menu, a tab or any headered item use RoutedUICommand; otherwise use RoutedCommand.

Pugilism in .NET ?

|

No, not that kind of boxing! At the end of a recent post I asked a question on Boxing, a term used in the .NET Framework to mean the process of converting a value type to a reference type, with un-boxing be the reverse process.

From a recent post:

...notice in the IL above [ED: below in *this* post] that the two Int32 values are boxed (note the box IL instruction) before they are used in the call to WriteLine(); how could the WriteLine() statement be re-written to avoid the boxing?

This was the IL in question:

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] int32 hours,
        [1] int32 days)
    L_0000: nop
    L_0001: ldc.i4.s 0x18 // decimal value 24
    L_0003: stloc.0
    L_0004: ldsfld int32 Innovation.Program::MaxDaysOfTheWeek
    L_0009: stloc.1
    L_000a: ldstr "Hours: {0}, Days: {1}"
    L_000f: ldloc.0
    L_0010: box int32
    L_0015: ldloc.1
    L_0016: box int32
    L_001b: call void [mscorlib]System.Console::WriteLine(string, object, object)
    L_0020: nop
    L_0021: ret
}

The code that generated this IL is reproduced here to save you, dear reader, from having to flit back and forth between the two posts:

using System;
class Program
{
    public const int MaxHoursInADay = 24;
    public static readonly int MaxDaysOfTheWeek = 7;
    static void Main(string[] args)
    {
        int hours = Program.MaxHoursInADay;
        int days = Program.MaxDaysOfTheWeek;

        Console.WriteLine
            ("Hours: {0}, Days: {1}", hours, days);
    }
}

So first of all, you're probably asking:

What's the big deal, why do I want to remove the boxing operations?

Boxing can have negative performance implications for your code. However, with the introduction of Generics in .NET 2.0 a fair number of use cases that would cause boxing have gone the way of the Dodo and the ArrayList. But, when every cycle counts removing that additional overhead could help your application.

The solution is simple, but maybe a little surprising:

Console.WriteLine
    ("Hours: {0}, Days: {1}",
        hours.ToString(), days.ToString());

Eh? How does that work? Surely, to call ToString(), a method on the Object class i.e. a reference type, prevent boxing? Surely the runtime has to box the Int32 to make the call?!?

Well, no. In that sense it defies common sense. The reason is that Microsoft have provided an implementation of the ToString() method for Int32 via an override on the ToString() method. The C# compiler picks this up and because the compiler knows polymorphism cannot come into play, emits the code that calls ToString() directly; the upshot is that the value type does not have to be boxed (nice!).

Just to prove it, here's the IL output for the exact same program shown above, but this time with additional calls to the ToString() method emitted by the compiler, replacing the box instructions:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 3
.locals init (
[0]
int32 num,
[1]
int32 num2)
L_0000:
nop
L_0001:
ldc.i4.s 0x18
L_0003:
stloc.0
L_0004:
ldsfld int32 Program::MaxDaysOfTheWeek
L_0009: stloc.1
L_000a:
ldstr "Hours: {0}, Days: {1}"
L_000f:
ldloca.s num
L_0011: call instance string [mscorlib]System.Int32::ToString()
L_0016:
ldloca.s num2
L_0018: call instance string [mscorlib]System.Int32::ToString()
L_001d:
call void [mscorlib]System.Console::WriteLine(string, object, object)
L_0022:
nop
L_0023:
ret
}


And that is the simple, albeit surprising answer. If you have any other surprising little tips like this I would love to hear from you.

WPF - ListBox, ItemsPanelTemplate and a WrapPanel

|

The ListBox is a very versatile control in WPF, when you want to have some kind of list selection, regardless of the actual item layout, you'll probably opt for a ListBox with a custom ItemsTemplate. Due to this versatility I've used the ListBox control quite a bit in my WPF applications, and a common task I find myself doing is hacking replacing the ItemsPanelTemplate.

This is the panel that houses and arranges all the child items that make up the list. For example, you might want a horizontal list rather than the default vertical list. This a simple task, as shown below:

<ListBox>
    <ListBox.ItemsPanel>
       
<ItemsPanelTemplate>
            <VirtualizingStackPanel
                Orientation="Horizontal"
                IsItemsHost="True" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.Items>
        <TextBlock Text="One" />
        <TextBlock Text="Two" />
        <TextBlock Text="Three" />
        <TextBlock Text="Four" />
    </ListBox.Items>
</ListBox>

The VirtualizingStackPanel is the default panel for the ListBox, so I'm using the same control here, but changing the value of the Orientation property to Horizontal. Job done.

Another common panel "trick" I like to do is to swap out the VirtualizingStackPanel for a WrapPanel. This gives me item selection in a WrapPanel, similar to the Windows Explorer experience when you're looking at your files in list mode. At first, this might appear to be a simple task:

<ItemsPanelTemplate>
    <WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>

Lovely, however, things start getting icky when you want the items to actually wrap - which kind of the point of using a WrapPanel in the first place!! Grrrr! Below is a screen-shot showing the problem.

The items are clearly not wrapping. This is because the Width property of the panel is set to Auto, the default value, meaning infinite width. The other issue is that further up the chain in the visual tree is a ScrollContentPresenter. The implications of that are if a width value is not explicitly set for the contained control, the WrapPanel in this case, it will be allowed to be as wide as it needs to be to display all of its content. The ScrollContentPresenter will then show a scroll bar if the width is larger than the viewable area. Drat and double drat!

One approach could be to remove the ScrollContentPresenter from the visual tree, but that involves replacing the ListBox's control template, which is a lot of hard work just to have the items wrap in the panel.

So what we really want to do is stop the ScrollContentPresenter from thinking that the WrapPanel is bigger than it is, which will stop the scrollbars AND ensure that the WrapPanel has a finite width, making it wrap. We can achieve both goals with a "simple" binding expression in the ItemsPanelTemplate defined for our WrapPanel:

<ItemsPanelTemplate x:Key="VideoItemsPanelTemplate">
    <WrapPanel
         IsItemsHost="True"
         Width="{Binding
                  Path=ActualWidth,
                  RelativeSource={RelativeSource
                      Mode=FindAncestor,
                      AncestorType=
                         {x:Type ScrollContentPresenter}}}"
/>
</ItemsPanelTemplate>

Now, I know that this is pretty funky looking syntax; especially if you've not done too much data binding. But quite simply, all I'm doing here is setting the Width of the WrapPanel to the value of the ActualWidth property on the ScrollContentPresenter, and I find the ScrollContentPresenter by walking up the visual tree.

See, simple(?)

On question you might have is why not just use the RelativeSource of TemplatedParent? The answer, to your great question, is that the parent of the WrapPanel is an ItemsPresenter not the ScrollContentPresenter,  as shown below with an image taken from XAMLPad; which suffers from the same problem as the WrapPanel, in that it can have infinite width and will cause the scroll bars to appear.

I'm sure that there are other solutions to the WrapPanel issue described in this post and if you have one I would love to hear from you.