Musings of a PC

Thoughts about Windows, TV and technology in general

Screen flexibility in Windows

For a while, I’ve been using my Surface Pro 2 with an external monitor, with the Surface beneath the monitor. In Windows, I’ve had the Surface’s screen to the left of the monitor and I’ve “trained” myself that if I want to move something from the monitor “down” to the Surface, I have to move it to the left.

Today, I added a 2nd external monitor, daisy-chained with Display Pro, and then moved the SP2 so that it sits underneath both of them, like this:

WP_20140709_001

I then opened the Screen resolution dialog and started dragging the SP’s screen across so that it would sit between the two external monitors. As I did, I realised you could alter the vertical position of the screen relative to the two monitors. In doing so, I discovered that you can literally match how the monitors are laid out:

Capture

I’ve now got to retrain my muscle memory so that it moves content in the physical direction of the screens rather than how I remembered the logical layout, but I am really impressed that this is possible!

(Probably obvious to most, but sometimes it is the little things that can make a big difference!)

 

Fixing the default Windows 8 XAML ItemContainerStyle

One of the things I love about the look of Windows 8 is the little touches of feedback as you navigate around. For example, here is the Messaging icon on the Start screen:

Messaging

If you hover over it on a mouse-based system, you get a little extra border to hint that you can click on it:

Messaging-2

and if you select the icon, you get a different border with an earmarked tick:

Messaging-3

Sweet! So what happens if you try to use this in your own app? Here are the screenshots of a button using the Standard500x130Template in a GridView:

Button

Button-2

Button-3

Now, I don’t consider myself to be a designer but these look pretty ugly to me! If you look at the Standard500x130Template, though, it doesn’t really hint at how it can be made prettier to get it to look more like the Messaging button.

The solution lies with ItemContainerStyle. This is a block of XAML that you can reference from, say, a GridView and it defines what an item is going to look like when it goes through different visual states. The first thing you have to do is use Blend to capture a copy of the default definition by using these steps:

  • Start a new project of type XAML (Windows Store). Select Grid App (XAML).
  • Blend should, by default, open GroupedItemsPage.xaml. If it doesn’t, open it.
  • On the Objects and Timeline pane, open up Grid so that you can see the items defined in the Grid.
  • Right-click on itemGridView and select Edit Additional Templates > Edit Generated Item Container > Edit a Copy:

EditAdditionalTemplate

  • In the window that appears (below), accept the defaults and click on OK:

CreateStyleResource

  • Switch Blend to code view and you should find a style has been added called GridViewItemStyle1. You can copy & paste this either into a central style XAML file for your project or, if only needed on a single page, directly into the Resources section of that page’s XAML.

(If you are struggling with the above steps, you can find the original XAML at the end of this post)

I’ve already mentioned that I’m not a designer, so I don’t pretend to fully understand VisualState or StoryBoard but I’ll work through some of the key elements as I understand them and explain how the rendering of the items works.

Let’s start with what happens when you position the pointer over an item. We’ve seen from the screenshots that a big grey rectangle appears. This happens because the VisualState PointerOver contains the following line:


<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PointerOverBorder"/>

If you look further down the XAML, you will see that this is a rectangle with a fill colour of ListViewItemPointerOverBackgroundThemeBrush. What the above animation does is switch the opacity so that it becomes visible.

So why is it a big grey rectangle and not a nice border like with the Messaging icon? The answer lies with another rectangle which, by default, is only shown when the item is selected! This is the SelectionBackground rectangle and it is filled with ListViewItemSelectedBackgroundThemeBrush. I’ll come back to this in a minute.

Now, let’s look in more detail at what happens when the item is selected. This is another VisualState called, sensibly enough, Selected. This changes the opacity of SelectionBackground, SelectedBorder and SelectedCheckMark so that they become visible. We’ve already looked at SelectionBackground, so what about SelectedBorder and SelectedCheckMark?

SelectedBorder is another rectangle that gets drawn around the item with a stroke colour of ListViewItemSelectedBackgroundThemeBrush so that you get a thin, unfilled rectangle.

SelectedCheckMark is a grid consisting of two paths – one drawn with a fill colour of ListViewItemSelectedBackgroundThemeBrush and the other drawn with a fill colour of ListViewItemCheckThemeBrush. The latter is the tick and the former is the corner triangle that is drawn around the tick.

So, all of the pieces of the puzzle are there. Why, then, doesn’t the item get drawn even remotely like the Messaging icon? Let’s look at the Selected visual state again. It causes three items to be displayed but there is a problem with this – the SelectionBackground, as defined in the style, is filled with the same colour as the SelectedBorder and the corner triangle in SelectedCheckMark. That is why the border and triangle don’t show up.

After a bit of thinking about this, I made a couple of changes that actually ended up fixing all of the above problems, so I’ll just share my changes:

  • Change the fill colour of SelectionBackground so that it uses a different colour from ListViewItemSelectedBackgroundThemeBrush. In my case, because my items are being drawn on a white background, I fixed the colour at white … you’ll see why in a minute.
  • Change the opacity of SelectionBackground from 0 to 1 so that it is always visible.

Why make the last change? It turns out that the reason you get a big grey rectangle instead of a nice border hint is because the selection background isn’t visible – remember that I mentioned it only gets switched on when the item is selected? So by forcing it to be visible all of the time, that changes the appearance of that big grey rectangle to a nice border hint.

Here are the screenshots after revising the style:

PointerOver

Selected

Much better!

I don’t know or understand why the default style is defined the way it is, and it may be that the changes I’ve made aren’t the optimal ones but they do seem to get my app to be closer to those released by Microsoft. Once you’ve defined the style, all you need to do is reference it in the GridView or ListView with ItemContainerStyle=”{StaticResource <whatever you called it>}”.

Original GridView item style:

<Style x:Key="GridViewItemStyle1" TargetType="GridViewItem">
<Setter Property="FontFamily" Value="{StaticResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="{StaticResource ControlContentThemeFontSize}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="TabNavigation" Value="Local"/>
<Setter Property="IsHoldingEnabled" Value="True"/>
<Setter Property="Margin" Value="0,0,2,2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewItem">
<Border x:Name="OuterContainer">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PointerOverBorder"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="SelectionBackground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedPointerOverBackgroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Stroke" Storyboard.TargetName="SelectedBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedPointerOverBorderThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="SelectedEarmark">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedPointerOverBackgroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<PointerDownThemeAnimation TargetName="ContentContainer"/>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOverPressed">
<Storyboard>
<PointerDownThemeAnimation TargetName="ContentContainer"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PointerOverBorder"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="SelectionBackground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedPointerOverBackgroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Stroke" Storyboard.TargetName="SelectedBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedPointerOverBorderThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="SelectedEarmark">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedPointerOverBackgroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To="{StaticResource ListViewItemDisabledThemeOpacity}" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="contentPresenter"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisual"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused"/>
<VisualState x:Name="PointerFocused"/>
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionHintStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.65" To="NoSelectionHint"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="VerticalSelectionHint">
<Storyboard>
<SwipeHintThemeAnimation ToHorizontalOffset="0" TargetName="SelectionBackground" ToVerticalOffset="15"/>
<SwipeHintThemeAnimation ToHorizontalOffset="0" TargetName="ContentBorder" ToVerticalOffset="15"/>
<SwipeHintThemeAnimation ToHorizontalOffset="0" TargetName="SelectedBorder" ToVerticalOffset="15"/>
<SwipeHintThemeAnimation ToHorizontalOffset="0" TargetName="SelectedCheckMark" ToVerticalOffset="15"/>
<DoubleAnimationUsingKeyFrames Duration="0:0:0.500" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="HintGlyph">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0.5"/>
<DiscreteDoubleKeyFrame KeyTime="0:0:0.500" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="HorizontalSelectionHint">
<Storyboard>
<SwipeHintThemeAnimation ToHorizontalOffset="-23" TargetName="SelectionBackground" ToVerticalOffset="0"/>
<SwipeHintThemeAnimation ToHorizontalOffset="-23" TargetName="ContentBorder" ToVerticalOffset="0"/>
<SwipeHintThemeAnimation ToHorizontalOffset="-23" TargetName="SelectedBorder" ToVerticalOffset="0"/>
<SwipeHintThemeAnimation ToHorizontalOffset="-23" TargetName="SelectedCheckMark" ToVerticalOffset="0"/>
<DoubleAnimationUsingKeyFrames Duration="0:0:0.500" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="HintGlyph">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0.5"/>
<DiscreteDoubleKeyFrame KeyTime="0:0:0.500" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="NoSelectionHint"/>
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Unselecting">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="HintGlyphBorder"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unselected">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="HintGlyphBorder"/>
</Storyboard>
</VisualState>
<VisualState x:Name="UnselectedPointerOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="HintGlyphBorder"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="contentPresenter">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="UnselectedSwiping">
<Storyboard>
<DoubleAnimation Duration="0" To="0.5" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectingGlyph"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="HintGlyphBorder"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Selecting">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectionBackground"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectedBorder"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectingGlyph"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="HintGlyphBorder"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="contentPresenter">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Selected">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectionBackground"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectedBorder"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectedCheckMark"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="contentPresenter">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="SelectedSwiping">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectionBackground"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectedBorder"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectedCheckMark"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="contentPresenter">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="SelectedUnfocused">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectionBackground"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectedBorder"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="SelectedCheckMark"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="contentPresenter">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ListViewItemSelectedForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DragStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.2" To="NotDragging"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="NotDragging"/>
<VisualState x:Name="Dragging">
<Storyboard>
<DoubleAnimation Duration="0" To="{StaticResource ListViewItemDragThemeOpacity}" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="InnerDragContent"/>
<DragItemThemeAnimation TargetName="InnerDragContent"/>
<FadeOutThemeAnimation TargetName="SelectedCheckMarkOuter"/>
<FadeOutThemeAnimation TargetName="SelectedBorder"/>
</Storyboard>
</VisualState>
<VisualState x:Name="DraggingTarget">
<Storyboard>
<DropTargetItemThemeAnimation TargetName="OuterContainer"/>
</Storyboard>
</VisualState>
<VisualState x:Name="MultipleDraggingPrimary">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="MultiArrangeOverlayBackground"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="MultiArrangeOverlayText"/>
<DoubleAnimation Duration="0" To="{StaticResource ListViewItemDragThemeOpacity}" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ContentBorder"/>
<FadeInThemeAnimation TargetName="MultiArrangeOverlayBackground"/>
<FadeInThemeAnimation TargetName="MultiArrangeOverlayText"/>
<DragItemThemeAnimation TargetName="ContentBorder"/>
<FadeOutThemeAnimation TargetName="SelectionBackground"/>
<FadeOutThemeAnimation TargetName="SelectedCheckMarkOuter"/>
<FadeOutThemeAnimation TargetName="SelectedBorder"/>
<FadeOutThemeAnimation TargetName="PointerOverBorder"/>
</Storyboard>
</VisualState>
<VisualState x:Name="MultipleDraggingSecondary">
<Storyboard>
<FadeOutThemeAnimation TargetName="ContentContainer"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ReorderHintStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.2" To="NoReorderHint"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="NoReorderHint"/>
<VisualState x:Name="BottomReorderHint">
<Storyboard>
<DragOverThemeAnimation Direction="Bottom" ToOffset="{StaticResource ListViewItemReorderHintThemeOffset}" TargetName="ReorderHintContent"/>
</Storyboard>
</VisualState>
<VisualState x:Name="TopReorderHint">
<Storyboard>
<DragOverThemeAnimation Direction="Top" ToOffset="{StaticResource ListViewItemReorderHintThemeOffset}" TargetName="ReorderHintContent"/>
</Storyboard>
</VisualState>
<VisualState x:Name="RightReorderHint">
<Storyboard>
<DragOverThemeAnimation Direction="Right" ToOffset="{StaticResource ListViewItemReorderHintThemeOffset}" TargetName="ReorderHintContent"/>
</Storyboard>
</VisualState>
<VisualState x:Name="LeftReorderHint">
<Storyboard>
<DragOverThemeAnimation Direction="Left" ToOffset="{StaticResource ListViewItemReorderHintThemeOffset}" TargetName="ReorderHintContent"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DataVirtualizationStates">
<VisualState x:Name="DataAvailable"/>
<VisualState x:Name="DataPlaceholder">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="PlaceholderTextBlock">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="PlaceholderRect">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="ReorderHintContent" Background="Transparent">
<Path x:Name="SelectingGlyph" Data="F1 M133.1,17.9 L137.2,13.2 L144.6,19.6 L156.4,5.8 L161.2,9.9 L145.6,28.4 z" Fill="{StaticResource ListViewItemCheckSelectingThemeBrush}" FlowDirection="LeftToRight" HorizontalAlignment="Right" Height="13" Margin="0,9.5,9.5,0" Opacity="0" Stretch="Fill" VerticalAlignment="Top" Width="15"/>
<Border x:Name="HintGlyphBorder" HorizontalAlignment="Right" Height="40" Margin="4" Opacity="0" VerticalAlignment="Top" Width="40">
<Path x:Name="HintGlyph" Data="F1 M133.1,17.9 L137.2,13.2 L144.6,19.6 L156.4,5.8 L161.2,9.9 L145.6,28.4 z" Fill="{StaticResource ListViewItemCheckHintThemeBrush}" FlowDirection="LeftToRight" HorizontalAlignment="Right" Height="13" Margin="0,5.5,5.5,0" Opacity="0" Stretch="Fill" VerticalAlignment="Top" Width="15"/>
</Border>
<Border x:Name="ContentContainer">
<Grid x:Name="InnerDragContent">
<Rectangle x:Name="PointerOverBorder" Fill="{StaticResource ListViewItemPointerOverBackgroundThemeBrush}" IsHitTestVisible="False" Margin="1" Opacity="0"/>
<Rectangle x:Name="FocusVisual" IsHitTestVisible="False" Opacity="0" Stroke="{StaticResource ListViewItemFocusBorderThemeBrush}" StrokeThickness="2"/>
<Rectangle x:Name="SelectionBackground" Fill="{StaticResource ListViewItemSelectedBackgroundThemeBrush}" Margin="4" Opacity="0"/>
<Border x:Name="ContentBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Margin="4">
<Grid>
<ContentPresenter x:Name="contentPresenter" ContentTransitions="{TemplateBinding ContentTransitions}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<TextBlock x:Name="PlaceholderTextBlock" Foreground="{x:Null}" IsHitTestVisible="False" Margin="{TemplateBinding Padding}" Text="Xg" Visibility="Collapsed"/>
<Rectangle x:Name="PlaceholderRect" Fill="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" IsHitTestVisible="False" Visibility="Collapsed"/>
<Rectangle x:Name="MultiArrangeOverlayBackground" Fill="{StaticResource ListViewItemDragBackgroundThemeBrush}" IsHitTestVisible="False" Opacity="0"/>
</Grid>
</Border>
<Rectangle x:Name="SelectedBorder" IsHitTestVisible="False" Margin="4" Opacity="0" Stroke="{StaticResource ListViewItemSelectedBackgroundThemeBrush}" StrokeThickness="{StaticResource GridViewItemSelectedBorderThemeThickness}"/>
<Border x:Name="SelectedCheckMarkOuter" HorizontalAlignment="Right" IsHitTestVisible="False" Margin="4" VerticalAlignment="Top">
<Grid x:Name="SelectedCheckMark" Height="40" Opacity="0" Width="40">
<Path x:Name="SelectedEarmark" Data="M0,0 L40,0 L40,40 z" Fill="{StaticResource ListViewItemSelectedBackgroundThemeBrush}" Stretch="Fill"/>
<Path Data="F1 M133.1,17.9 L137.2,13.2 L144.6,19.6 L156.4,5.8 L161.2,9.9 L145.6,28.4 z" Fill="{StaticResource ListViewItemCheckThemeBrush}" FlowDirection="LeftToRight" HorizontalAlignment="Right" Height="13" Margin="0,5.5,5.5,0" Stretch="Fill" VerticalAlignment="Top" Width="15"/>
</Grid>
</Border>
<TextBlock x:Name="MultiArrangeOverlayText" Foreground="{StaticResource ListViewItemDragForegroundThemeBrush}" FontSize="26.667" FontFamily="{StaticResource ContentControlThemeFontFamily}" IsHitTestVisible="False" Margin="18,9,0,0" Opacity="0" TextWrapping="Wrap" Text="{Binding TemplateSettings.DragItemsCount, RelativeSource={RelativeSource Mode=TemplatedParent}}" TextTrimming="WordEllipsis"/>
</Grid>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Easy unblocking of downloaded files in Windows

Windows tries to protect you from yourself, and it tries to do it without nannying you, but sometimes it can be a real nuisance. A good example is blocked files. If you download something from the Internet, it is considered to be “untrusted” and therefore blocked from being used until you go and unblock it.

Which is all well and good if it is just one file, but if you’ve downloaded a Zip file of source and you extract everything from the Zip file, you’ve then got to unblock each and every file.

Which is a nuisance if you are using Explorer, because you can only do one at a time.

So it was really nice to find that PowerShell can unblock files and you can provide it with a recursive listing of files to unblock, e.g.:

Get-ChildItem -Recurse | Unblock-File

USE WITH CAUTION! This will literally unblock every file from the current directory downwards, so be sure that you do trust everything you are unblocking, but it should save you some time.

 

Security and Email groups in LDAP

After working with Active Directory since 2000, I’ve clearly become a bit spoiled with the ability to create a group in AD that serves the purpose of both a security group and a distribution group with just one checkbox. Why do I say that? Because in LDAP, there isn’t a commonly used way of achieving that same goal.

LDAP, or Lightweight Directory Access Protocol, is an Internet protocol that services can use to look up information from a server. Active Directory makes use of LDAP which is why you’ll come across terms common to both such as schema.

For the UNIX world, the commonly used schemas are defined in RFCs. For example, the most common objectClass used to define a person – inetOrgPerson – is defined in RFC 2798. This is a really great class to use for storing personal information in an LDAP directory because it has attributes for all of the important stuff you might want to know about someone.

When it comes to groups, though, things get a bit tougher. There is posixGroup which is a good class to use for security needs because it stores a group ID, a description and the members of the group. Rather surprisingly, there isn’t an accepted standard for defining a distribution or email group. There are classes for defining groups of users, such as groupOfNames, groupOfUniqueNames and groupOfMembers. They each have their slight differences and which one(s) you use typically comes down to either personal preference or the tools you are going to be using to manage those groups.

Another curious aspect about groups in LDAP is that there are differences in how the members are represented. For example, posixGroup uses an attribute called memberUid and the value is just that – the uid of the member of the group. groupOfUniqueNames, by comparison, uses an attribute called uniqueMember and the value is the distinguishedName of the member. One of the benefits of using the distinguishedName is that it allows groupOfUniqueNames to contain other groups as members, which posixGroup does not.

So when it comes to trying to maximise the value of a group’s membership by using the group for dual-purposes, i.e. security and email, what can you do? One option is to define your own objectClass and add it to the schema on the LDAP server. That is essentially what Microsoft did but the problem then is that your tools probably won’t know how to work with that class unless you can modify the tools.

Given that the objectClasses do represent members in different ways makes any attempt to “merge” or “overlay” multiple objectClasses to get the desired result is also likely to fail.

For myself, in the end, I decided that I would use posixGroup as the definitive representation of a group and then have a script that reads the various posixGroup groups and creates groupOfUniqueNames groups to match those groups. I wrote the script in Perl and it is at the end of this blog.

Here is a bit more detail about how the script works and how I’ve got my LDAP server set up …

I have an organizationalUnit (OU) called groups, below which I have an OU called mailing and an OU called security. The idea should be clear but all of my posixGroup groups go into ou=security and the script creates the mailing groups in ou=mailing. The script can be used in two ways:

  1. Full scan of the security OU. It looks at each of the groups in turn and processes it.
  2. Processing of one security group by specifying the cn value on the command line. This functionality is primarily there for use with LDAP Account Manager Pro. This great web-based product allows the administrator to define custom scripts that get run at specified trigger points. In my case, I get it to run the script, passing the cn value, when a group is created or modified, thus ensuring the email group is kept up-to-date.

The logic behind processing a security group is as follows:

  • Get the attributes we need from the security group: modifyTimestamp, description, displayName, mail, owner and memberUid.
  • If there aren’t any members, we delete the corresponding email group. This is because groupOfUniqueNames has to have at least one member. If you want to use groupOfMembers instead, that restriction goes away and the script could be modified accordingly.
  • If the email group already exists and its modifyTimestamp is newer than that of the security group, we don’t do anything else because the implication is that it was created by the script after the security group was created/modified.
  • The next step is to delete the email group. This is done rather than trying to figure out the differences in group membership. If you want to get fancy with the code, go ahead but this works for me Smile.
  • The final step is to create a new email group, specifying the attributes and members retrieved from the security group.

A few notes about the attributes: the posixGroup class doesn’t allow you to specify a display name (displayName), email address (mail) or group owner (owner). To permit that, I use the objectClass extensibleObject which allows you to add any attribute defined in the schema. LDAP purists tend to frown on this because it could lead to errant attributes being added. If you are concerned, you could define your own objectClass as an auxiliary class in order to allow just those three attributes to be used. Alternatively, the script will work without them since displayName and owner aren’t strictly necessary and the script can auto-create an email address by adding the email domain to the end of the existing cn value.

For the email groups, I again use extensibleObject because groupOfUniqueNames doesn’t allow a display name or email address. The email address is clearly required if you want this to work as an email group and the display name may be required if you are, for example, syncing with Google (which was my requirement) and you want the group to have a “nicer” name than just the cn value. Again, if you don’t like the idea of allowing all attributes to be added, you could define your own objectClass and amend the script accordingly.

Final comments:

  • this is my first Perl script and I have been quite lazy in that I have hard-coded the various domain bits into the script. Feel free to improve and, if you want, share back!
  • I’ve not used SSL in the connection because the script runs directly on the LDAP server. It is quite straightforward to amend the script to use LDAPS and there are examples on the web on how to do that.
  • The script assumes, when converting from memberUid to uniqueMember that all of the UIDs exist in the same OU, namely ou=staff,ou=accounts,dc=example,dc=com. It should be fairly straightforward to extend the script so that it searches for the UID and finds the DN that way.

use strict;

use Net::LDAP;

# See if a group name has been passed on the command line, e.g. from

# LDAP Account Manager

my $groupMatch = "";

# $#ARGV is -1 if no parameters, 0 if 1 parameter, etc. We only

# look for one group name.

if ($#ARGV == 0)

{

$groupMatch = $ARGV[0];

}

# Create a connection to the LDAP server

my $ldap = Net::LDAP->new ( "<LDAP server>" ) or die $@;

my $mesg = $ldap->bind ( "account with appropriate write privs",

password => "account's password",

version => 3 );

my $result = $ldap->search ( base => "ou=security,ou=groups,dc=example,dc=com",

filter => "cn=*",

attrs => ['cn', 'description', 'displayName', 'mail', 'memberUid', 'owner', 'modifyTimestamp'] );

print "Got ", $result->count, " entries from the search.\n";

# Walk through the entries

my @entries = $result -> entries;

my $entr;

foreach $entr ( @entries ) {

my $thisCN = $entr->get_value("cn");

# Only process the group if either we are processing them

# all, or the group name matches.

if (($groupMatch eq "") || ($groupMatch eq $thisCN))

{

print "DN: ", $entr->dn, "\n";

my $deleteEmailGroup = 1;

my $buildEmailGroup = 1;

my $thisModify = $entr->get_value("modifyTimestamp");

my $thisDescription = $entr->get_value("description");

my $thisDisplayName = $entr->get_value("displayName");

my $thisMail = $entr->get_value("mail");

my $thisOwner = $entr->get_value("owner");

my $memberRef = $entr->get_value ( "memberUid", asref => 1 );

if ($memberRef == undef)

{

# No members means we don't build a new email group, regardless

# of timestamps, etc. We do still try to delete an existing email

# group though.

$buildEmailGroup = 0;

}

else

{

# We have members in the security group so now we check timestamps

# so that we only create a new email group if the security group has

# been modified more recently.

# See if the email group exists already and, if it does, when was it

# modified? There is no point in creating the new email group if it

# was modified after the security group.

my $emailGroup = $ldap->search ( base => "ou=mailing,ou=groups,dc=example,dc=com",

filter => "cn=$thisCN",

attrs => ['modifyTimestamp']);

if ($emailGroup->count == 1)

{

my @emailEntries = $emailGroup -> entries;

my $emailEntry = @emailEntries[0];

my $emailModify = $emailEntry->get_value("modifyTimeStamp");

if ($thisModify > $emailModify)

{

print "... security group is newer\n";

}

else

{

print "... email group is newer\n";

$deleteEmailGroup = 0;

$buildEmailGroup = 0;

}

}

else

{

print "... email group doesn't exist.\n";

}

}

if ($deleteEmailGroup)

{

print "  ... deleting old email group\n";

$mesg = $ldap->delete("cn=$thisCN,ou=mailing,ou=groups,dc=example,dc=com");

# If we got an error from that, print the error and don't try to

# create the replacement group

if ($mesg->code() != 0 && $mesg->code() != Net::LDAP::Constant->LDAP_NO_SUCH_OBJECT)

{

print "  ... error while deleting group: ", $mesg->error(), " (code ", $mesg->code(), ")\n";

$buildEmailGroup = 0;

}

}

if ($buildEmailGroup)

{

# If we have members in the group, create a new email group

my $entry = Net::LDAP::Entry->new();

$entry->dn("cn=$thisCN,ou=mailing,ou=groups,dc=example,dc=com");

$entry->add('cn' => $thisCN,

'objectClass' => [ 'groupOfUniqueNames', 'extensibleObject' ]

);

# If we have an email address set that, otherwise make one up

if ($thisMail)

{

$entry->add('mail' => $thisMail);

}

else

{

$entry->add('mail' => "$thisCN\@example.com");

}

# If we have a description, display name or owner, set them

if ($thisDescription)

{

$entry->add('description' => $thisDescription);

}

if ($thisDisplayName)

{

$entry->add('displayName' => $thisDisplayName);

}

if ($thisOwner)

{

$entry->add('owner' => $thisOwner);

}

# For each of the memberUid entries, add a uniqueMember attribute

# $memberRef is a reference to the array, so dereference it

my @members = @{ $memberRef };

foreach ( @members ) {

print "  ... adding $_\n";

$entry->add('uniqueMember' => "uid=$_,ou=staff,ou=accounts,dc=example,dc=com");

}

#         $entry->dump();

print "  ... creating group\n";

$mesg = $entry->update( $ldap );

if ($mesg->code() != 0)

{

print "  ... error while creating group: ", $mesg->error(), "\n";

}

}

print "\n";

}

}

$ldap->unbind;

Converting Blu-Rays to high quality MP4s

Those of you who have followed my occasional blog postings will know that I have a home theatre setup with a Hush PC running Windows 7 with Media Center and a Synology NAS to store full rips of DVDs and Blu-Rays. I use Media Browser as the front-end to all of my stored videos.

Recently, however, two factors have led me to become increasingly frustrated with this configuration:

  1. ArcSoft’s Total Media Theatre does not play well in conjunction with Media Browser. The more recent versions of MB have improved the situation but TMT 5 just doesn’t play well. I don’t know if it is the fact that my hardware is now getting a bit old or because TMT 5 just isn’t a good bit of software.
  2. Some of the Blu-Rays recently purchased are protected by Cinavia. If you haven’t come across this before, it is an audio watermark designed in such a way that if you play a copy of the content rather than an original source, playback is supposed to stop after a period of time with an error. Now, the version of TMT 5 I was using had not had Cinavia support added but the Blu-Rays wouldn’t play properly, if at all, so I was left wondering if there was a change in the way the discs were being created that my version of TMT 5 was choking on. Upgrading TMT to the most recent version would introduce Cinavia support, which would then totally prevent me from using ripped copies.

So I’ve been researching the various options available to me, focussing mostly on HandBrake. This is a great, free piece of software that does a fantastic job of taking various source material (DVD, Blu-Ray and others) and converting to MP4. It does one job and it does it really well. It does not include any capability for defeating copy protection but I use AnyDVD HD for that.

Now I know that converting Blu-Rays to a different compressed format – both audio and video – is going to lose me some fidelity, and I know that I’ll lose functionality as well, such as the ability to dynamically turn subtitles on and off, or select different audio streams, etc. There are ways to solve this, such as using different containers such as MKV, but Windows 7 doesn’t support MKV natively and I didn’t want to install any more software onto the media PC. According to reports I’ve read, it is possible that the Cinavia watermark survives the transcoding but Windows 7 doesn’t provide any support for Cinavia :-).

Here are the settings I ultimately ended up with:

  • Normal preset
  • Video tab: select Fast Decode. I found this necessary to stop the media PC occasionally choking on the playback.
  • Audio tab: add an audio track of your choice with the codec set to AAC (faac) and mixdown set to 5.1 Channels.
  • Subtitles tab: if your Blu-Ray has any foreign language in it (e.g. Avatar, Star Wars I, Salt, etc), you can choose to have the English subtitles for those sections burned into the video image. This requires a copy of the nightly build of HandBrake and not the stable build at this time (0.9.8 doesn’t support this feature). Just add Foreign Audio Scan, select Forced Only and Burn In. It should be noted that enabling this feature will result in longer processing time as HB then has to scan through the video to see where the subtitles get turned on and off in order to determine if there are any foreign language subtitles to select.

That’s it. I found the resulting video and audio to be of very high quality.

2012 in review

The WordPress.com stats helper monkeys prepared a 2012 annual report for this blog.

Here’s an excerpt:

4,329 films were submitted to the 2012 Cannes Film Festival. This blog had 35,000 views in 2012. If each view were a film, this blog would power 8 Film Festivals

Click here to see the complete report.

VS2012, Windows Phone and the “Reference to a higher version” error

Having installed Windows 8 Pro, Visual Studio 2012 Premium and the new Windows Phone 8 SDK, I was keen to make sure that my Windows Phone 7.1 project still built & worked. That meant getting all of the references to work again.

Most of the references were for packages that I could install through Nuget. However, one was for a Zip file that I had to download and unpack. Upon browsing to the appropriate DLL and selecting it as the reference, Visual Studio promptly reported:

A reference to a higher version or incompatible assembly cannot be added to the project

The ultimate solution was to right-click on each of the files that had been contained in the Zip file, choosing Properties and then clicking on the Unblock button. Once that was all done, Visual Studio then allowed me to add the reference, although it did warn me that it might be unstable! Not a lot I can do about that J.

Win8 development: using the Multilingual App Toolkit

The Multilingual App Toolkit (or MAT for short!) integrates into Visual Studio 2012 and is intended to make it easy to manage the translation of strings through features such as import & export of translations and the use of Microsoft Translator to automate some of the processes.

There are some good videos (http://go.microsoft.com/fwlink/?LinkId=266600, http://go.microsoft.com/fwlink/?LinkId=266603 and http://go.microsoft.com/fwlink/?LinkId=266604) and articles (http://msdn.microsoft.com/en-us/library/windows/apps/xaml/JJ572370(v=win.10).aspx and http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh965329.aspx) on how to use MAT but I still couldn’t get my app to use the translated strings. The app continued to use the strings that I was putting into my English resource file.

Here is the answer: you must mark each and every string as Signed off in the Multilingual Editor (so that the blob on the left hand side of the window goes green) before those strings are used. Once you’ve done that, you should then see the strings being used so long as you’ve set your display language appropriate in Windows.

 

Win8 Development: getting icons

One of the significant improvements from developing for the Windows Phone platform is that Windows 8 supports XAML as an image type, allowing you to provide vector graphics rather than rasterised. This ensures that you get the crispest, cleanest iconography you can have because the OS will scale the images appropriately.

One of the sites that I’ve used in the past is the Noun Project. They have some stunning icons, all of which can be downloaded in SVG format … but how to then get that into XAML?

The simplest method I’ve found so far is Mike Swanson’s free Adobe Illustrator to XAML Export plug-in. Download, copy to the right directory, open the SVG file in AI, choose Export > XAML.

Job done.

Well, almost. You’ve then got to incorporate that into the project as a resource, but that’s for another blog ;-).

 

Win8 Development: major gotcha in page navigation

One of the major differences in page navigation between Windows Phone 7 and Windows 8 “Modern UI” apps is that the latter allows you to pass an object to the page whereas Windows Phone 7 is pretty much limited to simple items because navigation is done in the form of a URL.

Unfortunately, it turns out that while passing objects to a page does work, it causes a problem with the SuspensionManager because the object cannot be serialized. If you try, the call that SuspensionManager makes to Frame.GetNavigationState() results in an exception.

This took me a while to figure out (mainly because I’d forgotten that I’d been extending my code by passing objects to the pages) but also because I didn’t read the debug output closely enough to see this message:

WinRT information: GetNavigationState doesn’t support serialization of a parameter type which was passed to Frame.Navigate.

So, the upshot is that if you’ve written a Windows Phone app, you can pretty much stick to the same navigation methodology, passing simple objects to the page.

Reference: Microsoft Connect

Follow

Get every new post delivered to your Inbox.