Tag Archives: wpf

Upgrade a .Net Framework WPF Application to .Net Core 3.x

One of the main things that was introduced as part of .Net Core 3 was the ability to upgrade your WinForms or WPF application to use Core. I have an example of such an application here. This was upgraded using the side-by-side method. If you are upgrading, you essentially have 2 options: Big Bang and Side-by-Side.

Big Bang Upgrade

This is the process by which you edit your csproj file on the framework app, and convert that file to use .Net Core 3. This means potentially less work, and is suited for a situation where you know that you will never need the original application again. For a personal project this may be fine, but realistically, it too big a risk for most companies, who would want the security of a gradual rollout, and the ability to fix bugs and make interim releases in the meantime.

Side-by-Side Upgrade

There are three ways to do this, but essentially, what you’re doing here is creating a second project that is running .Net Core. The benefit here is that the two applications can continue to run, and you can gradually discontinue the Framework app as and when you see fit. The work involved can be greater; but it varies depending on your methodology and requirements.

1. Copy and Paste

This is the simplest method: create a brand new .Net Core WPF application, and copy the entire contents of your directory across. You’ll need to convert the packages (covered later), but other than that, you should be good to go. Obviously, that depends hugely on the complexity of your project.

The downside here is that if you fix a bug, or make a change to one of these projects, you either need to do it twice, or have them get out of sync.

2. Two Projects One Directory

This seems to be Microsoft’s preferred approach: the idea being that you create a new .Net Core project inside the same directory as the .Net Framework app. This means that all the files just appear in your framework app. You’ll need to convert the packages, and exclude the csproj, and other .Net Framework specific files from the .Net Core project. This, and the following approach both give you the ability to change the code in both files simultaneously.

3. Two Projects Linked Files

This is my personal preference. You create your .Net Core project it a different directory and just link the files and directories. You get all the benefits of having the projects in the same directory, but without the hassle of trying to manage files being there that you don’t want.

The downside to this approach is that you need to include the files yourself.

Two Projects Linked Files Upgrade

The following steps, whilst for this particular approach, are not specific to it, unless stated.

1. Start by installing the UWP Workload in Visual Studio, assuming you haven’t already.

2. In your WPF Framework app, convert your packages.config, as that doesn’t exist in .Net Core:

3. Create a new project. Whilst this is specific to this approach, you will need a new project for any of the side-by-side methods.

For this method, the project needs to be in a different directory; my suggestion is that you put it inside the solution directory, under its own folder; for example, in the example above, you might create: WpfCoreApp1:

The directory structure might look like this:

4. Copy the package references from your packages.config directly into the new csproj file (following step 1, this should be a simple copy and paste).

5. Gut the new project by removing MainWindow.xaml and App.xaml (from here on in, all of the steps are specific to this method):

6. Edit the new csproj file. Depending on your directory structure, the following may differ, but broadly speaking you need the following code in your new csproj file:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <ApplicationDefinition Include="..\WpfApp1\App.xaml" Link="App.xaml">
      <Generator>MSBuild:Compile</Generator>
    </ApplicationDefinition>
    <Compile Include="..\WpfApp1\App.Xaml.cs" Link="App.Xaml.cs" />
    <Page Include="..\WpfApp1\MainWindow.xaml" Link="MainWindow.xaml">
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Compile Include="..\WpfApp1\MainWindow.Xaml.cs" Link="MainWindow.Xaml.cs" />
  </ItemGroup>
</Project>

If, for example, you were to have a directory that you wished to bring across, you could use something similar to the following:

  <ItemGroup>
    <Compile Include="..\WpfApp1\Helpers\**">
      <Link>Helpers\%(Filename)%(Extension)</Link>
    </Compile>
</ItemGroup>

That’s it – you should now be able to set your new .Net Core project as start-up and run it. The code is the same code as that running the Framework app, and should you change either, it will affect the other.

As an addendum, here is a little chart that I think should help you decide which approach to take for an upgrade:

MSix Packaging Project

Deployment is hard. Arguably, deployment is the reason the web apps have won over desktop apps. I’ve seen applications written as web apps that would have been easier, faster and more reliable written as desktop applications; however, the deployment problem is such a big factor, that these days, you have to have such a compelling reason to create a desktop application that they are almost unheard of.

With .Net Core 3, came a new attempt from Microsoft to make this whole deployment process easier. MSix allows you to take a Winforms or WPF application, and package it up in a very similar way to that used for UWP. You can then deploy these to the Windows Store; or, you can self host them, and deploy them from your own web-site.

Using an MSix does solve a number of deployment problems; for example, it pretty much picks up where ClickOnce left off. It guarantees that you’re not installing anything horrendous on your machine, because the packaging process sandboxes the application.

However, it is, by no means, perfect. This post covers how you can create a side-loaded deployment project. As a disclaimer, I used this post from James Montemagno as a base for this; however, there were a couple of parts where I got stuck with his post, so hopefully this fills in some gaps.

The post assumes that you have a WPF or WinForms application that you wish to deploy, and that you’re using Visual Studio.

1. Set runtime identifier:

<PropertyGroup>
    <!--Regular Settings-->
    <Platforms>AnyCPU;x64;x86</Platforms>
    <RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers>
  </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>

2. Create new Windows Application Packaging Project in your solution:

3. Name project:

4. Pick a minimum and target version:

5. Add reference to the project to deploy. That is, reference the project that you wish to be deployed by your packaging application:

6. Create App Packages (if you were publishing to the store, and you’d already created the app in the store, this is where you would diverge from these instructions):

7. Select Sideloading (if you were registering a store app for the first time, this is where you would diverge from these instructions):

8. Create a test certificate:

9. Set-up version and target platforms:

10. Create a distribution web site (https://docs.microsoft.com/en-us/windows/msix/app-installer/web-install-azure):

Contrary to what this screenshot may lead you to believe, don’t bother doing anything to it just yet – just create it so that you have the URL.

11. Deploy the app packages (you’ll need the URL from above):

12. Once you’ve published the app, install the test certificate to Local Machine -> Trusted Root Certificates. Obviously, were you to do this in real life, you’d need a kosher certificate.

13. FTP the entire contents of the AppPackages directory to the site. To find the FTP details, select the deployment centre:

From here, select FTP:

Once you select FTP, a Dashboard button will appear at the bottom of the screen, select that, and the FTP credentials should appear:

14. Create the default.html, as shown above; clearly, you’ll need your own link, but the format should be the same as mine.

That’s it – you should have a web-site with a link that allows you to install a desktop application locally (albeit you’ll need the certificate installed on the local machine).

References

https://montemagno.com/distributing-a-net-core-3-wpf-and-winforms-app-with-msix/

https://docs.microsoft.com/en-gb/windows/msix/app-installer/web-install-azure

https://www.advancedinstaller.com/install-test-certificate-from-msix.html

WPF Performance – TextBlock

WPF typically doesn’t have many performance issues and, where it does, this can usually be fixed by virtualisation. Having said that, even if you never need to use this, it’s useful to know that you can eek that last ounce of performance out of the system.

Here’s a basic program that contains a TextBlock:

<Window x:Class="TextBlockTest.MainWindow"
        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"
        xmlns:local="clr-namespace:TextBlockTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        x:Name="MainWindowView">
    <Grid>
        <ScrollViewer>
            <ItemsControl ItemsSource="{Binding BigList, ElementName=MainWindowView}" Margin="0,-1,0,1">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>
</Window>

Code behind:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TextBlockTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public ObservableCollection<string> BigList { get; set; }

        public MainWindow()
        {
            BigList = new ObservableCollection<string>();
            for (int i = 0; i <= 10000; i++)
            {
                BigList.Add($"Item {i}");
            }

            InitializeComponent();
        }
    }
}

Let’s, for a minute, imagine this is slow, and profile it:

The layout is taking most of the time. Remember that each control needs to be created, and remember that the TextBlock does slightly more than just display text:

The whole panel took 3.46s. Not terrible, performance, but can it be improved? The answer is: yes, it can! Very, very slightly.

Let’s create a Custom Control:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace FastTextBlock
{
   
    public class MyTextBlockTest : Control
    {
        private FormattedText _formattedText;

        static MyTextBlockTest()
        {
            //DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTextBlockTest), new FrameworkPropertyMetadata(typeof(MyTextBlockTest)));
        }

        public static readonly DependencyProperty TextProperty =
             DependencyProperty.Register(
                 "Text", 
                 typeof(string),
                 typeof(MyTextBlockTest), 
                 new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure,
                    (o, e) => ((MyTextBlockTest)o).TextPropertyChanged((string)e.NewValue)));

        private void TextPropertyChanged(string text)
        {
            var typeface = new Typeface(
                new FontFamily("Times New Roman"),
                FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);

            _formattedText = new FormattedText(
                text, CultureInfo.CurrentCulture,
                FlowDirection.LeftToRight, typeface, 15, Brushes.Black);
        }


        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            if (_formattedText != null)
            {
                drawingContext.DrawText(_formattedText, new Point());
            }
        }

        protected override Size MeasureOverride(Size constraint)
        {
            //return base.MeasureOverride(constraint);

            return _formattedText != null
            ? new Size(_formattedText.Width, _formattedText.Height)
            : new Size();
        }
    }
}

Here’s the new XAML:

    <Grid>
        <ScrollViewer>
            <ItemsControl ItemsSource="{Binding BigList, ElementName=MainWindowView}" Margin="0,-1,0,1">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <!--<TextBlock Text="{Binding}"/>-->
                        <controls:MyTextBlockTest Text="{Binding}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>

Shaves around 10ms off the time:

Even more time can be shaved by moving up an element (that is, inheriting from a more base class than `Control`. In fact, `Control` inherits from `FrameworkElement`:

public class MyTextBlockTest : FrameworkElement

Shaves another 10ms off:

Conclusion

Clearly, this isn’t a huge performance boost, and in 99% of use cases, this would be massively premature optimisation. However, the time that this really comes into its own is where you have a compound control (a control that consists of other controls), and you have lots of them (hundreds). See my next post for details.

References:

https://social.msdn.microsoft.com/Forums/en-US/94ddd25e-7093-4986-b8c8-b647924251f6/manual-rendering-of-a-wpf-user-control?forum=wpf

http://www.codemag.com/article/100023

http://stackoverflow.com/questions/20338044/how-do-i-make-a-custom-uielement-derived-class-that-contains-and-displays-othe

http://stackoverflow.com/questions/42494455/wpf-custom-control-inside-itemscontrol

WPF Performance Debugging

WPF is an interesting (and currently still active framework. How long that will continue depends, IMHO, largely on how well MS can bring UWP XAML to a state where people are happy to switch.

I recently investigated a performance problem in one of our WPF screens. After running a few analysis tools, including Prefix (which I’m finding increasingly my first port of call for this kind of thing), I came to the conclusion that the performance problem was with the screen itself.

Performance Profiler

You can reach this via:

Analyse -> Performance Profiler

You can actually run this against a compiled exe, a store app, or even a website. For my purposes, I ran it against the screen that I’d identified as being slow:

The bar graph above clearly marks out the points at which the app suddenly spikes, and the legends tells me that it’s caused by the layout. With this information, you can highlight relevant area:

Once I did this, I could instantly see that a very large number of controls were being created:

So, the problem here was that the client was going to the service and bringing back a huge volume of data, and as soon as this was bound to the screen, WPF was attempting to render the layout for thousands of controls immediately.

The Solution

So, the solution to this issue is to virtualise the ItemsControl. Whilst the standard items control will attempt the render the layout for every possible control bound to the underlying data, virtualising it allows to it only render those that are actually displayed on the screen. Here’s how you might achieve that:

                        <ItemsControl Grid.Row="1" ItemsSource="{Binding Path=MyObject.Data}"
                              Margin="10" BorderBrush="Black" BorderThickness="2" 
                                      VirtualizingPanel.VirtualizationMode="Recycling"
                                      VirtualizingPanel.IsVirtualizing="True"
                                      ScrollViewer.CanContentScroll="True">
                            <ItemsControl.Template>
                                <ControlTemplate>
                                    <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
                                        <ItemsPresenter/>
                                    </ScrollViewer>
                                </ControlTemplate>
                            </ItemsControl.Template>
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <VirtualizingStackPanel Orientation="Vertical" Margin="5" IsItemsHost="True" />
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>

Re-running the screen with the analyser reveals that we have now alleviated the spike in activity:

Summary

Obviously, there is a trade-off here; if you’re dealing with a screen that will be used extensively and change very infrequently, then you might decide it’s better to have the upfront hit (as the work still needs to be done). However, if you’re loading so much data that you’re in this situation, I would have thought it very unlikely that the end-user is ever going to want to actually see it all!

It’s also worth acknowledging here that this solution doesn’t actually speed anything up, just defers it. I’m not saying that’s a good or bad thing, but it is definitely a thing.

References

https://blogs.windows.com/buildingapps/2015/10/07/optimizing-your-xaml-app-for-performance-10-by-10/#4zjWfXrk69bTPpi0.97

https://blogs.msdn.microsoft.com/wpf/2015/01/16/new-ui-performance-analysis-tool-for-wpf-applications/

http://stackoverflow.com/questions/2783845/virtualizing-an-itemscontrol

https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx

Programmatically editing the hosts file

If you deal with multiple web servers, all with the same service addresses, but in different locations, you’ll find you’re constantly in your hosts file. It is not exactly an onerous task, changing the hosts file; typically, it resides in

C:\Windows\System32\drivers\etc

You have to be running as admin to make any changes, but other than that, it’s just a text file; mine looks like this:

# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a ‘#’ symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host

# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
127.0.0.1 http://www.myuri.test
#192.168.1.1 http://www.myuri.test

The important part for me are the last two lines. As you can see, I have two potential IP addresses for the same URL. I obviously could simply change this in the hosts file; which, as I said, is not exactly a difficult task. But what if there are another 25 possible I.P. addresses? You could certainly have comments, or you could use the following code to create a small utility to change this for you.

I’ve put all the code for this into a single XAML window (using the code behind); this means that I’ve tightly coupled the logic to the UI. I’m a big fan of the MVVM pattern, but there is such a thing as over thinking a problem, and given that this is changing a Windows text file, putting the code into some form of MVVM framework seems overkill.

The XAML looks like this:

<Window x:Class="ChangeHosts.MainWindow"
        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"
        xmlns:local="clr-namespace:ChangeHosts"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <StackPanel>
            <Button x:Name="LoadHosts" Click="LoadHosts_Click" Content="Load File"/>
            <ComboBox x:Name="comboHosts" ItemsSource="{Binding HostSelections}" SelectedIndex="{Binding HostSelectedIdx}"/>
            <Button x:Name="SetActiveHost" Click="SetActiveHost_Click" Content="Save Selection"/>
        </StackPanel>
    </Grid>
</Window>

As you can see, the first thing that I’ve done is set the data context to the code behind. Obviously, this means that the code-behind will need to implement INotifyPropertyChanged:

    public partial class MainWindow : Window, INotifyPropertyChanged
    {

We’re going to need a collection called HostSelections and an index to represent the selected item; here they are:

        private List<string> _hostSelections = new List<string>();
        public List<string> HostSelections
        {
            get { return _hostSelections; }
            set
            {
                _hostSelections = value;
                NotifyPropertyChanged();
            }
        }

        private int _previousSelectedIdx = -1;
        private int _hostSelectedIdx;
        public int HostSelectedIdx
        {
            get { return _hostSelectedIdx; }
            set
            {
                if (_hostSelectedIdx != _previousSelectedIdx || _previousSelectedIdx == -1)
                {
                    // Update previous value
                    _previousSelectedIdx = _hostSelectedIdx;

                    // Now update the selected index
                    _hostSelectedIdx = value;
                    NotifyPropertyChanged();
                }
            }
        }

The reason for tracking the previous value will become apparent in a minute. Other than that, there’s nothing really to see here. There are two event handlers required by the XAML; the first loads the hosts file:

        private async void LoadHosts_Click(object sender, RoutedEventArgs e)
        {
            await LoadHostFile();
        }

        private async Task LoadHostFile()
        {
            using (StreamReader sr = File.OpenText(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers/etc/hosts")))
            {
                string hosts = await sr.ReadToEndAsync();
                string[] entries = hosts.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
                HostSelections = entries.ToList();

                int idxSel = entries.Select((str, idx) => new { str, idx }).FirstOrDefault(e => !e.str.StartsWith("#")).idx;
                HostSelectedIdx = idxSel;
            }
        }

So, we parse the text and then set the bound collection to the list. Because the file is reloaded every time, there’s no need for an observable collection here. The next event handler sets the new active entry:

        private async void SetActiveHost_Click(object sender, RoutedEventArgs e)
        {
            await ChangeSelecteHost();
        }

        private async Task ChangeSelecteHost()
        {
            if (HostSelections[HostSelectedIdx].StartsWith("#"))
            {
                HostSelections[HostSelectedIdx] = HostSelections[HostSelectedIdx].TrimStart('#');
                HostSelections[_previousSelectedIdx] = $"#{HostSelections[_previousSelectedIdx]}";

                WriteHostsFile();
                await LoadHostFile();
            }
            else
            {
                MessageBox.Show("The selected line is already selected");
            }
        }

        private void WriteHostsFile()
        {
            string updatedHosts = string.Join(Environment.NewLine, HostSelections);

            File.WriteAllText(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), 
                "drivers/etc/hosts"), updatedHosts);
        }

As you can see, setting the previous index was needed here.

That’s it – the code works as is (I’m omitted the NotifyPropertyChanged boiler plate code).

Conclusion

I’ve uploaded this project to GitHub. Feel free to make suggestions, or submit a pull request.

Creating a speech bubble with rounded corners

Following on from an earlier post I decided that the speech bubble in that post would look more like a … bubble if the corners were rounded. The relevant geometry to achieve this is a Cubic Bezier Curve. The MSDN page is here.

The abbreviated form is C. It works with 3 parameters:

C[start co-ordinates] [middle co-ordinate] [final co-ordinate]

bezier

The following is the geometry for the speech bubble:

<PathGeometry x:Key="SpeechBubble">M 10,10 L20,0 L30,10 L190,10 C190,10 200,10 200,20 L200,90 C200,90 200,100 190,100 L10,100 C10,100 0,100 0,90 L0,20 C0,20 0,10 10,10 Z</PathGeometry>

The idea is that instead of drawing the line right to the corner, I go to within 10 pixels and curve round the corner.

roundedtooltip

Tooltip Speech Bubbles

I recently tried to introduce a concept of a tooltip that appeared as a speech bubble; that is, a box that has a pointer to the originating control.

Standing on the Shoulders of Giants

The following is a rundown of links that I used extensively during this investigation:

http://stackoverflow.com/questions/337181/how-do-i-create-a-custom-wpf-control-like-a-cartoon-bubble-with-constant-corners

http://stackoverflow.com/questions/337181/how-do-i-create-a-custom-wpf-control-like-a-cartoon-bubble-with-constant-corners

http://jobijoy.blogspot.co.uk/2008/12/xaml-balloon-comments-expression-blend.html

http://stackoverflow.com/questions/11446250/how-to-style-wpf-tooltip-like-a-speech-bubble

http://stevenhollidge.blogspot.co.uk/2012/04/custom-tooltip-and-popup.html

Possible Approaches

In investigating this, I created four separate projects; these essentially boiled down to three different approaches:
1. A styled tooltip with no arrow

tt1

2. A tooltip using the Expression Blend “Callout” method

tt2

3. A styled tooltip using the “PathGeometry” to define a pointer

tt3

(1) had the advantage that it looked much better, but doesn’t have a concept of an arrow to the source control. (2) was by far the easiest, but the arrow style makes it look a bit like a cartoon.

In the end I opted for (3), the solution looks a little like this:

    <Window.Resources>
        <LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#FFF" Offset="0.0"/>
                    <GradientStop Color="#EEE" Offset="1.0"/>
                </GradientStopCollection>
            </GradientBrush.GradientStops>
        </LinearGradientBrush>

        <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />
    
        <Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
            <Setter Property="OverridesDefaultStyle" Value="true" />
            <Setter Property="HorizontalOffset" Value="0" />
            <Setter Property="VerticalOffset" Value="0" />
            <Setter Property="Background" Value="GhostWhite" />
            <Setter Property="Foreground" Value="Gray" />
            <Setter Property="FontSize" Value="12" />
            <Setter Property="FontFamily" Value="Segoe UI" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ToolTip">
                        <Canvas Width="200" Height="100">
                            <Path x:Name="Container"
                          Canvas.Left="0"
                          Canvas.Top="0"
                          Margin="0"
                          Data="M 50,10 L60,0 L70,10 L100,10 L100,100 L0,100 L0,10 L50,10"
                          Fill="{TemplateBinding Background}"
                          Stroke="Black">
                                <Path.Effect>
                                    <DropShadowEffect BlurRadius="10"
                                              Opacity="0.5"
                                              ShadowDepth="4" />
                                </Path.Effect>
                            </Path>
                            <TextBlock Canvas.Left="50"
                               Canvas.Top="28"
                               Width="100"
                               Height="65"
                               Text="{TemplateBinding Content}"
                               TextWrapping="Wrapwithoverflow" />
                        </Canvas>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

You can style the bubble a bit:

        <LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#FFF" Offset="0.0"/>
                    <GradientStop Color="#EEE" Offset="1.0"/>
                </GradientStopCollection>
            </GradientBrush.GradientStops>
        </LinearGradientBrush>

        <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />
    
        <Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
            <Setter Property="OverridesDefaultStyle" Value="true" />
            <Setter Property="HorizontalOffset" Value="-50" />
            <Setter Property="VerticalOffset" Value="0" />
            <Setter Property="Background" Value="#BE1C1C1C" />
            <Setter Property="Foreground" Value="Gray" />
            <Setter Property="FontSize" Value="12" />
            <Setter Property="FontFamily" Value="Segoe UI" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ToolTip">
                        <Canvas Width="200" Height="100">

                            <Path x:Name="Container"
                          Canvas.Left="0"
                          Canvas.Top="0"                                  
                          Margin="0"
                          Data="M 50,10 L60,0 L70,10 L200,10 L200,100 L0,100 L0,10 L50,10"                                  
                          Stroke="Black">
                                <Path.Effect>
                                    <DropShadowEffect BlurRadius="10"
                                              Opacity="0.5"
                                              ShadowDepth="4" />
                                </Path.Effect>
                                <Path.Fill>
                                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                        <GradientStop Color="#CF181818" Offset="0"/>
                                        <GradientStop Color="#BE1C1C1C" Offset="1"/>
                                    </LinearGradientBrush>

                                </Path.Fill>
                            </Path>
                            <TextBlock Canvas.Left="50"
                               Canvas.Top="28"
                               Width="100"
                               Height="65"
                               Text="{TemplateBinding Content}"
                               TextWrapping="Wrapwithoverflow" />
                        </Canvas>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

I then looked into the concept of centring the arrow, the following links looked like they might help:

http://stackoverflow.com/questions/14167763/wpf-path-geometry-is-there-a-way-to-bind-the-data-property

http://www.blackwasp.co.uk/WPFPathMarkupsyntax.aspx

And I did try expanding the syntax:

                                <Path.Data>
                                    <PathGeometry>
                                        <PathGeometry.Figures>
                                            <PathFigureCollection>
                                                <PathFigure IsClosed="True" StartPoint="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ControlToCentreConverter}}">
                                                    <PathFigure.Segments>
                                                        <PathSegmentCollection>
                                                            <LineSegment Point="60,0"/>
                                                            <LineSegment Point="70,10"/>
                                                            <LineSegment Point="200,10"/>
                                                            <LineSegment Point="200,100"/>
                                                            <LineSegment Point="0,100"/>
                                                            <LineSegment Point="0,10"/>
                                                        </PathSegmentCollection>
                                                    </PathFigure.Segments>
                                                </PathFigure>
                                            </PathFigureCollection>
                                        </PathGeometry.Figures>
                                    </PathGeometry>

The idea was to bind the line segments. I’m not saying it’s not possible; it clearly is, but it started to get prohibitively complex. If anyone comes up with a simple way of doing this (or even a complex one) then please add a link in the comments.

How to Draw a Diagonal Line Using Only XAML (and no path data)

I’ve been playing around with an app to maintain a score. The first thing that I wanted to do was to split the screen, but I wanted a diagonal split; similar to this:

phonescreen

There are a number of ways that this can be achieved; however, this is a method using only XAML. The same method will (I believe) work on Windows 8, WPF and Windows Phone.

First, split the screen into three rows or columns (which depends on how you want the orientation):

        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

This needs to be an odd number of rows / columns if you want the line to be central; additionally, an increased number of rows or columns will result in a smaller incline. Next, draw a transparent rectangle across the middle grid square:

        <Rectangle Name="Placeholder" Grid.Row="1" 
                   HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                   Opacity="0"/>

Finally, simply draw a line, binding the X2, Y2 values to the height and width of your rectangle:

        <Line Grid.Row="1" 
              X1="0" X2="{Binding ActualWidth, ElementName=Placeholder}"
              Y1="0" Y2="{Binding ActualHeight, ElementName=Placeholder}"
              StrokeThickness="2" Stroke="Blue"/>

Conclusion

I’m not saying the is the best, most efficient, or only way of doing this. However, it does mean that I can do it on both Windows Phone and Windows Store (and in WPF if I were so inclined) using a single shared project.

Startup Uri Not Working – Cannot locate recource ‘mainwindow.xaml’

I recently re-visited this project, and found that, amongst other things, it would no longer run up. Clearly something had changed, and it was a while until I realised what. The error I was getting was this:

Cannot locate resource

The App.Xaml looked like this:

<Application x:Class="TFSUtils.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="clr-namespace:TFSUtils.ViewModel"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             StartupUri="MainWindow.xaml"
             mc:Ignorable="d">

Solution

I finally realised that I must have copied the MainWindow.Xaml into the Views folder as a last minute cleanup. The fix was very simple:

<Application x:Class="TFSUtils.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="clr-namespace:TFSUtils.ViewModel"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             StartupUri="View/MainWindow.xaml"
             mc:Ignorable="d">

Conclusion

So, there you go: long untouched and untested code just spontaneously breaks! Magic.

Using a RepeatButton to Rapidly Increment Values

Recently, I’ve been playing with the WPF `RepeatButton`. I’ve not used it before, as it does seem to fulfil a very specific purpose. The use that I was trying to put it to was to act in the same way as you would expect your alarm clock to behave if you changed the time.

Say it’s currently 3:10 and you need it to be 3:15; you’d simply press the plus button five times, right? But what if you want it to be 17:50? Well, you’d hold the plus button down, but you wouldn’t expect it to increase by minutes; you’d maybe expect it to jump by 30 mins at a time after holding the plus button for a while.

The RepeatButton can do the same… but unfortunately not out of the box.

The Box

What you can do out of the box is this:

 <RepeatButton Content="+" Name="IncreaseButton"/>

And to handle the click:

IncreaseButton.Click += new RoutedEventHandler(IncreaseButton_Click);

Finally:

private void IncreaseButton_Click(object sender, RoutedEventArgs e)
{            
    Value += 1;
}

What’s wrong with that?

So far, so good, but what if I want the increment to increase though, as described in the alarm clock scenario above? I have two options here. One thing you can do is to increase the frequency that you increment; however, what I wanted to do was to increase the increment. The way I did it was to store a count:

private int _incrementButtonRepeat;

Then, in the `click` event:

private void IncreaseButton_Click(object sender, RoutedEventArgs e)
{            
    if (_incrementButtonRepeat > 5)
        Value += 5;
    else
        Value += 1;
    _incrementButtonRepeat++;        
}

Finally, you’ll need to reset:

private void IncreaseButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    _incrementButtonRepeat = 0;
}

A little gotcha here is the MouseUp command never seems to fire, because it’s apparently handled internally. Use the preview to intercept it first.

Conclusion

You could create a very basic control that encapsulates this behaviour, based on some parameters; then you’d never have to consider this again.