AWS AppSync Resolvers – Finding what you’ve got in context

When you write your own Resolvers for AWS AppSync, you are given a $context object that contains a lot of helpful things. In my case I needed to grab a unique ID associated with the user from the Cognito User Pool I set up. But the $ctx.identity.cognitoIdentityId just wasn’t there. I needed to find out what I could use there.

Normally my response template looks like this:

#if($ctx.error)
    $utils.error($ctx.error.message, $ctx.error.type)
#end

$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])

In my case it was telling me that a value was required. So I changed the template to spit out the whole context object instead of just the message:

#if($ctx.error)
    $utils.error($utils.toJson($ctx), $ctx.error.type)
#end

Then I went to Queries and ran it again, this time getting the whole context spat out as the “error”. A trip through a JSON unescape tool and formatting with the JSON Viewer Notepad++ plugin gave me a complete picture of what’s available.

Turns out I need to use sub or username, and the cognitoIdentityId field is nowhere to be found.

Implementing a Custom Window Title Bar in WPF

There are several good reasons for wanting custom window chrome in WPF, such as fitting in additional UI or implementing a Dark theme. However the actual implementation is kind of tricky, since it is now your job to provide a bunch of features that you used to get for free. I’ll walk you through my implementation.

Appearance

I chose to emulate the Windows 10 style.

Windows 10 standard title bar UI

This will keep my UI consistent with the rest of Windows. I am choosing not to attempt to match the style for old versions of Windows, as I don’t think that would be a great time investment. It’s one little thing you lose when going with a full custom title bar, but it should be worth it by allowing full cohesive theming. Buttons are 46 wide, 32 tall.

Building the UI

First, set WindowStyle=”None” on your Window. That will remove the built-in title bar and allow you to do everything on your own.

Next, allocate space for your title bar in your UI. I like to allocate a Grid row for it.

Add this under the <Window> node in your XAML:

<WindowChrome.WindowChrome>
    <WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</WindowChrome.WindowChrome>

The CaptionHeight tells the OS to treat the top 32px of your window as if it was a title bar. This means that click to drag works, along with double clicking to maximize/restore, shaking to minimize other windows, etc. The ResizeBorderThickness allows the standard window resize logic to work, so we don’t need to reimplement that either.

Now we need to make the actual UI. This is mine:

<Grid>
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="Auto" />
		<ColumnDefinition Width="*" />
		<ColumnDefinition Width="Auto" />
		<ColumnDefinition Width="Auto" />
		<ColumnDefinition Width="Auto" />
	</Grid.ColumnDefinitions>
	<Image
		Grid.Column="0"
		Width="22"
		Height="22"
		Margin="4"
		Source="/Icons/VidCoder32.png" />
	<TextBlock
		Grid.Column="1"
		Margin="4 0 0 0"
		VerticalAlignment="Center"
		FontSize="14"
		Text="{Binding WindowTitle}">
		<TextBlock.Style>
			<Style TargetType="TextBlock">
				<Style.Triggers>
					<DataTrigger Binding="{Binding IsActive, RelativeSource={RelativeSource AncestorType=Window}}" Value="False">
						<Setter Property="Foreground" Value="{DynamicResource WindowTitleBarInactiveText}" />
					</DataTrigger>
				</Style.Triggers>
			</Style>
		</TextBlock.Style>
	</TextBlock>

	<Button
		Grid.Column="2"
		Click="OnMinimizeButtonClick"
		RenderOptions.EdgeMode="Aliased"
		Style="{StaticResource TitleBarButtonStyle}">
		<Path
			Width="46"
			Height="32"
			Data="M 18,15 H 28"
			Stroke="{Binding Path=Foreground,
							 RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
			StrokeThickness="1" />
	</Button>
	<Button
		Name="maximizeButton"
		Grid.Column="3"
		Click="OnMaximizeRestoreButtonClick"
		Style="{StaticResource TitleBarButtonStyle}">
		<Path
			Width="46"
			Height="32"
			Data="M 18.5,10.5 H 27.5 V 19.5 H 18.5 Z"
			Stroke="{Binding Path=Foreground,
							 RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
			StrokeThickness="1" />
	</Button>
	<Button
		Name="restoreButton"
		Grid.Column="3"
		Click="OnMaximizeRestoreButtonClick"
		Style="{StaticResource TitleBarButtonStyle}">
		<Path
			Width="46"
			Height="32"
			Data="M 18.5,12.5 H 25.5 V 19.5 H 18.5 Z M 20.5,12.5 V 10.5 H 27.5 V 17.5 H 25.5"
			Stroke="{Binding Path=Foreground,
							 RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
			StrokeThickness="1" />
	</Button>
	<Button
		Grid.Column="4"
		Click="OnCloseButtonClick"
		Style="{StaticResource TitleBarCloseButtonStyle}">
		<Path
			Width="46"
			Height="32"
			Data="M 18,11 27,20 M 18,20 27,11"
			Stroke="{Binding Path=Foreground,
							 RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
			StrokeThickness="1" />
	</Button>
</Grid>

I’ve got the app icon. I chose not to implement the special drop-down menu that comes with the standard title bar since it’s not often used and other major apps like Visual Studio Code don’t bother with it. But it’s certainly something you could add.

The title text has a trigger to change its color based on the “Active” state of the window. That allows the user to better tell if the window has focus or not.

The actual buttons use TitleBarButtonStyle and TitleBarCloseButtonStyle:

<Style x:Key="TitleBarButtonStyle" TargetType="Button">
	<Setter Property="Foreground" Value="{DynamicResource WindowTextBrush}" />
	<Setter Property="Padding" Value="0" />
	<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
	<Setter Property="IsTabStop" Value="False" />
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="{x:Type Button}">
				<Border
					x:Name="border"
					Background="Transparent"
					BorderThickness="0"
					SnapsToDevicePixels="true">
					<ContentPresenter
						x:Name="contentPresenter"
						Margin="0"
						HorizontalAlignment="Center"
						VerticalAlignment="Center"
						Focusable="False"
						RecognizesAccessKey="True" />
				</Border>
				<ControlTemplate.Triggers>
					<Trigger Property="IsMouseOver" Value="true">
						<Setter TargetName="border" Property="Background" Value="{DynamicResource MouseOverOverlayBackgroundBrush}" />
					</Trigger>
					<Trigger Property="IsPressed" Value="true">
						<Setter TargetName="border" Property="Background" Value="{DynamicResource PressedOverlayBackgroundBrush}" />
					</Trigger>
				</ControlTemplate.Triggers>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>

<Style x:Key="TitleBarCloseButtonStyle" TargetType="Button">
	<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
	<Setter Property="Padding" Value="0" />
	<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
	<Setter Property="IsTabStop" Value="False" />
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="{x:Type Button}">
				<Border
					x:Name="border"
					Background="Transparent"
					BorderThickness="0"
					SnapsToDevicePixels="true">
					<ContentPresenter
						x:Name="contentPresenter"
						Margin="0"
						HorizontalAlignment="Center"
						VerticalAlignment="Center"
						Focusable="False"
						RecognizesAccessKey="True" />
				</Border>
				<ControlTemplate.Triggers>
					<Trigger Property="IsMouseOver" Value="true">
						<Setter TargetName="border" Property="Background" Value="{DynamicResource MouseOverWindowCloseButtonBackgroundBrush}" />
						<Setter Property="Foreground" Value="{DynamicResource MouseOverWindowCloseButtonForegroundBrush}" />
					</Trigger>
					<Trigger Property="IsPressed" Value="true">
						<Setter TargetName="border" Property="Background" Value="{DynamicResource PressedWindowCloseButtonBackgroundBrush}" />
						<Setter Property="Foreground" Value="{DynamicResource MouseOverWindowCloseButtonForegroundBrush}" />
					</Trigger>
				</ControlTemplate.Triggers>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>

These are buttons with stripped-down control templates to remove a lot of the extra gunk. They have triggers to change the background color on mouse over (and the foreground color in the case of the Close button). Also they set WindowChrome.IsHitTestVisibleInChrome to True, which allows them to pick up clicks even though they are in the 32px caption area we set up earlier.

The button content itself uses <Path> to draw the icons. The minimize button uses RenderOptions.EdgeMode=”Aliased” to disable anti-aliasing and make sure it renders crisply without blurring over into other pixels. I set the Stroke to pick up the Foreground color from the parent button. The Path data for the maximize/restore buttons are all set on .5 to make sure it renders cleanly at the standard 96 DPI. With whole numbers it ends up drawing on the edge of the pixel and blurring the lines. We can’t use the same “Aliased” trick here as that might cause the pixel count for different lines to change and look off at different zoom levels like 125%/150%.

Looking good!

Responding to button clicks

Now that we have the UI in place, we need to respond to those button clicks. I normally use databinding/MVVM, but in this case I decided to bypass the viewmodel since these are really talking directly to the window.

Event handler methods:

private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
{
	this.WindowState = WindowState.Minimized;
}

private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e)
{
	if (this.WindowState == WindowState.Maximized)
	{
		this.WindowState = WindowState.Normal;
	}
	else
	{
		this.WindowState = WindowState.Maximized;
	}
}

private void OnCloseButtonClick(object sender, RoutedEventArgs e)
{
	this.Close();
}

Helper method to refresh the maximize/restore button:

private void RefreshMaximizeRestoreButton()
{
	if (this.WindowState == WindowState.Maximized)
	{
		this.maximizeButton.Visibility = Visibility.Collapsed;
		this.restoreButton.Visibility = Visibility.Visible;
	}
	else
	{
		this.maximizeButton.Visibility = Visibility.Visible;
		this.restoreButton.Visibility = Visibility.Collapsed;
	}
}

This, we call in the constructor and in an event handler for Window.StateChanged:

private void Window_StateChanged(object sender, EventArgs e)
{
	this.RefreshMaximizeRestoreButton();
}

This will make sure the button displays correctly no matter how the window state change is invoked.

Maximized placement

You thought we were done? Hah. Windows has other plans. You might notice that when you maximize your window, some content is getting cut off and it’s hiding your task bar. The default values it picks for maximized window placement are really weird, where it cuts off 7px of your window content and doesn’t account for task bar placement.

To fix this, we need to listen for the WM_GETMINMAXINFO WndProc message to tell our window it needs to go somewhere different when maximize. Put this in your window codebehind:

protected override void OnSourceInitialized(EventArgs e)
{
	base.OnSourceInitialized(e);
	((HwndSource)PresentationSource.FromVisual(this)).AddHook(HookProc);
}

public static IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
	if (msg == WM_GETMINMAXINFO)
	{
		// We need to tell the system what our size should be when maximized. Otherwise it will cover the whole screen,
		// including the task bar.
		MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

		// Adjust the maximized size and position to fit the work area of the correct monitor
		IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

		if (monitor != IntPtr.Zero)
		{
			MONITORINFO monitorInfo = new MONITORINFO();
			monitorInfo.cbSize = Marshal.SizeOf(typeof(MONITORINFO));
			GetMonitorInfo(monitor, ref monitorInfo);
			RECT rcWorkArea = monitorInfo.rcWork;
			RECT rcMonitorArea = monitorInfo.rcMonitor;
			mmi.ptMaxPosition.X = Math.Abs(rcWorkArea.Left - rcMonitorArea.Left);
			mmi.ptMaxPosition.Y = Math.Abs(rcWorkArea.Top - rcMonitorArea.Top);
			mmi.ptMaxSize.X = Math.Abs(rcWorkArea.Right - rcWorkArea.Left);
			mmi.ptMaxSize.Y = Math.Abs(rcWorkArea.Bottom - rcWorkArea.Top);
		}

		Marshal.StructureToPtr(mmi, lParam, true);
	}

	return IntPtr.Zero;
}

private const int WM_GETMINMAXINFO = 0x0024;

private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;

[DllImport("user32.dll")]
private static extern IntPtr MonitorFromWindow(IntPtr handle, uint flags);

[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);

[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
	public int Left;
	public int Top;
	public int Right;
	public int Bottom;

	public RECT(int left, int top, int right, int bottom)
	{
		this.Left = left;
		this.Top = top;
		this.Right = right;
		this.Bottom = bottom;
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
	public int cbSize;
	public RECT rcMonitor;
	public RECT rcWork;
	public uint dwFlags;
}

[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
	public int X;
	public int Y;

	public POINT(int x, int y)
	{
		this.X = x;
		this.Y = y;
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
	public POINT ptReserved;
	public POINT ptMaxSize;
	public POINT ptMaxPosition;
	public POINT ptMinTrackSize;
	public POINT ptMaxTrackSize;
}

When the system asks the window where it should be when it maximizes, this code will ask what monitor it’s on, then place itself in the work area of the monitor (not overlapping the task bar).

Window border

Finally, the window can be kind of hard to pick out when it doesn’t have a border, if it’s put against the wrong background. Let’s fix that now. Wrap your window UI in this:

<Border Style="{StaticResource WindowMainPanelStyle}">
    ...your UI
</Border>

This is the style:

<Style x:Key="WindowMainPanelStyle" TargetType="{x:Type Border}">
    <Setter Property="BorderBrush" Value="{DynamicResource WindowBorderBrush}" />
    <Setter Property="BorderThickness" Value="1" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
            <Setter Property="BorderThickness" Value="0" />
        </DataTrigger>
    </Style.Triggers>
</Style>

This will remove the 1px border when the window is maximized.

Okay, now we’re actually done

At least until Windows decides they want to shake up the title bar style again.

Dark Theme in WPF

In a recent Windows 10 update a toggle switch was added to allow the user to specify that they wanted “Dark” themes in apps:

I decided to add support for this to VidCoder. But no updates to WPF were made to make this easy. WPF does having theming support where it can pull in different .xaml resource files from a Themes folder, but this is all Luna/Aero/Win10 styling that is automatically applied based on your OS. After digging in the WPF source code I determined that there’s no way to hook in your own theme to this or manually alter which theme is loaded.

Furthermore, the actual dark theme setting is not exposed in a friendly manner to WPF. But we can still do it. Let’s get started.

Detecting Windows Dark Mode setting + High Contrast

The first step is finding out what theme we should be applying. To do that we need to tell what dark mode choice the user has made and detect when it’s changed. We also need to tell if the user has turned on High Contrast and detect when it’s changed.

Dark mode detection

To get the windows theme we need to poke into the registry. I used a WMI query to watch the registry for changes so we can update the app theme when they change the setting.

private const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";

private const string RegistryValueName = "AppsUseLightTheme";

private enum WindowsTheme
{
	Light,
	Dark
}

public void WatchTheme()
{
	var currentUser = WindowsIdentity.GetCurrent();
	string query = string.Format(
		CultureInfo.InvariantCulture,
		@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{0}\\{1}' AND ValueName = '{2}'",
		currentUser.User.Value,
		RegistryKeyPath.Replace(@"\", @"\\"),
		RegistryValueName);

	try
	{
		var watcher = new ManagementEventWatcher(query);
		watcher.EventArrived += (sender, args) =>
		{
			WindowsTheme newWindowsTheme = GetWindowsTheme();
			// React to new theme
		};

		// Start listening for events
		watcher.Start();
	}
	catch (Exception)
	{
		// This can fail on Windows 7
	}

	WindowsTheme initialTheme = GetWindowsTheme();
}

private static WindowsTheme GetWindowsTheme()
{
	using (RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath))
	{
		object registryValueObject = key?.GetValue(RegistryValueName);
		if (registryValueObject == null)
		{
			return WindowsTheme.Light;
		}

		int registryValue = (int)registryValueObject;

		return registryValue > 0 ? WindowsTheme.Light : WindowsTheme.Dark;
	}
}

High Contrast detection

Our app will have 3 “modes”: Light, Dark and High Contrast. In High Contrast we’ll reference a limited set of system colors which will come from the user’s settings:

We’ll need to find if High Contrast is enabled and when it changes:

bool isHighContrast = SystemParameters.HighContrast;
SystemParameters.StaticPropertyChanged += (sender, args) =>
{
	if (args.PropertyName == nameof(SystemParameters.HighContrast))
	{
		bool newIsHighContrast = SystemParameters.HighContrast;
	}
};

Combining the values

We want to end up with a value from this enum:

public enum AppTheme
{
	Light,
	Dark,
	HighContrast
}

You’ll need to use logic to combine light/dark with the High Contrast bool to get the theme and react to changes. I used ReactiveX Observables but you might have something else. Anyhow, if High Contrast is enabled, use that theme. Otherwise, use the Light/Dark as indicated by the registry key setting.

Setting up theme swapping

After we know what theme we want, we now need to apply it. Fortunately WPF has an excellent mechanism to swap out styles: ResourceDictionaries. Make your dictionaries like so:

Then inside each:

<ResourceDictionary
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
	<SolidColorBrush x:Key="MyBackgroundBrush" Color="#8EC2FA" />
</ResourceDictionary>

We can specify the “default” style by including Light.xaml in App.xaml:

<Application.Resources>
	<ResourceDictionary>
		<ResourceDictionary.MergedDictionaries>
			<ResourceDictionary Source="/Themes/Light.xaml" />
		</ResourceDictionary.MergedDictionaries>

		<!-- Other App-level items -->

That will make the designer happy. Though obviously we don’t want to have that theme all the time. We can swap out the theme before opening any windows in App.xaml.cs:

this.Resources.MergedDictionaries[0].Source =
    new Uri($"/Themes/{appTheme}.xaml", UriKind.Relative);

That way the app loads up with the correct theme. You can also run this code when the user updates the theme.

Populating the theme dictionaries

The only thing you want to put into the theme dictionaries are Brush resources (and maybe Colors if you need them). Dark and Light should have whatever looks good to you. For High Contrast, we should pick from the official SystemColors. For example, if you had a special window background brush, you’d want to define it as this in HighContrast.xaml:

<SolidColorBrush x:Key="MyBackgroundBrush" Color="{x:Static SystemColors.WindowColor}" />

If you choose from SystemColors, it will work for any High Contrast variant the user chooses, even if they customize it.

Another trick you can use in Dark.xaml is overriding system colors with your own:

<SolidColorBrush x:Key="{x:Static SystemColors.WindowBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.WindowTextBrushKey}" Color="White" />

This causes any standard control that uses the system colors to use the one you’ve supplied.

Referencing theme colors

Now to use a theme color you should always refer to it like so:

{DynamicResource MyBackgroundBrush}

A StaticResource never changes, but a DynamicResource reference can update, which comes in handy when the users switches on/off dark mode or High Contrast.

Theming built-in controls

While the SystemColor overrides I mentioned earlier can help a lot with re-theming built-in controls, unfortunately there are a lot of controls that don’t pay any attention to system colors.

Button, for example is one of them. Unfortunately, there doesn’t appear to be any way to override these colors. The only way to re-skin them is to make a copy of their ControlTemplate and insert references to our own themed colors. Unfortunately that means that the controls will now look the same no matter what version of Windows the user has, but I don’t know of any way around it. I based mine off the styling in Windows 10, just so it would look consistent in the latest version and stay “current” longer.

Anyway, there’s a couple ways to do it: One by copying the Styles and Templates documentation for the control you want to theme. Another is by right clicking on the control in the designer, then Edit Template -> Edit a Copy.

You’ll want to park the style with ControlTemplate override in App.xaml (Or another dictionary referenced by App.xaml):

<Style x:Key="ButtonBaseStyle" TargetType="Button">
	<Setter Property="Background" Value="{DynamicResource Button.Static.Background}" />
	<Setter Property="BorderBrush" Value="{DynamicResource Button.Static.Border}" />
	<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
	<Setter Property="BorderThickness" Value="1" />
	<Setter Property="HorizontalContentAlignment" Value="Center" />
	<Setter Property="VerticalContentAlignment" Value="Center" />
	<Setter Property="Padding" Value="1" />
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="{x:Type Button}">
				<Border
					x:Name="border"
					Background="{TemplateBinding Background}"
					BorderBrush="{TemplateBinding BorderBrush}"
					BorderThickness="{TemplateBinding BorderThickness}"
					SnapsToDevicePixels="true">
					<ContentPresenter
						x:Name="contentPresenter"
						Margin="{TemplateBinding Padding}"
						HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
						VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
						Focusable="False"
						RecognizesAccessKey="True"
						SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
				</Border>
				<ControlTemplate.Triggers>
					<Trigger Property="IsDefaulted" Value="true">
						<Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
					</Trigger>
					<Trigger Property="IsMouseOver" Value="true">
						<Setter TargetName="border" Property="Background" Value="{DynamicResource Button.MouseOver.Background}" />
						<Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource Button.MouseOver.Border}" />
					</Trigger>
					<Trigger Property="IsPressed" Value="true">
						<Setter TargetName="border" Property="Background" Value="{DynamicResource Button.Pressed.Background}" />
						<Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource Button.Pressed.Border}" />
					</Trigger>
					<Trigger Property="IsEnabled" Value="false">
						<Setter TargetName="border" Property="Background" Value="{DynamicResource Button.Disabled.Background}" />
						<Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource Button.Disabled.Border}" />
						<Setter TargetName="contentPresenter" Property="TextElement.Foreground" Value="{DynamicResource Button.Disabled.Foreground}" />
					</Trigger>
				</ControlTemplate.Triggers>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>
<Style BasedOn="{StaticResource ButtonBaseStyle}" TargetType="Button" />

I ripped out the FocusVisualStyle override since the default one seems to work fine for all themes. I also include both a “Base” style and an implicit style (that has no key). Controls that don’t specify a style will pick up the implicit style, while control that do need to specify a style can base it on the Base style.

I also moved all the “Brush” resources out to the Theme dictionaries where they belong, and changed all the references to {DynamicResource}.

One thing to watch out for is that some overrides like ComboBox will reference a control from PresentationFramework.Aero2 in their ControlTemplate. If you see a namespace brought in that references Aero2, that means your program won’t work on Windows 7. To keep it compatible, delete the “2” to reference PresentationFramework.Aero.

There’s a lot more “grunt work” involved in picking all the colors, but that covers the overall strategy I used to convert all the app UI. Here’s our new, themed UI:

Window title bar

Now that we’ve converted all of our UI, we might see something like this:

All of the content is themed, but the title bar isn’t. It doesn’t look the best. We can fix it, but unfortunately the only way to do that is to implement the title bar from scratch all on our own. That is a whole separate journey that I go over in another post.

Here’s what we get with our custom title bar:

A pain to implement, but at least now it looks less like garbage.

Source reference

You can check out VidCoder’s source code for reference (beta branch).

Item change tracking in DynamicData

Since ReactiveUI’s ReactiveList<T> has recently been deprecated, I’ve been moving to DynamicData.

One feature I used from ReactiveList was item change tracking. It might look something like this:

var myList = new ReactiveList<MyClass>();
myList.ChangeTrackingEnabled = true;
myList.ItemChanged
	.Where(x => x.PropertyName == nameof(MyClass.SomeProperty))
	.Select(x => x.Sender)
	.Subscribe(myObject =>
	{
		// Do some stuff
	});

In DynamicData it goes like this:

var myList = new SourceList<MyClass>();
myList
	.Connect()
	.WhenPropertyChanged(myObject => myObject.SomeProperty)
	.Subscribe(propValue =>
	{
		MyClass myObject = propValue.Sender;
		string newValue = propValue.Value;
		// Do some stuff
	});

There is also WhenValueChanged, which just gives you the new property value as a straight IObservable:

myList
	.Connect()
	.WhenValueChanged(myObject => myObject.SomeProperty)
	.Subscribe(newValue =>
	{
		// Do some stuff
	});

The most powerful is WhenAnyPropertyChanged, which can tell you when any of the properties changed:

var myList = new SourceList<MyClass>();
myList
	.Connect()
	.WhenAnyPropertyChanged()
	.Subscribe(myObject =>
	{
		// Do stuff with myObject
	});

Or when any set of specified properties has changed:

var myList = new SourceList<MyClass>();
myList
	.Connect()
	.WhenAnyPropertyChanged(nameof(MyClass.FirstProperty), nameof(MyClass.SecondProperty))
	.Subscribe(myObject =>
	{
		// Do stuff with myObject
	});

WPF Databinding using DynamicData

My project is MVVM and I had been using ReactiveList<T> as my go-to observable collection for viewmodel properties. But ReactiveUI deprecated ReactiveList in 8.6.1. So I needed to get on to the new recommended library: DynamicData.

But there is no direct drop-in replacement you can do like ObservableCollection<T> <-> ReactiveList<T>. I grabbed the NuGet package, dropped in SourceList<T>, but that doesn’t work. SourceList<T> does not implement INotifyCollectionChanged, which is what WPF looks for when binding to ListView, GridView, ItemsControl, etc. So how do we bind to this thing? After some fruitless searches I dug through the source code unit tests and found the answer. Add this to the viewmodel class:

private readonly SourceList<string> myList = new SourceList<string>();
public IObservableCollection<string> MyListBindable { get; } = new ObservableCollectionExtended<string>();

And this to its constructor:

this.myList.Connect().Bind(this.MyListBindable).Subscribe();

It’s a two-stage affair. The SourceList<T> is where all the operations happen. Adds, clears, deletes and all that. But you don’t directly observe the source list. To do that, you need to call Connect() to get an IObservable<IChangeSet<T>> . If you follow this observable you can see everything that’s changed about the collection by looking at the change sets.

But you still need to do one more thing: get it in a format that the WPF UI can bind to, that is an object that implements INotifyCollectionChanged. ObservableCollectionExtended is DynamicData’s implementation of that. The Bind() call “slaves” it to the SourceList. Any changes made in the SourceList are now reflected in the ObservableCollectionExtended. A final call to Subscribe() activates it. Now since we’re exposing it as a property, we can bind our UI to it:

<ItemsControl
	ItemsSource="{Binding MyListBindable}"
	...

The important thing here is to remember that this property is here ONLY so the UI can bind to it. That’s why I’ve named it Bindable: so I’ll realize I’m doing something wrong if I try to modify it directly.

The ObservableCollectionExtended property also allows you to get batches to avoid update churn from making individual edits to the collection. When you call Edit() on the SourceList<T>:

this.myList.Edit(innerList =>
{
	innerList.Clear();
	foreach (string name in names)
	{
		innerList.Add(name);
	}
});

It will batch up all the changes made into one event under INotifyCollectionChanged, meaning the UI only needs to react once.

Installing .NET Framework 4.7 automatically with Inno Setup

In this guide I will walk through how to get the .NET framework to download and install on-the-fly in an Inno Setup installer.

It works in 3 steps:

  1. Detect if the desired .NET framework is installed
  2. Download the .NET Framework bootstrap installer with Inno Download Plugin
  3. Run the bootstrap installer in quiet mode, which will download and install the .NET Framework. This is better than downloading the full installer since it only downloads the files it needs for your platform.

Here’s the full code:

#include <idp.iss>

// Other parts of installer file go here

[CustomMessages]
IDP_DownloadFailed=Download of .NET Framework 4.7.2 failed. .NET Framework 4.7 is required to run VidCoder.
IDP_RetryCancel=Click 'Retry' to try downloading the files again, or click 'Cancel' to terminate setup.
InstallingDotNetFramework=Installing .NET Framework 4.7.2. This might take a few minutes...
DotNetFrameworkFailedToLaunch=Failed to launch .NET Framework Installer with error "%1". Please fix the error then run this installer again.
DotNetFrameworkFailed1602=.NET Framework installation was cancelled. This installation can continue, but be aware that this application may not run unless the .NET Framework installation is completed successfully.
DotNetFrameworkFailed1603=A fatal error occurred while installing the .NET Framework. Please fix the error, then run the installer again.
DotNetFrameworkFailed5100=Your computer does not meet the requirements of the .NET Framework. Please consult the documentation.
DotNetFrameworkFailedOther=The .NET Framework installer exited with an unexpected status code "%1". Please review any other messages shown by the installer to determine whether the installation completed successfully, and abort this installation and fix the problem if it did not.

[Code]

var
  requiresRestart: boolean;

function NetFrameworkIsMissing(): Boolean;
var
  bSuccess: Boolean;
  regVersion: Cardinal;
begin
  Result := True;

  bSuccess := RegQueryDWordValue(HKLM, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', regVersion);
  if (True = bSuccess) and (regVersion >= 461308) then begin
    Result := False;
  end;
end;

procedure InitializeWizard;
begin
  if NetFrameworkIsMissing() then
  begin
    idpAddFile('http://go.microsoft.com/fwlink/?LinkId=863262', ExpandConstant('{tmp}\NetFrameworkInstaller.exe'));
    idpDownloadAfter(wpReady);
  end;
end;

function InstallFramework(): String;
var
  StatusText: string;
  ResultCode: Integer;
begin
  StatusText := WizardForm.StatusLabel.Caption;
  WizardForm.StatusLabel.Caption := CustomMessage('InstallingDotNetFramework');
  WizardForm.ProgressGauge.Style := npbstMarquee;
  try
    if not Exec(ExpandConstant('{tmp}\NetFrameworkInstaller.exe'), '/passive /norestart /showrmui /showfinalerror', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
    begin
      Result := FmtMessage(CustomMessage('DotNetFrameworkFailedToLaunch'), [SysErrorMessage(resultCode)]);
    end
    else
    begin
      // See https://msdn.microsoft.com/en-us/library/ee942965(v=vs.110).aspx#return_codes
      case resultCode of
        0: begin
          // Successful
        end;
        1602 : begin
          MsgBox(CustomMessage('DotNetFrameworkFailed1602'), mbInformation, MB_OK);
        end;
        1603: begin
          Result := CustomMessage('DotNetFrameworkFailed1603');
        end;
        1641: begin
          requiresRestart := True;
        end;
        3010: begin
          requiresRestart := True;
        end;
        5100: begin
          Result := CustomMessage('DotNetFrameworkFailed5100');
        end;
        else begin
          MsgBox(FmtMessage(CustomMessage('DotNetFrameworkFailedOther'), [IntToStr(resultCode)]), mbError, MB_OK);
        end;
      end;
    end;
  finally
    WizardForm.StatusLabel.Caption := StatusText;
    WizardForm.ProgressGauge.Style := npbstNormal;
    
    DeleteFile(ExpandConstant('{tmp}\NetFrameworkInstaller.exe'));
  end;
end;

function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
  // 'NeedsRestart' only has an effect if we return a non-empty string, thus aborting the installation.
  // If the installers indicate that they want a restart, this should be done at the end of installation.
  // Therefore we set the global 'restartRequired' if a restart is needed, and return this from NeedRestart()

  if NetFrameworkIsMissing() then
  begin
    Result := InstallFramework();
  end;
end;

function NeedRestart(): Boolean;
begin
  Result := requiresRestart;
end;

Detecting if the desired .NET Framework is installed

First you need to determine what registry key to check to see if your .NET version is installed. There is a good Stack Overflow answer that covers this, though the Microsoft docs page is more likely to be up to date. There is also a good article on how to apply that in Inno Setup’s Pascal scripting language.

I wrote mine to check for .NET 4.7. (See the NetFrameworkIsMissing method)

Downloading the bootstrapper

Next we need to find out where to download the installer from. The .NET Framework Deployment Guide for Developers has a great list of stable download links for the bootstrapper (web) installers. I picked 4.7.2, as it still supports code targeting .NET 4.7, and we might as well give the users the latest we can. This link should prompt you to download an .exe file directly; if it’s bringing you to a download webpage, it won’t work.

Now install Inno Download Plugin. This will make the idpAddFile and idpDownloadAfter calls inside InitializeWizard work.

This “InitializeWizard” method is a special one that InnoSetup calls when first setting up its wizard pages. We call our helper function to detect if the framework is installed, then schedule a download step after the “Ready to install” page. We include our direct download link determined earlier, and save it in a temp folder, with a file name of “NetFrameworkInstaller.exe”. This name I picked arbitrarily; we just need to refer to it later when we’re installing and cleaning up.

Installing the bootstrapper

Our code is activated on PrepareToInstall. When this function returns a string, that string is shown as an error message that stops the install from happening. We call into InstallFramework and return its result.

Inside InstallFramework we’re running the bootstrapper we downloaded earlier, with a flag to make the install passive (non interactive). The /showrmui option prompts the user to close applications to avoid a system restart. /showfinalerror tells the installer to show an error message if the install fails. Our main installer will show this screen while the framework is getting downloaded and installed: 

Then the .NET installer UI will show alongside the first window:

If you’d like to keep it “cleaner” and just show the first screen you can swap out the /passive argument for /q. However I like showing the user the .NET install progress since it can take a long time and it reassures them that work is still really happening.

After running the installer, the bootstrapper is deleted (whether or not the install succeeded).

And now your installer is done!

Testing the installer

To test a .NET 4 or 4.7 install, I’d recommend setting up a Windows 7 virtual machine in Hyper-V. Windows 7 comes with .NET 2, 3 and 3.5 out of the box, but not .NET 4 or 4.7. After it installs the framework you can go to Programs and Features to cleanly remove it and test it all over again.

To test an earlier .NET version you should just be able to use a modern OS like Windows 8.1 or 10.

Thanks to Antony Male for suggesting updates to the error handling code.

DateTime and DateTimeOffset in .NET: Good practices and common pitfalls

It becomes necessary to deal with dates and times in most .NET programs. A lot of programs use DateTime but that structure is frought with potential issues when you start serializing, parsing, comparing and displaying dates from different time zones and cultures. In this post I will go over these issues and the APIs and practices you should use to avoid them.

Background

The DateTime structure stores only two pieces of information: Ticks and Kind. 1 tick is 100 nanoseconds (10,000 ticks in a millisecond). The ticks counter represents how many 100ns ticks you are away from 1/1/0001 12:00 AM. It’s the part that determines that it’s April 6th, 2012, 3:32:07 PM.

Another bit of data is the Kind: represented as a DateTimeKind enumeration. Utc, Local and Unspecified are the possible values. Utc means that the ticks counter represents a Coordinated Universal Time (that doesn’t change due to daylight savings or time zones). Local means that it represents the local time of whatever time zone the computer is set to. This one is sensitive to daylight savings. Note that a timezone offset is not built in to the DateTime structure. It can only get back to a real UTC time by checking the current time zone settings on the computer. ToUniversalTime() and ToLocalTime() will do these conversions. It will return a new DateTime value with the Ticks adjusted to get the correct year/month/day and with the requested type. Calling ToUniversalTime() on a UTC DateTime or ToLocalTime() on a Local DateTime has no effect. Remember that these functions are generating a new DateTime value and not modifying the original.

There’s also a third type: Unspecified. This means we don’t know whether it’s local or UTC. All we know is the year, month, day, etc. When working with DateTime values, Unspecified is not very helpful since we don’t know what to do when we want to get a local or UTC time from it. If you call ToUniversalTime() on one of these, it assumes that the type must have been Local and converts based on that. If you call ToLocalTime() it assumes that the type must have been UTC and converts in that direction.

DateTimeOffset is a newer structure. It is also based on ticks but instead of storing a Kind, it keeps track of the offset from UTC as a TimeSpan. A DateTimeOffset always knows what time zone it’s in. Calling ToUniversalTime() will always result in a TimeSpan.Zero offset, and ToLocalTime() will convert and result in an offset of the user’s current time zone.

Use DateTimeOffset, not DateTime

DateTime has countless traps in it that are designed to give your code bugs:

  • DateTime values with DateTimeKind.Unspecified are bad news. It’s common for code to call ToUniversalTime() or ToLocalTime() to make sure it’s got the date properly ready for storage or display respectively. If you call ToUniversalTime() it converts X hours in one direction, but if you call ToLocalTime() on it, it converts X hours in the opposite direction. Both of those can’t possibly be correct.
  • DateTime doesn’t care about UTC/Local when doing comparisons. It only cares about the number of Ticks on the objects. It could not care less that you’ve lovingly specified that value A is most certainly UTC and value B is local; it won’t do any conversions for you. 7:30 AM Local is always going to be evaluated as less than 8:22 AM UTC, even if your timezone is EDT and really the first time was 11:30 AM UTC.
  • DateTime values are not aware of standard format strings. For instance the “r” (RFC1123, the format used in HTTP headers) and “u” (universal sortable) formats both put UTC markers in the string you serialize to. If you call .ToString(“u”) on a local time, DateTime will happily label your local time as UTC without doing any conversion. The basically guarantees disaster when it gets parsed back in again.
  • Parsing a string that has a UTC marker with DateTime does not guarantee a UTC time. If you run DateTime.Parse(“2012-04-06 23:46:23Z”) you might expect the result to be a UTC DateTime since the “Z” UTC flag is right there in the string, but you do not. It will correctly note that it is dealing with a UTC time, then promptly convert it to a Local time. It’s still technically correct, but it can result in the hours count changing when you are not expecting it to.

But DateTimeOffset doesn’t have any of these problems! It’s the newer, more robust version; think of it as DateTime v2. They couldn’t fix the old DateTime object because that might break existing code that was relying on the bad behavior.

So you’ll want to use DateTimeOffset in any situation that you’re referring to a specific point in time. You might still use DateTime for things like general dates (July 4th, 1776) or store hours (9AM-5PM), since they are not affected by time zones.

Pick the right format when serializing

It’s common to convert DateTimeOffset values to strings for storage or sending them somewhere else. The list of standard date and time format strings is helpful when you are contemplating how to do this. These are used with the DateTimeOffset.ToString method: for example myDate.ToString(“u”). Note how with most of the standard formats you get different values for different cultures: different month names, swapped month/day places, etc. If you use one of these to serialize and Parse back in, it might work if you have the same culture on both sides, but will break if one side uses a different culture. You can get failed parses or swapped month/day values. Only the “o”, “r”, “s” and “u” formats are culture-invariant. Use one of those for serialization:

Format string Name Example  
“o” Round-trip 2012-04-17T16:46:48.0820143+00:00 (UTC)
2012-04-17T09:46:48.0820143-07:00 (local)
The only format that preserves the DateTimeOffset fully. The others round to the nearest second.
“r” RFC1123 Tue, 17 Apr 2012 16:46:48 GMT Used for HTTP headers
“u” Universal sortable 2012-04-17 16:46:48Z  
“s” Sortable 2012-04-17T16:46:48 (UTC)
2012-04-17T09:46:48 (local)
Not a great format for this use as it has no timezone or UTC marker and can change based on what offset you have

Conclusion

Use DateTimeOffset and be careful with serialization. If you’re dealing with legacy code that’s using DateTime, be aware of all the timezone-related pitfalls when comparing, serializing, parsing and converting.

One final issue…

If it is Coordinated Universal Time, why is the acronym UTC and not CUT? When the English and the French were getting together to work out the notation, the french wanted TUC, for Temps Universel Coordonné. UTC was a compromise: it fit equally badly for each language. 🙂

Using the Dispatcher with MVVM

When writing an MVVM application, you want to separate from the UI. However you also need to make sure that UI updates happen on the UI thread. Changes made through INotifyPropertyChanged get automatically marshaled to the UI thread, so in most cases you’ll be fine. However, when using INotifyCollectionChanged (such as with an ObservableCollection), these changes are not marshaled to the UI thread.

This means that unless you modify your collection on the UI thread, you’ll get a cross threading error. Thus the problem. We’re in the ViewModel and we don’t have access to the Dispatcher. That’s on a View object. How do we update the collection on the UI thread?

I came up with a small static class to help with this:

public static class DispatchService
{
    public static void Invoke(Action action)
    {
        Dispatcher dispatchObject = Application.Current.Dispatcher;
        if (dispatchObject == null || dispatchObject.CheckAccess())
	{
            action();
        }
        else
        {
            dispatchObject.Invoke(action);
        }
    }
}

When your ViewModel gives the DispatchService actions, it can run them on the correct thread. Which is, in the case of unit tests, always the current thread. A call to the DispatchService may look like this:

DispatchService.Invoke(() =>
{
    this.MyCollection.Add("new value");
});

The use of a simple lambda statement makes it a relatively lightweight way of updating on the correct thread.

Saving window size and location in WPF and WinForms

A common user interface tweak is to save the size and location of a window and restore that state when the program starts up again. That way, the users don’t need to re-do their work of resizing and moving the windows to where they want them. However I’ve found that there isn’t really a convenient way to do with in C#. Many solutions focus on recording the window width, height, x and y coordinates and current state (maximized, restored, etc), then applying all those values again on startup. But then you have to deal with edge cases: What if the user has changed resolutions since your program last ran? What if it was present on a monitor that’s now disconnected?

Fortunately, there are a couple of native Windows functions that can do all this work for us: SetWindowPlacement and GetWindowPlacement. MSDN has a sample demonstrating how to PInvoke to these functions in a WPF project, but it’s got a few issues with it. For one, it’s trying to store the placement as a custom type in settings, which can cause strange errors every time you open your Settings file. Here’s my take on it:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace WindowPlacementExample
{
    // RECT structure required by WINDOWPLACEMENT structure
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;

        public RECT(int left, int top, int right, int bottom)
        {
            this.Left = left;
            this.Top = top;
            this.Right = right;
            this.Bottom = bottom;
        }
    }

    // POINT structure required by WINDOWPLACEMENT structure
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int X;
        public int Y;

        public POINT(int x, int y)
        {
            this.X = x;
            this.Y = y;
        }
    }

    // WINDOWPLACEMENT stores the position, size, and state of a window
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWPLACEMENT
    {
        public int length;
        public int flags;
        public int showCmd;
        public POINT minPosition;
        public POINT maxPosition;
        public RECT normalPosition;
    }

    public static class WindowPlacement
    {
        private static Encoding encoding = new UTF8Encoding();
        private static XmlSerializer serializer = new XmlSerializer(typeof(WINDOWPLACEMENT));

        [DllImport("user32.dll")]
        private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

        [DllImport("user32.dll")]
        private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);

        private const int SW_SHOWNORMAL = 1;
        private const int SW_SHOWMINIMIZED = 2;

        public static void SetPlacement(IntPtr windowHandle, string placementXml)
        {
            if (string.IsNullOrEmpty(placementXml))
            {
                return;
            }

            WINDOWPLACEMENT placement;
            byte[] xmlBytes = encoding.GetBytes(placementXml);

            try
            {
                using (MemoryStream memoryStream = new MemoryStream(xmlBytes))
                {
                    placement = (WINDOWPLACEMENT)serializer.Deserialize(memoryStream);
                }

                placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
                placement.flags = 0;
                placement.showCmd = (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd);
                SetWindowPlacement(windowHandle, ref placement);
            }
            catch (InvalidOperationException)
            {
                // Parsing placement XML failed. Fail silently.
            }
        }

        public static string GetPlacement(IntPtr windowHandle)
        {
            WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
            GetWindowPlacement(windowHandle, out placement);

            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8))
                {
                    serializer.Serialize(xmlTextWriter, placement);
                    byte[] xmlBytes = memoryStream.ToArray();
                    return encoding.GetString(xmlBytes);
                }
            }
        }
    }
}

To get the placement of an existing window, you give it the native handle for the window, and get back a string of XML that represents the placement for the window. You save this string somewhere, then when the program starts back up again, apply it with SetPlacement. The above code works for both WinForms and WPF. And in both cases you can open your Properties/Settings.settings file and add a new setting to store the string:

image

This will store the window state locally, per-user. Of course you could also store the string wherever you wanted to.

What I like about this approach is that the WindowPlacement class does the XML serialization for you and gives you a nice, tidy string containing all the placement data.

But where in the program code should we hook in to save and re-apply the window placements? The answer is a bit different between WinForms and WPF.

WPF:

You might benefit by throwing a couple of extension methods on the WindowPlacement class to make things a bit easier:

public static void SetPlacement(this Window window, string placementXml)
{
    WindowPlacement.SetPlacement(new WindowInteropHelper(window).Handle, placementXml);
}

public static string GetPlacement(this Window window)
{
    return WindowPlacement.GetPlacement(new WindowInteropHelper(window).Handle);
}

To save the window placement, just handle the Closing event on the Window:

private void Window_Closing(object sender, CancelEventArgs e)
{
    Settings.Default.MainWindowPlacement = this.GetPlacement();
    Settings.Default.Save();
}

To restore, you’ll need to hook in on SourceInitialized:

protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);
    this.SetPlacement(Settings.Default.MainWindowPlacement);
}

WinForms:

This one is a bit more straightforward. Save on the FormClosing event and restore the window state in the Load event handler. The Form’s native handle is accessible from its Handle property.

Now we have a robust and relatively painless way of persisting window sizes and positions, and we only need to keep track of one string per window.