In the previous posts (Light sensor, Compass and Inclinometer), I’ve shown how to use some sensors in a UWP app. In this post, I’ll show how to use another sensor, the accelerometer. The accelerometer will measure the acceleration of the device. This measure is very useful to see when the device is moved.

This sensor will show the acceleration in the three axis, X, Y and Z, and has a particularity: as it will show the forces that the device is submitted, its measure usually is not 0, because the device is always submitted to the gravity force (unless you take it to the space). The normal measure for the standing device is 1, the gravity force. When you move it, the measure should be different than 1.

Making a ball jump in UWP

For this post, I will develop a program that shows a ball at the bottom of the window, and when the user shakes the device, the ball jumps, using an animation. The stronger the shake, the ball will jump higher.

The sensor gives three measures, but I am not interested in the acceleration in every axis. I just want the magnitude of the acceleration, and that’s given by this formula:

[sourcecode language=”text”]
magnitude = sqrt(x^2 + y^2 + z^2)
[/sourcecode]

The magnitude of the acceleration is the square root of the sum of the squares of the three measures.

With this information, we can start our program. Create a new blank UWP solution. In the main page, change the main element to a Canvas and add an ellipse to it:

[sourcecode language=”xml”]
<Canvas Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Ellipse Width="80" Height="80" Fill="Red" x:Name="Ball"/>
</Canvas>
[/sourcecode]

In the constructor of MainPage, add the code to position the ball, get the accelerometer and add the ReadingChanged event handler:

[sourcecode language=”csharp”]
public MainPage()
{
this.InitializeComponent();
Loaded += (s, e) =>
{
Canvas.SetLeft(Ball, ActualWidth / 2 – 40);
Canvas.SetTop(Ball, ActualHeight – 80);
var accelerometer = Accelerometer.GetDefault();
if (accelerometer == null)
{
return;
}
accelerometer.ReadingChanged += async (s1, e1) =>
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
var magnitude = Math.Sqrt(
e1.Reading.AccelerationX * e1.Reading.AccelerationX +
e1.Reading.AccelerationY * e1.Reading.AccelerationY +
e1.Reading.AccelerationZ * e1.Reading.AccelerationZ);
if (magnitude &gt; 1.5)
JumpBall(magnitude);

});
};
};
}
[/sourcecode]

The code is very similar to the ones in the previous posts: we set the ball position to the bottom of the window and then set the ReadingChanged event handler. This handler gets the magnitude of the reading and then, if the magnitude is greater than 1.5G, it calls JumpBall, to make the ball jump. The code for JumpBall is:

[sourcecode language=”csharp”]
private void JumpBall(double acceleration)
{
var da = new DoubleAnimation()
{
From = ActualHeight-80,
To = ActualHeight-80-acceleration * 200,
AutoReverse = true,
Duration = new Duration(TimeSpan.FromSeconds(1))
};
Storyboard.SetTarget(da, Ball);
Storyboard.SetTargetProperty(da, "(Canvas.Top)");
var sb = new Storyboard();
sb.Children.Add(da);
sb.Begin();
}
[/sourcecode]

We create a DoubleAnimation with the bottom of the window as the starting point, the end point is given by the magnitude of the reading. The animation is related to the Canvas.Top attached property, related to the ball. With all that set, we start the animation.

That way, when the user shakes the device, the ball jumps. Now, when you run the app and shake the device, the ball will jump.

image

Conclusions

With the accelerometer, you can give your users more interactivity, detecting when they move or shake the device and act accordingly. The accelerometer is very easy to use and can improve the games usability a lot.

The full source code for this project is in https://github.com/bsonnino/JumpingBall

In the previous two posts (Light sensor and Compass), I’ve shown how to use the sensors in a UWP app. Working with them is very easy and it can enhance the usability of your app. In this post, we willshow how to use another sensor, the Inclinometer.

This sensor is used a lot for games, as it detects the inclination of the device. That way, the user can control the game by tilting the device. Pretty cool, no? It’s note a pure sensor, as you won’t have an inclinometer in your device, but it gets its data from other two sensors, the accelerometer and the gyroscope.

The sensor usage is similar to the other sensors, and it shows three measures, Yaw (the rotation on the Z axis), Pitch (rotation on the X axis) and Roll (rotation on the Y axis):

inclinometer

(source – https://msdn.microsoft.com/en-us/windows/uwp/devices-sensors/sensors)

Playing with a ball in UWP

To show the usage f an inclinometer, we will create a project that can be a starting point for a game. We will draw a ball in the window and, depending on how the user tilts the device, the ball will move faster or slower, to the left and to the right.

We will start creating a new UWP blank app. Change the main element in the main page to a Canvas and add a new Ellipse to it:

[sourcecode language=”xml” padlinenumbers=”true”]
<Canvas Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Ellipse Width="80" Height="80" Fill="Red" x:Name="Ball"/>
</Canvas>
[/sourcecode]

In the constructor of the MainPage, we position the ball and add the code to initialize the inclinometer and set its ReadingChanged event handler:

[sourcecode language=”csharp”]
public MainPage()
{
this.InitializeComponent();
Loaded += (s, e) =>
{
Canvas.SetLeft(Ball, ActualWidth / 2 – 40);
Canvas.SetTop(Ball, ActualHeight – 80);
var inclinometer = Inclinometer.GetDefault();
if (inclinometer == null)
{
return;
}
inclinometer.ReadingChanged += async (s1, e1) =>
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
var position = Canvas.GetLeft(Ball);
var newPosition = position + e1.Reading.RollDegrees;
if (newPosition ActualWidth – 80)
newPosition = ActualWidth – 80;
Canvas.SetLeft(Ball, newPosition);
});
};
};
}
[/sourcecode]

As you can see, this time I’m doing all the initialization in the handler of the Loaded event of the page. That’s because ActualWidth and ActualHeight of the page aren’t set before the Loaded event fires, and if I don’t use this handler, the ball would be placed in the wrong place. As we have a Canvas, we position the ball with the attached properties Left and Top. In the code behind, this is done with the static methods Canvas.SetLeft and Canvas.SetTop.

The next step is to get the Inclinometer sensor with GetDefault and set its ReadingChanged handler. It will get the current position of the ball and will set the new position depending on the Roll (number of degrees of tilt in the Y position). That way, if the user tilts more, the ball will move faster. If he tilts less, it will move slower.

With that code in place, we have our moving ball project complete and can run it. If your device has an inclinometer, you can see the ball moving. If it doesn’t have, you can use a Windows Phone and see the same app running there.

image

Conclusions

Using the inclinometer is very simple and you can provide your users a different kind of input with it, you just need to get its reading and move the elements in your app according to the tilt of the device.

The source code for this app is in https://github.com/bsonnino/MovingBall

In the last post, I have shown how to use the light sensor to get light data and offer a better experience for the user. In this post, I will show another sensor available in 2-in-1 devices and tablets: the compass. The compass is a sensor the shows the direction, related to the magnetic north.

On physical compasses, this reading is given by a magnetic needle that is always pointing to the magnetic north. When you turn the compass, it will show you where are you heading to. In computers and smartphones, there is no magnetic needle, but the sensor shows the angle of the device, related to the magnetic north. To use the sensor, we must create a program that does the same three steps of the last post:

  • Get an instance of the sensor with the GetDefault method
  • Set its properties
  • Set a handler for the ReadingChanged

In this post, we will create a program that simulates a physical compass and shows the heading of the device.

Creating a compass simulator in UWP

The first step is to create a blank UWP project. In this project, add images tor the compass background and needle (I got my background from http://www.clipartbest.com/images-of-a-compass and the needle from http://www.clker.com/clipart-compassnorth.html) in the Assets folder.

Then, add the two images to the main window:

[sourcecode language=”xml” padlinenumbers=”true”]
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Image Source="Assets\\Compass.png" HorizontalAlignment="Stretch"
           VerticalAlignment="Stretch" Stretch="Uniform"/>
    <Image Source="Assets\\Compassnorth-Hi.png" HorizontalAlignment="Stretch"
           VerticalAlignment="Stretch" Stretch="Uniform" x:Name="Needle"/>
</Grid>
[/sourcecode]

In the constructor of MainWindow, we must get the compass and set the ReadingChanged event handler:

[sourcecode language=”csharp”]
public MainPage()
{
this.InitializeComponent();
var compass = Compass.GetDefault();
if (compass == null)
{
return;
}
compass.ReadingChanged += async (s, e) =>
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
RotateNeedle(e.Reading.HeadingMagneticNorth);
});
};
}
[/sourcecode]

The event handler just calls RotateNeedle, passing the reading angle as parameter. RotateNeedle is very simple:

[sourcecode language=”csharp”]
private void RotateNeedle(double angle)
{
var transform = new RotateTransform() { Angle = angle };
Needle.RenderTransformOrigin = new Windows.Foundation.Point(0.5, 0.5);
Needle.RenderTransform = transform;
}
[/sourcecode]

It just creates a RotateTransform, setting the angle to the reading angle, sets the origin of the rotation on the middle of the needle and then sets the RenderTransform property to this transform.

With this, you have a compass simulator that works both on desktops and smartphones. As you turn the device, the needle will be pointing to the magnetic north and the head will show you the direction.

image

Conclusions

As you can see, working with the compass is very easy, you can easily get the reading of the sensor and change the view, depending on what the user is heading. This is one more tool that you can use to give the user a better experience.

The full source code for the project is in https://github.com/bsonnino/Compass

Some time ago, computer makers introduced new classes of Windows devices: the 2-in-1 and the tablets. These kinds of computers bring a lot of innovation – the 2-in-1 computers can be used as notebooks and tablets, while still maintaining their power: you can have very powerful devices, that can be used at the office and on the road, thus avoiding the need of having a tablet and a notebook.

But this kind of devices have another kind of innovations that are not visible at first sight: sensors. With them, you can have the same experience you have with a smartphone – you can use the accelerator for games or gestures, the GPS for location and routing and many others. This series of posts will show how to use these sensors in UWP, so you can give your user a better experience, so he can use all the resources of his device. And all this with an added bonus, thanks to UWP development – the same project will work both on desktop devices and smartphones.

Working with sensors in UWP

Basically, working with sensors in UWP is pretty much the same:

  • You get an instance of the sensor with the GetDefault method
  • You set its properties
  • You set a handler for the ReadingChanged

That’s all that is needed to work with sensors in UWP. So, let’s go and start with the first sensor: the light sensor.

Working with the light sensor

The light sensor can be used to detect the environment lighting and adjust the screen brightness according to the external light, so the use has the best experience. In this post I will show a simple way to create the effect used in GPS devices: when it’s day, it shows the display with a white background and when it’s night, it shows it with a black background.

To get this effect, the simplest way is to use themes. UWP has three default themes, Dark, Light and High Contrast and you can set them by using the RequestedTheme property of the control (in our case, the main window). So, let’s start.

In Visual Studio create a UWP application and add this XAML to the main page:

[sourcecode language=”xml” padlinenumbers=”true”]

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="Day" HorizontalAlignment="Center" x:Name="MainText"
VerticalAlignment="Center" FontSize="48" />
</Grid>
[/sourcecode]

Then, in the MainPage constructor, we will initialize the light sensor and process its readings:

[sourcecode language=”csharp”]
public MainPage()
{
this.InitializeComponent();
var lightSensor = LightSensor.GetDefault();
if (lightSensor == null)
{
MainText.Text = "There is no light sensor in this device";
return;
}
lightSensor.ReportInterval = 15;
lightSensor.ReadingChanged += async (s, e) =>
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
if (e.Reading.IlluminanceInLux < 30)
{

RequestedTheme = ElementTheme.Dark;
MainText.Text = "Night";
}
else
{
RequestedTheme = ElementTheme.Light;
MainText.Text = "Day";
}
});
};
}
[/sourcecode]

We must use the Dispatcher to change the theme and text, because the ReadingChanged can be called in a different thread of the UI thread. When the Illuminance reading is less than 30 Lux, we change to the Dark theme and change the text to Night.

Now, when you run the program in a device that has a light sensor, you will see the default view in daylight like this:

Day

If you cover the light sensor to simulate the lack of light, you will see something like this:

Night

Conclusions

As you can see, it’s very easy to work with sensors. With them, you can give a better experience for your users, so they can enjoy using your apps. And a added benefit is that you can use the same app in a desktop or in a Windows Phone.

The source code for this app is in https://github.com/bsonnino/LightSensor

In the last post, I showed how to load a complete view dynamically, but that’s not the most common way to change the visualization of data. Most of the time, you will want to just change the appearance of the view, customizing the style for a client.

If we want to apply the styles for all controls, we must use a Theme. A Theme provides the default appearance for all controls of an application. The default theme is provided by the OS the app is running, but that can be changed by using an alternate theme file, a resource dictionary that has default styles for all controls.

Loading a theme dynamically

One way to load themes dynamically is to use any theme dll available for our use (many of them are free). We can add the themes to our project using Nuget and then change the theme as we want, but that’s not the focus, here. We want to load the themes from loose XAML files dynamically.

For that, I used an open source project, named WpfThemes (https://wpfthemes.codeplex.com/), that has several themes available and creates a themes dll that can be used in our project. As it is open source, I won’t use the dll, but I will use the theme structure and its demo to show how to load the theme from its files.

I downloaded the project and restructured it in this way:

  • I created a single project, LoadThemes, and added the content of the demo main window to the new project’s main window
  • I created a new folder on the project, named Themes and added all themes folders from the WPF.Themes project
  • I set all Theme.xaml file Build Action to None and Copy to output Directory to “ Copy if newer”

With this structure, we can load all available theme names to the theme combobox using this code:

[sourcecode language='csharp'  padlinenumbers='true']
private void LoadThemes()
{
    themes.ItemsSource = Directory.EnumerateDirectories("Themes").Select(t => t.Substring(7));
}
[/sourcecode]

I just enumerate all folders under the Themes folder and get the folder names, removing the initial “Themes\”. Now, we must load the theme dynamically. This is done in the SelectionChanged event for the theme combobox:

[sourcecode language='csharp'  padlinenumbers='true']
private void themes_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count == 0)
        return;
    using (FileStream fs = new FileStream($"Themes\\{e.AddedItems[0]}\\Theme.xaml", FileMode.Open))
    {
        var dict = XamlReader.Load(fs) as ResourceDictionary;
        if (dict != null)
        {
            Application.Current.Resources.Clear();
            Application.Current.Resources.MergedDictionaries.Add(dict);
        }
    }
}
[/sourcecode]

The program gets the selected item and opens the Theme.xaml located in the selected folder. Then, it loads the file as a ResourceDictionary and sets the MergedDictionaries for the current Application to it. This is everything that’s needed to change the style of all controls at once.

image

Loading styles dynamically

Usually, you do not want such a radical change, like the theme change, but you want to change the way some controls are drawn (colors, margins, etc). For that, you just need to change some styles. One way to do it is to apply some styles to your controls and then load the resource dictionaries dynamically. For example, the project from previous post could be redesigned by using styles. We can take the Master/Detail file and apply styles to the file, setting something like this:

[sourcecode language='xml' ]
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             mc:Ignorable="d"
             d:DesignHeight="600" d:DesignWidth="800" Foreground="{StaticResource PageForeground}">
    <UserControl.Resources>
        <Style TargetType="TextBlock" >
            <Setter Property="Foreground" Value="{StaticResource PageForeground}" />
        </Style>
    </UserControl.Resources>
    <Grid Background="{StaticResource PageBackground}">
        <Grid.RowDefinitions>
            <RowDefinition Height="40" />
            <RowDefinition Height="*" />
            <RowDefinition Height="2*" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Country" VerticalAlignment="Center" Margin="5"/>
            <TextBox Height="25"
                     VerticalAlignment="Center" Margin="5,3" Width="250" Text="{Binding SearchText, Mode=TwoWay}"  />
            <Button Content="Search" Width="75" Height="25" Margin="10,5" VerticalAlignment="Center" 
                    Command="{Binding SearchCommand}" Style="{StaticResource ButtonStyle}"/>
        </StackPanel>
        <DataGrid AutoGenerateColumns="False" x:Name="master" CanUserAddRows="False" CanUserDeleteRows="True" Grid.Row="1"
                  ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}" >
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding Path=CustomerId}" Header="Customer ID" Width="80" />
                <DataGridTextColumn x:Name="companyNameColumn" Binding="{Binding Path=CompanyName,ValidatesOnDataErrors=True}" Header="Company Name" Width="300" />
                <DataGridTextColumn x:Name="cityColumn" Binding="{Binding Path=City}" Header="City" Width="100" />
                <DataGridTextColumn x:Name="countryColumn" Binding="{Binding Path=Country}" Header="Country" Width="100" />
            </DataGrid.Columns>
        </DataGrid>
        <Grid DataContext="{Binding SelectedCustomer}" Grid.Row="2">
            <Grid Name="grid1" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock Text="Customer Id:" Grid.Column="0" Grid.Row="0"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="0"   Margin="3" Name="customerIdTextBox" Text="{Binding Path=CustomerId, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Company Name:" Grid.Column="0" Grid.Row="1"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="1"   Margin="3" Name="companyNameTextBox" Text="{Binding Path=CompanyName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Contact Name:" Grid.Column="0" Grid.Row="2"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="2"   Margin="3" Name="contactNameTextBox" Text="{Binding Path=ContactName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Contact Title:" Grid.Column="0" Grid.Row="3"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="3"   Margin="3" Name="contactTitleTextBox" Text="{Binding Path=ContactTitle, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Address:" Grid.Column="0" Grid.Row="4" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="4" Margin="3" Name="addressTextBox" Text="{Binding Path=Address, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" />
                <TextBlock Text="City:" Grid.Column="0" Grid.Row="5"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="5"   Margin="3" Name="cityTextBox" Text="{Binding Path=City, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Postal Code:" Grid.Column="0" Grid.Row="6"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="6"   Margin="3" Name="postalCodeTextBox" Text="{Binding Path=PostalCode, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Region:" Grid.Column="0" Grid.Row="7"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="7"   Margin="3" Name="regionTextBox" Text="{Binding Path=Region, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Country:" Grid.Column="0" Grid.Row="8"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="8"   Margin="3" Name="countryTextBox" Text="{Binding Path=Country, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Phone:" Grid.Column="0" Grid.Row="9"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="9"   Margin="3" Name="phoneTextBox" Text="{Binding Path=Phone, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <TextBlock Text="Fax:" Grid.Column="0" Grid.Row="10"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="10"   Margin="3" Name="faxTextBox" Text="{Binding Path=Fax, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
            </Grid>
        </Grid>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" Grid.Row="3">
            <Button Width="75" Height="25" Margin="5" Content="Add" Command="{Binding AddCommand}" Style="{StaticResource ButtonStyle}"/>
            <Button Width="75" Height="25" Margin="5" Content="Remove" Command="{Binding RemoveCommand}" Style="{StaticResource ButtonStyle}"/>
            <Button Width="75" Height="25" Margin="5" Content="Save" Command="{Binding SaveCommand}" Style="{StaticResource ButtonStyle}"/>
        </StackPanel>
    </Grid>
</UserControl>
[/sourcecode]

We can create a default resource dictionary and call it DefaultDict.xaml:

[sourcecode language='xml' ]
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:CustomerApp">
    <SolidColorBrush x:Key="PageForeground" Color="Black" />
    <SolidColorBrush x:Key="PageBackground" Color="AliceBlue" />
    <Style x:Key="ButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="BlanchedAlmond" />
        <Setter Property="Margin" Value="10,0" />
    </Style>
</ResourceDictionary>
[/sourcecode]

And add this dictionary to App.xaml:

[sourcecode language='xml' ]
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="DefaultDict.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>
[/sourcecode]

When we run the application, the default style is applied to the view:

image

Now, we want to apply dynamic styles to the view, so we can create loose files with new resource dictionaries. In the project, create a new folder and name it Styles. In this folders, add three files and name them as Blue.xaml, Red.xaml and Yellow.xaml:

[sourcecode language='xml' ]
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:CustomerApp">
    <SolidColorBrush x:Key="PageForeground" Color="White" />
    <SolidColorBrush x:Key="PageBackground" Color="Navy" />
    <Style x:Key="ButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="Azure" />
        <Setter Property="Margin" Value="10,0" />
    </Style>
    <Style TargetType="TextBlock" >
        <Setter Property="FontFamily" Value="Arial" />
    </Style>
</ResourceDictionary>
[/sourcecode]
[sourcecode language='xml' ]
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:CustomerApp">
    <SolidColorBrush x:Key="PageForeground" Color="White" />
    <SolidColorBrush x:Key="PageBackground" Color="Red" />
    <Style x:Key="ButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="LightPink" />
        <Setter Property="Margin" Value="10,0" />
    </Style>
    <Style TargetType="TextBlock" >
        <Setter Property="FontFamily" Value="Times New Roman" />
    </Style>
</ResourceDictionary>
[/sourcecode]
[sourcecode language='xml' ]
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:CustomerApp">
    <SolidColorBrush x:Key="PageForeground" Color="Black" />
    <SolidColorBrush x:Key="PageBackground" Color="Yellow" />
    <Style x:Key="ButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="BlanchedAlmond" />
        <Setter Property="Margin" Value="10,0" />
    </Style>
    <Style TargetType="TextBlock" >
        <Setter Property="FontFamily" Value="Comic Sans MS" />
    </Style>
</ResourceDictionary>
[/sourcecode]

And set their Build Action to None and Copy to output directory to “Copy if newer”. As you can see, these files are very similar to the default dictionary: they override some colors and styles and add a new one, setting the font for all TextBlocks.

Then, add a new Combobox in the main window, to select the new styles:

[sourcecode language='xml' ]
<ComboBox x:Name="StylesCombo" Height="30" Width="200" 
            SelectionChanged="SelectedStyleChanged" HorizontalAlignment="Right" Margin="5" />
[/sourcecode]

The SelectionChanged event handler for this box is:

[sourcecode language='csharp' ]
private void SelectedStyleChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count == 0)
            return;
        using (FileStream fs = new FileStream($"Styles\\{e.AddedItems[0]}", FileMode.Open))
        {
            var dict = XamlReader.Load(fs) as ResourceDictionary;
            if (dict != null)
            {
                Application.Current.Resources.MergedDictionaries.Add(dict);
            }
        }
}
[/sourcecode]

When you run the program and try to change the styles, you will see a strange thing:

image

The font that was not defined in the default style is changed, but the colors have not changed. This happens because we have set the styles as Static, using StaticResource when we applied the styles. In order for the style be applied dynamically, we must apply it using DynamicResource. When we change all StaticResource to DynamicResource in the master/detail file, we get this:

image

All the colors and styles have changed.

Conclusions

As you can see, WPF gives you a great flexibility to change you views dynamically. You can change the entire view, restyle all controls or only change the style of part of the controls. Everything can be done at runtime, and you can load the files from anywhere you want: loose files in a selected folder, from a database or from a request to a server, you can load the XAML as a string, parse and load it into your program.

The files for this project are in https://github.com/bsonnino/LoadThemes (runtime themes) and https://github.com/bsonnino/DynamicXamlViews (dynamic styles).

Most of the time, our design is fixed and we don’t have to change it while it’s executing, but sometimes this is not true: we need to load the files dynamically, due to a lot of factors:

  • We have the same business rules for the project but every client needs a different UI
  • The design can be different, depending on some conditions
  • We are not sure of the design and want to make changes at runtime and see how they work

When this happens, WPF is a huge help, as it can load its design dynamically and load the XAML files at runtime.

Preparing the application

When we want to load the XAML dynamically, we must ensure that all business rules are separated from the UI – we are only loading a new UI, the business rules remain the same. A good fit for that is the MVVM pattern. With it, the business rules are completely separated from the UI and you can use Data Binding to bind the data to the UI. So, when you are designing a dynamic application you should use the MVVM pattern.

One other preparation is to know how will your application be designed: will it run on a single window, changing the content, or will it have multiple windows? If it runs on a single window, you should create a shell with a content control and load all dynamic files in that control. If the application will have multiple windows, you should create a new window every time there is new content.

Once you have the application ready, with the business rules separated from the UI and the kind of UI defined, you can load your XAML dynamically.

Loading the XAML Dynamically

WPF has a simple way to load XAML dynamically. It has a class, XamlReader that has methods to load XAML dynamically and instantiate a new control. It can be used like this:

var mainContent = XamlReader.Parse(@"<Button Width=""85"" Height=""35"" Content=""Test""
    xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
    xmlns:x = ""http://schemas.microsoft.com/winfx/2006/xaml"" />") as Button;
MainGrid.Children.Add(mainContent);

In this example, we are using the Parse method to a XAML component from a string. We could load a XAML file dynamically using the Load method:

using (FileStream fs = new FileStream("Button.xaml", FileMode.Open))
{
  mainContent = XamlReader.Load(fs) as Button;
  MainGrid.Children.Add(mainContent);
}

Note that both Load and Parse return an object and that must be typecasted to the real type of the control.

Creating a real world dynamic application

In a real world application, we can have different uses to this feature:

  • Use the same data with different layouts – in this case, we have a single viewmodel and the views connect to the same data, with different layouts
  • Use different formatting options to the same view. This is used when you want to customize the view presentation (color, layout) for different clients

For the first usage, you must create completely different views for the same data and use the MVVM pattern to connect to the data.

For the second usage, you can use the same views, but connect to different Resource Dictionaries, so the new styles can be implemented. In this post, we will implement different views for the same viewmodel.

The project I’ll be using will use a Customers list, loaded from an XML file. The app uses the MVVM pattern (I’ve chosen not to use any MVVM framework, but a simple homemade MVVM framework, with a base ViewModelBase class and a RelayCommand class, to build the infrastructure).

The Customer’s list is loaded in the CustomersViewModel, which has many properties (and commands) to interact with the views:

public class CustomersViewModel : ViewModelBase
{
    readonly CustomerRepository customerRepository = new CustomerRepository();
    private readonly ObservableCollection<CustomerViewModel> customers;

    public CustomersViewModel()
    {
        var customerViewModels = customerRepository.Customers.Select(c => new CustomerViewModel(c));
        customers = new ObservableCollection<CustomerViewModel>(customerViewModels);
        customerView = (CollectionView)CollectionViewSource.GetDefaultView(customers);
    }

    private CustomerViewModel selectedCustomer;
    public CustomerViewModel SelectedCustomer
    {
        get { return selectedCustomer; }
        set
        {
            selectedCustomer = value;
            RaisePropertyChanged("SelectedCustomer");
        }
    }

    private ICommand goFirstCommand;
    public ICommand GoFirstCommand => goFirstCommand ?? (goFirstCommand = new RelayCommand(GoFirst));

    private void GoFirst(object obj)
    {
        customerView.MoveCurrentToFirst();
        SelectedCustomer = (CustomerViewModel)customerView.CurrentItem;
    }

    private ICommand goPrevCommand;
    public ICommand GoPrevCommand => goPrevCommand ?? (goPrevCommand = new RelayCommand(GoPrev));

    private void GoPrev(object obj)
    {
        customerView.MoveCurrentToPrevious();
        SelectedCustomer = (CustomerViewModel)customerView.CurrentItem;
    }

    private ICommand goNextCommand;
    public ICommand GoNextCommand => goNextCommand ?? (goNextCommand = new RelayCommand(GoNext));

    private void GoNext(object obj)
    {
        customerView.MoveCurrentToNext();
        SelectedCustomer = (CustomerViewModel)customerView.CurrentItem;
    }

    private ICommand goLastCommand;
    public ICommand GoLastCommand => goLastCommand ?? (goLastCommand = new RelayCommand(GoLast));

    private void GoLast(object obj)
    {
        customerView.MoveCurrentToLast();
        SelectedCustomer = (CustomerViewModel)customerView.CurrentItem;
    }
    private string searchText;
    public string SearchText
    {
        get { return searchText; }
        set
        {
            searchText = value;
            RaisePropertyChanged("SearchText");
        }
    }

    public ObservableCollection<CustomerViewModel> Customers
    {
        get { return customers; }
    }

    private ICommand addCommand;
    public ICommand AddCommand
    {
        get { return addCommand ?? (addCommand = new RelayCommand(AddCustomer, null)); }
    }

    private void AddCustomer(object obj)
    {
        var customer = new Customer();
        var customerViewModel = new CustomerViewModel(customer);
        customers.Add(customerViewModel);
        customerRepository.Add(customer);
        SelectedCustomer = customerViewModel;
    }

    private ICommand removeCommand;
    public ICommand RemoveCommand
    {
        get {
            return removeCommand ??
                   (removeCommand = new RelayCommand(RemoveCustomer, c => SelectedCustomer != null));
        }
    }

    private void RemoveCustomer(object obj)
    {
        customerRepository.Remove(SelectedCustomer.Customer);
        customers.Remove(SelectedCustomer);
        SelectedCustomer = null;
    }

    private ICommand saveCommand;
    public ICommand SaveCommand
    {
        get { return saveCommand ?? (saveCommand = new RelayCommand(SaveData, null)); }
    }

    private void SaveData(object obj)
    {
        customerRepository.Commit();
    }

    private ICommand searchCommand;
    private ICollectionView customerView;

    public ICommand SearchCommand
    {
        get
        {
            if (searchCommand == null)
                searchCommand = new RelayCommand(SearchData, null);
            return searchCommand;
        }
    }

    private void SearchData(object obj)
    {
        customerView.Filter = !string.IsNullOrWhiteSpace(SearchText) ?
            c => ((CustomerViewModel)c).Country.ToLower()
                           .Contains(SearchText.ToLower()) :
            (Predicate<object>)null;
    }
}

The main property is Customer, that has the list of CustomerViewModels that will be shown in the master views and SelectedCustomer, that has the selected customer that will be edited in the details views. There are a lot of commands to filter the list, move the selected record, add, remove and save the data.

The CustomerViewModel class is:

public class CustomerViewModel : ViewModelBase
{
    public Customer Customer { get; private set; }


    public CustomerViewModel(Customer cust)
    {
        Customer = cust;
    }


    public string CustomerId
    {
        get { return Customer.CustomerId; }
        set
        {
            Customer.CustomerId = value;
            RaisePropertyChanged("CustomerId");
        }
    }

    public string CompanyName
    {
        get { return Customer.CompanyName; }
        set
        {
            Customer.CompanyName = value;
            RaisePropertyChanged("CompanyName");
        }
    }

    public string ContactName
    {
        get { return Customer.ContactName; }
        set
        {
            Customer.ContactName = value;
            RaisePropertyChanged("ContactName");
        }
    }

    public string ContactTitle
    {
        get { return Customer.ContactTitle; }
        set
        {
            Customer.ContactTitle = value;
            RaisePropertyChanged("ContactTitle");
        }
    }

    public string Region
    {
        get { return Customer.Region; }
        set
        {
            Customer.Region = value;
            RaisePropertyChanged("Region");
        }
    }

    public string Address
    {
        get { return Customer.Address; }
        set
        {
            Customer.Address = value;
            RaisePropertyChanged("Address");
        }
    }

    public string City
    {
        get { return Customer.City; }
        set
        {
            Customer.City = value;
            RaisePropertyChanged("City");
        }
    }

    public string Country
    {
        get { return Customer.Country; }
        set
        {
            Customer.Country = value;
            RaisePropertyChanged("Country");
        }
    }

    public string PostalCode
    {
        get { return Customer.PostalCode; }
        set
        {
            Customer.PostalCode = value;
            RaisePropertyChanged("PostalCode");
        }
    }

    public string Phone
    {
        get { return Customer.Phone; }
        set
        {
            Customer.Phone = value;
            RaisePropertyChanged("Phone");
        }
    }

    public string Fax
    {
        get { return Customer.Fax; }
        set
        {
            Customer.Fax = value;
            RaisePropertyChanged("Fax");
        }
    } 
}

It doesn’t do anything but expose the Customer’s properties. With this infrastructure in place, I can create my view loading. I did this in the code behind, the code is very simple, it just sets the data context for the window and loads the correct file, depending on the selection of a combobox:

public MainWindow()
{
    InitializeComponent();
    DataContext = new CustomersViewModel();
}

private void SelectedViewChanged(object sender, SelectionChangedEventArgs e)
{
    var viewIndex = (sender as ComboBox).SelectedIndex;
    FrameworkElement view = null;
    switch (viewIndex)
    {
        case 0:
            view = LoadView("masterdetail.xaml");
            break;
        case 1:
            view = LoadView("detail.xaml");
            break;
        case 2:
            view = LoadView("master.xaml");
            break;
    }
    MainContent.Content = view;
}

private FrameworkElement LoadView(string fileName)
{
    using (FileStream fs = new FileStream(fileName, FileMode.Open))
    {
        return XamlReader.Load(fs) as FrameworkElement;
    }
}

The XAML for the main window is:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ComboBox Height="30" Width="200" SelectionChanged="SelectedViewChanged" HorizontalAlignment="Left" Margin="5">
        <ComboBoxItem>Master/Detail</ComboBoxItem>
        <ComboBoxItem>Detail</ComboBoxItem>
        <ComboBoxItem>Master</ComboBoxItem>
    </ComboBox>
    <ContentControl Grid.Row="1" x:Name="MainContent"/>
</Grid>

As you can see, the main window is very simple, it just has the combobox to select the view and a ContentControl, where the new view is loaded. An example of the dynamic view is the Master/Detail view:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             mc:Ignorable="d"
             d:DesignHeight="600" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="40" />
            <RowDefinition Height="*" />
            <RowDefinition Height="2*" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Country" VerticalAlignment="Center" Margin="5"/>
            <TextBox Height="25"
                     VerticalAlignment="Center" Margin="5,3" Width="250" Text="{Binding SearchText, Mode=TwoWay}"  />
            <Button Content="Search" Width="75" Height="25" Margin="10,5" VerticalAlignment="Center" 
                    Command="{Binding SearchCommand}" />
        </StackPanel>
        <DataGrid AutoGenerateColumns="False" x:Name="master" CanUserAddRows="False" CanUserDeleteRows="True" Grid.Row="1"
                  ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}" >
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding Path=CustomerId}" Header="Customer ID" Width="80" />
                <DataGridTextColumn x:Name="companyNameColumn" Binding="{Binding Path=CompanyName,ValidatesOnDataErrors=True}" Header="Company Name" Width="300" />
                <DataGridTextColumn x:Name="cityColumn" Binding="{Binding Path=City}" Header="City" Width="100" />
                <DataGridTextColumn x:Name="countryColumn" Binding="{Binding Path=Country}" Header="Country" Width="100" />
            </DataGrid.Columns>
        </DataGrid>
        <Grid DataContext="{Binding SelectedCustomer}" Grid.Row="2">
            <Grid Name="grid1" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Label Content="Customer Id:" Grid.Column="0" Grid.Row="0"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="0"   Margin="3" Name="customerIdTextBox" Text="{Binding Path=CustomerId, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Company Name:" Grid.Column="0" Grid.Row="1"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="1"   Margin="3" Name="companyNameTextBox" Text="{Binding Path=CompanyName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Contact Name:" Grid.Column="0" Grid.Row="2"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="2"   Margin="3" Name="contactNameTextBox" Text="{Binding Path=ContactName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Contact Title:" Grid.Column="0" Grid.Row="3"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="3"   Margin="3" Name="contactTitleTextBox" Text="{Binding Path=ContactTitle, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Address:" Grid.Column="0" Grid.Row="4" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="4" Margin="3" Name="addressTextBox" Text="{Binding Path=Address, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" />
                <Label Content="City:" Grid.Column="0" Grid.Row="5"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="5"   Margin="3" Name="cityTextBox" Text="{Binding Path=City, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Postal Code:" Grid.Column="0" Grid.Row="6"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="6"   Margin="3" Name="postalCodeTextBox" Text="{Binding Path=PostalCode, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Region:" Grid.Column="0" Grid.Row="7"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="7"   Margin="3" Name="regionTextBox" Text="{Binding Path=Region, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Country:" Grid.Column="0" Grid.Row="8"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="8"   Margin="3" Name="countryTextBox" Text="{Binding Path=Country, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Phone:" Grid.Column="0" Grid.Row="9"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="9"   Margin="3" Name="phoneTextBox" Text="{Binding Path=Phone, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Fax:" Grid.Column="0" Grid.Row="10"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="10"   Margin="3" Name="faxTextBox" Text="{Binding Path=Fax, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
            </Grid>
        </Grid>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" Grid.Row="3">
            <Button Width="75" Height="25" Margin="5" Content="Add" Command="{Binding AddCommand}"/>
            <Button Width="75" Height="25" Margin="5" Content="Remove" Command="{Binding RemoveCommand}"/>
            <Button Width="75" Height="25" Margin="5" Content="Save" Command="{Binding SaveCommand}" />
        </StackPanel>
    </Grid>
</UserControl>

There are some things to note, here:

  • There is no x:Class attribute in the UserControl
  • There is no code behind at all for the XAML. As this is a loose file, there should not be any cs file tied to it (and that’s a reason for not having the x:Class attribute)
  • I added the XAML files to the project, setting the Build Action to None and the Copy to Output Directory to Copy If Newer. This is an optional step, the files don’t need to be added to the project, they only need to be available on the executable directory at runtime

With this setting, you can run the application and get something like in the figure below:

clip_image002

You can see that everything works, both the data bindings and the commands. These files can be changed at will and they will be linked to the CustomersViewModel.

Conclusions

This kind of structure is very flexible and easy to use. Some uses I envision for it is to create different views for the same data, use layout customization for different clients, allow designers to be really free regarding to the design of the app, or create globalized apps (you can have different directories for each language, and each directory can have a translated view).

The next article will show subtler changes, when you only want to change the style of the controls.

The full code for the article is in https://github.com/bsonnino/DynamicXamlViews

After some time, I needed to analyze the disk space of my C: drive and used the program I have developed in my previous post and I got an “UnauthorizedAccessException”. After some thinking, I realized that was happening because of some paths that I have not access unless I am an admin.

Running as admin is not a good thing to do and, so, I thought of other ways to get the disk space. Soon, I realized that using GetFiles for all directories, although it was the easiest way to get the information, wasn’t the way to do it: at some point, there would be some path with no access and that exception would be thrown.

If I was using Powershell, I could use the -ErrorAction “SilentlyContinue” to continue without an error message, but there is no such thing in the GetFiles method (here is a suggestion for the .NET Framework designers Smile).

So, I had to enumerate all files, directory by directory and process any exception thrown. I came up to a recursive function like this one:

[sourcecode language=”csharp” padlinenumbers=”true”]
private List<FileInfo> GetFilesInDirectory(string directory)
{
    var files = new List<FileInfo>();
    try
    {
        var directories = Directory.GetDirectories(directory);
        try
        {
            var di = new DirectoryInfo(directory);
            files.AddRange(di.GetFiles("*"));
        }
        catch
        {
        }
        foreach (var dir in directories)
        {
            files.AddRange(GetFilesInDirectory(Path.Combine(directory, dir)));
        }
    }
    catch
    {
    }
   
    return files;
}
[/sourcecode]

Initially, I get all directories in the selected path, then I get all files for that directory and process the subdirectories recursively. All exceptions are caught and swallowed, so the processing doesn’t stop if I don’t have access to the folder. As I made this change, I made another one: the previous code was blocking the program while I was enumerating the files, so I used a Task to enumerate the files in the background:

[sourcecode language=”csharp”]
List<FileInfo> files = null;
await Task.Factory.StartNew( () =>
  files = GetFilesInDirectory(selectedPath)
    .Where(f => f.Length >= minSize)
    .OrderByDescending(f => f.Length)
    .ToList());
[/sourcecode]

That way, the enumeration doesn’t block the UI and the program remains responsible all times.

With these changes, everything was working fine and I had a program that could analyze my disk space, even if I had no access to all folder in the disk.

The full source code for the project is in https://github.com/bsonnino/DiskAnalysis

Some time ago, I’ve written an article about analyzing disk space using Excel and Powershell. While these tools are very easy to use and very flexible, that requires some organization: you must run a Powershell script, open the resulting file in Excel and run the analysis. To do that, I prefer to run a single command and have the data on my hands with no extra operations. So, I decided to create a WPF program to get the disk data and show it.

Getting Disk Information

The way to get disk information on .NET is to use the method GetFiles of the DirectoryInfo class, like this:

[sourcecode language=”csharp” padlinenumbers=”true”]
var di = new DirectoryInfo(selectedPath);
var files = di.GetFiles("*",SearchOption.AllDirectories);
[/sourcecode]

Then, we can add the files to a DataGrid and show them, but I’d like to get only the largest files and just some data. The best option for that is to use Linq. So, I decided to use Linq to get the same info and show it on the window.

The first step is to get the initial folder and start the enumeration. WPF doesn’t have a native folder selection , but that can be solved with the brave open source developers out there. I installed WPFFolderBrowser (http://wpffolderbrowser.codeplex.com/) using the Nuget Package Manager (just right click on the References node in the solution explorer and select Manage NuGet packages and type WPFFolderBrowser on the search box).

Then, I added the basic UI for the window. I used a TabControl with three tabs and a button at the top to select the folder and start the search:

[sourcecode language=”text” padlinenumbers=”true”]
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="5">
        <TextBlock Text="Minimum size:" VerticalAlignment="Center" Margin="0,0,5,0"/>
        <TextBox Width="150" x:Name="MinSizeBox" Text="1048576" VerticalContentAlignment="Center"/>
    </StackPanel>
    <Button Width="85" Height="30" Content="Start" Click="StartClick"/>
    <TabControl Grid.Row="1">
        <TabItem Header="File Data">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="30"/>
                </Grid.RowDefinitions>
                <ListBox x:Name="FilesList">

                </ListBox>
                <StackPanel Grid.Row="1" Orientation="Horizontal">
                    <TextBlock x:Name="TotalFilesText" Margin="5,0" VerticalAlignment="Center"/>
                    <TextBlock x:Name="LengthFilesText" Margin="5,0" VerticalAlignment="Center"/>
                </StackPanel>
            </Grid>
        </TabItem>
        <TabItem Header="Extensions">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <ListBox x:Name="ExtList">

                </ListBox>
            </Grid>
        </TabItem>
        <TabItem Header="ABC Curve">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <ListBox x:Name="AbcList">

                </ListBox>
            </Grid>
        </TabItem>
    </TabControl>
</Grid>
[/sourcecode]

image

The code for the start button is:

[sourcecode language=”csharp”]
private void StartClick(object sender, RoutedEventArgs e)
{
    var fbd = new WPFFolderBrowserDialog();
    if (fbd.ShowDialog() != true)
        return;
    var selectedPath = fbd.FileName;
    var di = new DirectoryInfo(selectedPath);
    Int64 minSize;
    if (!Int64.TryParse(MinSizeBox.Text, out minSize))
        return;
    var files = di.GetFiles("*", SearchOption.AllDirectories)
        .Where(f => f.Length >= minSize)
        .Select(f => new {f.Name, f.Length, f.DirectoryName, f.FullName, f.Extension})
        .OrderByDescending(f => f.Length)
        .ToList();
    TotalFilesText.Text = $"# Files: {files.Count}";
    LengthFilesText.Text = $"({totalSize:N0} bytes)";
    FilesList.ItemsSource = files;
}
[/sourcecode]

This code shows the folder selection dialog. If the user selects a file, it creates a DirectoryInfo and then uses GetFiles to enumerate the files in the folder and its subfolders. I am using Linq to select only the files with size greater than the minimum size, select only the data I want and sort the resulting files by length. At the end, I am converting the enumeration to a list. I am doing that because this list will be traversed some times to get the data by extension or the ABC curve, and I wanted to avoid multiple enumeration.

If you run this code and click the start button, you will see that, after some time, the data is shown in the main window:

image

But that’s not what we want. We want a better display for the data, so we need to create an ItemTemplate for the list:

[sourcecode language=”text”]
<ListBox x:Name="FilesList">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding FullName}"/>
                <TextBlock Text="{Binding Length, StringFormat=N0}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
[/sourcecode]

And now we have the data shown in a better way:

image

We can use Linq to fill the other tabs:

[sourcecode language=”csharp”]
private void StartClick(object sender, RoutedEventArgs e)
{
    var fbd = new WPFFolderBrowserDialog();
    if (fbd.ShowDialog() != true)
        return;
    var selectedPath = fbd.FileName;
    var di = new DirectoryInfo(selectedPath);
    Int64 minSize;
    if (!Int64.TryParse(MinSizeBox.Text, out minSize))
        return;
    var files = di.GetFiles("*", SearchOption.AllDirectories)
        .Where(f => f.Length >= minSize)
        .Select(f => new {f.Name, f.Length, f.DirectoryName, f.FullName, f.Extension})
        .OrderByDescending(f => f.Length)
        .ToList();
    var totalSize = files.Sum(f => f.Length);
    TotalFilesText.Text = $"# Files: {files.Count}";
    LengthFilesText.Text = $"({totalSize:N0} bytes)";
    FilesList.ItemsSource = files;
    ExtList.ItemsSource = files.GroupBy(f => f.Extension)
        .Select(g => new {Extension = g.Key, Quantity = g.Count(), Size = g.Sum(f => f.Length)})
        .OrderByDescending(t => t.Size);
    var tmp = 0.0;
    AbcList.ItemsSource = files.Select(f =>
        {
            tmp += f.Length;
            return new {f.Name, Percent = tmp/totalSize*100};
        });
}
[/sourcecode]

For the extension list, we made a grouping by extension and selected the data we want, getting the quantity and total length for each group. For the ABC curve, we used a temporary value to get the total length for the files larger than the selected one. If you run the program, you will see that the data is not formatted. We can format the data by using the ItemTemplate for the lists:

[sourcecode language=”text”]
<TabItem Header="Extensions">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <ListBox x:Name="ExtList">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Extension}" Width="100" Margin="5"/>
                        <TextBlock Text="{Binding Quantity, StringFormat=N0}" Width="100" Margin="5" TextAlignment="Right"/>
                        <TextBlock Text="{Binding Size,StringFormat=N0}" Width="150" Margin="5" TextAlignment="Right"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</TabItem>
<TabItem Header="ABC Curve">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <ListBox x:Name="AbcList">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Width="250" Margin="5"/>
                        <TextBlock Text="{Binding Percent, StringFormat=N2}" Width="100" TextAlignment="Right"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</TabItem>
[/sourcecode]

When we run the program, we can see the data for the extensions and the ABC Curve:

image

image

Now we need only to add the charts to the window.

Adding Charts

We will add the WPF Toolkit (https://wpf.codeplex.com/releases/view/40535) to get the charts, but, as it doesn’t seem to be maintained anymore, I’ll use a fork of this project that has a NuGet package and is at https://github.com/dotnetprojects/WpfToolkit.

To add the Pie Chart on the Extensions page, we must add this XAML code:

[sourcecode language=”text”]
<chartingToolkit:Chart Title="Extensions"
               Grid.Row="0"
               Grid.Column="1"
               HorizontalAlignment="Stretch"
               VerticalAlignment="Stretch">
    <chartingToolkit:PieSeries DependentValuePath="Size"
                       IndependentValuePath="Extension"
                       x:Name="ExtSeries" />
</chartingToolkit:Chart>
[/sourcecode]

We must add the namespace at the top of the file:

[sourcecode language=”text”]
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=DotNetProjects.DataVisualization.Toolkit"
[/sourcecode]

When we run the application, we see the data and the chart, side by side:

image

For the ABC Curve, we must add this XAML:

[sourcecode language=”text”]
<chartingToolkit:Chart Title="Abc Curve"
               Grid.Row="0"
               Grid.Column="1"
               HorizontalAlignment="Stretch"
               VerticalAlignment="Stretch">
    <chartingToolkit:Chart.Axes>
        <chartingToolkit:CategoryAxis Orientation="X" ShowGridLines="False"  />
        <chartingToolkit:LinearAxis Title="% Size"
                                 Orientation="Y"
                                 ShowGridLines="True" />
    </chartingToolkit:Chart.Axes>
    <chartingToolkit:LineSeries DependentValuePath="Percent"
                        IndependentValuePath="Item"
                        x:Name="AbcSeries" />
</chartingToolkit:Chart>
[/sourcecode]

Then, we change the source code to assign the data for the series:

[sourcecode language=”csharp”]
private void StartClick(object sender, RoutedEventArgs e)
{
    var fbd = new WPFFolderBrowserDialog();
    if (fbd.ShowDialog() != true)
        return;
    var selectedPath = fbd.FileName;
    var di = new DirectoryInfo(selectedPath);
    Int64 minSize;
    if (!Int64.TryParse(MinSizeBox.Text, out minSize))
        return;
    var files = di.GetFiles("*", SearchOption.AllDirectories)
        .Where(f => f.Length >= minSize)
        .Select(f => new {f.Name, f.Length, f.DirectoryName, f.FullName, f.Extension})
        .OrderByDescending(f => f.Length)
        .ToList();
    var totalSize = files.Sum(f => f.Length);
    TotalFilesText.Text = $"# Files: {files.Count}";
    LengthFilesText.Text = $"({totalSize:N0} bytes)";
    FilesList.ItemsSource = files;
    var extensions = files.GroupBy(f => f.Extension)
        .Select(g => new {Extension = g.Key, Quantity = g.Count(), Size = g.Sum(f => f.Length)})
        .OrderByDescending(t => t.Size).ToList();
    ExtList.ItemsSource = extensions;
    ExtSeries.ItemsSource = extensions;
    var tmp = 0.0;
    var abcData = files.Select(f =>
    {
        tmp += f.Length;
        return new {f.Name, Percent = tmp/totalSize*100};
    }).ToList();
    AbcList.ItemsSource = abcData;
    AbcSeries.ItemsSource = abcData.OrderBy(d => d.Percent).Select((d,i) => new {Item = i, d.Percent});
}
[/sourcecode]

When we run the code, we can see the ABC curve.

image

Conclusion

As you can see, it’s fairly easy to get the disk space in a WPF program. With some Linq queries, we can analyze the data the way we want, and then present it using the WPF visualization.

The full source code for this project is at https://github.com/bsonnino/DiskAnalysis

Nowadays it’s very common to receive JSON data from many sources and to process it in our programs. I have the same problem and, sometimes, I also have to debug the received data to see what’s coming from the server. Many times, the data is minimized and it’s very difficult to analyze what’s coming. On the other side, I have formatted JSON data and want to save space, minimizing it. You can go to online sites (just do a search for “json formatter” in your preferred search engine) and format the JSON data, to get the formatted output.

But, as a developer, I wanted to create a program that does that. So I decided to create a Windows UWP program to process the JSON data. The first step is to create a UWP program in Visual Studio:

In MainPage.xaml, we add the two textboxes, one for the minified JSON and the other for the processed JSON:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="\*"/>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="\*"/>
   </Grid.ColumnDefinitions>
   <TextBox x:Name="MiniBox" Margin="5" AcceptsReturn="True" TextWrapping="Wrap"/>
   <StackPanel Grid.Column="1" VerticalAlignment="Center">
     <Button Width="85" Height="40" Content="Format >>" Margin="5" />
     <Button Width="85" Height="40" Content="<< Minify" Margin="5" />
   </StackPanel>
   <TextBox x:Name="FormBox" Margin="5" Grid.Column="2" AcceptsReturn="True" TextWrapping="Wrap"/>
</Grid>

To process the JSON data, we use the Newtonsoft Json.NET library (http://www.newtonsoft.com/json). We can add it using NuGet. In the Solution Explorer, right click in References and select “Manage NuGet packages” and add the library Newtonsoft.Json. Then, in MainPage.xaml, add the handler for the Click handler for the Format button:

<Button Width="90" Height="40" Content="Format >>"  Margin="5" Click="FormatJsonClick"/>

Select the Click event handler and press F12 to create the event handler in code and type this code:

private async void FormatJsonClick(object sender, RoutedEventArgs e)
{
    try
    {
        if (!string.IsNullOrWhiteSpace(MiniBox.Text))
        {
            FormBox.Text = JsonConvert.SerializeObject(
                JsonConvert.DeserializeObject(MiniBox.Text), Formatting.Indented);
        }
    }
    catch (JsonReaderException ex)
    {
        await new MessageDialog("Error parsing JSON: {ex.Message}").ShowAsync();
    }
}

We are using two functions of the library to format the data: DeserializeObject, to transform the string into an object and then SerializeObject to transform the object into a string again. This time, we use the Formatting.Indented to format the result and add it to the destination box. To minify the JSON, you must use the same procedure, but use Formatting.None:

private async void MinifyJsonClick(object sender, RoutedEventArgs e)
{
    try
    {
        if (!string.IsNullOrWhiteSpace(FormBox.Text))
        {
            MiniBox.Text = JsonConvert.SerializeObject(
                JsonConvert.DeserializeObject(FormBox.Text), Formatting.None);
        }
    }
    catch (JsonReaderException ex)
    {
        await new MessageDialog("Error parsing JSON: {ex.Message}").ShowAsync();
    }
}

If you run the program and paste some JSON into the first box and click Format, the formatted JSON is shown in the second box. http://blogs.msmvps.com/bsonnino/files/2016/11/image-7.png If you want to reverse the process, just click on the Minify button. Easy, no? Just one line of code and you have a Minifier/Formatter for JSON data. If you want to take a look at the source code for the project, you can go to https://github.com/bsonnino/ProcessJson

After finishing the series of articles about data structures (see here, here, here and here) I started to think about the GetItems method I had implemented in the Linked List and in the Tree. This method was iterating on the structure and retrieving the items as IEnumerable.

That was not what I was expecting of the class. I was expecting to replicate the .NET data structures, and they implemented IEnumerable<T>, instead of using a special method for getting the items. So I decided to implement IEnumerable<T> on the data structures.

In order to implement IEnumerable<T>, we must create two methods:

[sourcecode language='csharp' ]
public IEnumerator<T> GetEnumerator()

IEnumerator IEnumerable.GetEnumerator()
[/sourcecode]

In reality, we should create only one function:  the second one calls the first one and doesn’t need to be implemented. As the linked list already had the GetItems implemented, I only had to remove its code and add it to GetEnumerator:

[sourcecode language='csharp' ]
public IEnumerator<T> GetEnumerator()
{
    if (Count == 0)
        yield break;
    _currentItem = _top;
    while (_currentItem != null)
    {
        yield return _currentItem.Data;
        _currentItem = _currentItem.Next;
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}
[/sourcecode]

That was simple, and caused no troubles. For the stack, I hadn’t implemented the GetItems method, so I needed to create the enumerator for it, but it was really simple, as it’s very similar to the one in the linked list:

[sourcecode language='csharp' ]
public IEnumerator<T> GetEnumerator()
{
    if (Count == 0)
        yield break;
    _currentItem = _top;
    while (_currentItem != null)
    {
        yield return _currentItem.Data;
        _currentItem = _currentItem.Next;
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}
[/sourcecode]

When it came to add the IEnumerable in the stack implementation as a linked list, it was also easy:

[sourcecode language='csharp'  padlinenumbers='true']
public IEnumerator<T> GetEnumerator()
{
    return _linkedList.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}
[/sourcecode]

But then I wrote a unit test to test this feature:

[sourcecode language='csharp' ]
[TestMethod]
public void StackAddFiveItemsIterateInReverseOrder()
{
    var stack = new StackAsList<int>();
    for (int i = 0; i < 5; i++)
    {
        stack.Push(i);
    }
    var num = 4;
    foreach (var item in stack)
    {
        Assert.AreEqual(num, item);
        num--;
    }
}
[/sourcecode]

And it failed. The data was iterated in the normal order, and not in the reversed order (last pushed should be the first in the iteration). The problem was that I was inserting the data at the end and retrieving the last item. That worked fine, but it wasn’t a real stack: the items should be piled in the beginning of the list. So, the implementation should be changed:

[sourcecode language='csharp' ]
public void Push(T obj)
{
    _linkedList.InsertAt(0, obj);
}

public T Peek()
{
    if (_linkedList.Count == 0)
        throw (new InvalidOperationException("The stack is empty"));
    return _linkedList[0];
}

public T Pop()
{
    if (_linkedList.Count == 0)
        throw (new InvalidOperationException("The stack is empty"));
    var result = _linkedList[0];
    _linkedList.RemoveAt(0);
    return result;
}
[/sourcecode]

With these changes, the test passed. The code of GetEnumerator for the queue is the same as for the stack:

[sourcecode language='csharp' ]
public IEnumerator<T> GetEnumerator()
{
    if (Count == 0)
        yield break;
    _currentItem = _top;
    while (_currentItem != null)
    {
        yield return _currentItem.Data;
        _currentItem = _currentItem.Next;
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}
[/sourcecode]

For the tree, implementing IEnumerable was also easy, as we already had the GetItems method:

[sourcecode language='csharp' ]
public IEnumerator<T> GetEnumerator()
{
    if (_root == null)
        yield break;
    foreach (var data in VisitNode(_root))
        yield return data;
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}
[/sourcecode]

That way, we have implemented IEnumerable in all data structures we’ve developed. The source code for the data structures is in https://github.com/bsonnino/DataStructures