Tag Archives: wpf

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.

Creating and Binding to a User Control in MVVM Cross

While creating my game, I recently came across the problem of navigation. This post describes how to create a custom user control and react to the event inside.

The usual disclaimer still applies here; although this is an MVVM Cross post, the contents of it should be applicable for any MVVM Framework or, in fact, any XAML binding at all.

User Control

The user control that I’m going to use is simply a navigation bar to appear at the top of each screen.

The XAML for the user control is here:

<UserControl
    x:Name="NavigationControlRoot">
    
    <Grid DataContext="{Binding ElementName=NavigationControlRoot}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Button Content="Home" Grid.Column="0" Command="{Binding HomeClick}" />
        <Button Content="Back" Grid.Column="1" Command="{Binding BackClick}" />
    </Grid>
</UserControl>

So, I’ve got two commands, binding to the code behind. The key point here is that the root control (grid) binds to the DataContext of the user control – effectively this binds it to the containing DataContext. Here’s the code behind:


    public sealed partial class NavigationPanel : UserControl
    {
        public static DependencyProperty BackCommandProperty =
            DependencyProperty.Register(
                "BackClick",
                typeof(ICommand),
                typeof(NavigationPanel),
                new PropertyMetadata(null));

        public static DependencyProperty HomeCommandProperty =
            DependencyProperty.Register(
                "HomeClick",
                typeof(ICommand),
                typeof(NavigationPanel),
                new PropertyMetadata(null));

        public NavigationPanel()
        {
            this.InitializeComponent();
        }
        
        public ICommand BackClick
        {
            get
            {
                return (ICommand)GetValue(BackCommandProperty);
            }

            set
            {
                SetValue(BackCommandProperty, value);
            }
        }

        public ICommand HomeClick
        {
            get
            {
                return (ICommand)GetValue(HomeCommandProperty);
            }

            set
            {
                SetValue(HomeCommandProperty, value);
            }
        }        
    }

So, you’ve now exposed two dependency properties and bound them to the XAML in the user control.

Host App

Here’s the relevant XAML for the hosting view:

<controls:NavigationPanel BackClick="{Binding BackClickCommand}"                                  
                                  HomeClick="{Binding HomeClickCommand}"/>

And that just binds to the ViewModel as you would expect.

        private IMvxCommand _homeClickCommand;
        public IMvxCommand HomeClickCommand
        {
            get
            {
                if (_homeClickCommand == null)
                {
                    _homeClickCommand = new MvxCommand(() => GoHome());
                }
                return _homeClickCommand;
            }
            set
            {
                _homeClickCommand = value;
            }
        }

        private void GoHome()
        {
            ShowViewModel<MainViewModel>();
        }

For me, this is in a base view model, so it responds to the same command in every view.

Conclusion

I think the final version of this code will interact with an IMvxPresenter in some way, which may make another post.

Creating a WPF app with no interface (or WPF)

This does sound counter intuitive, but let’s have a look at an example. Let’s say that I want to create an application that, when called, created a text file with a list of its own arguments, and then finishes.

Console App?

So, the first thing you think is: create a console app. Good idea; here’s what it looks like:

    class Program
    {
        static void Main(string[] args)
        {            
            File.WriteAllLines("tmp.txt", args);
        }
    }

Okay, so it’s simple and it works. And here’s what it looks like when you run it:

Running Console App

It won’t say “Press any key” unless you run it with Ctrl-F5 (because you want to take a screenshot).

Anyway, as unobtrusive as that it, what if you didn’t want it to appear? Well, one option (and, as usual, I don’t claim this is the only option), you could create a WPF app instead. In the App.xaml, you can specify the start-up object… or not:

<Application x:Class="WpfApplication3.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             >
    <Application.Resources>
         
    </Application.Resources>
</Application>

So, no StartupURI element. Next, you need to override the Startup event in App.Xaml.cs:

        protected override void OnStartup(StartupEventArgs e)
        {
            var args = e.Args.Select(a => a.ToString());
            File.WriteAllLines("tmp.txt", args);
            
            Shutdown();
        }

Conclusion

And that’s it… you now have a runable app that is completely devoid of a UI.

One word of warning: if this is going to be a slightly more complex app that makes use of asynchrony, then remember that you’re closing the app here.

As usual, if anyone knows of a better method of doing this then please leave a comment or get in touch.

Implementing single cell copy and full row Item Selection in WPF DataGrid

Recently, I came across a strange problem with a DataGrid while using the MVVM-Light pattern. The problem was that I needed to implement two, seemingly similar pieces of functionality:

1. To copy any given cell in the data grid
2. To search the database, based on a specific piece of data in the selected row of the datagrid (regardless of which cell was selected).

To implement the Copy feature was unbelievably easy – you don’t even need a custom command; just add the following setting to the DataGrid:

SelectionUnit ="CellOrRowHeader"

And the following context menu:

<DataGrid.ContextMenu>
    <ContextMenu>                    
       <MenuItem Command="Copy"/>                    
    </ContextMenu>
</DataGrid.ContextMenu>

And the copy is done!

So, next, I wanted to implement the custom search. I needed to identify the currently selected row in my ViewModel, so I added a new property:

        private MyDataClass _selectedItem;
        public MyDataClass SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                _selectedItem = value;
                RaisePropertyChanged(() => SelectedItem);
            }
        }

I then bound this to the selected item of the datagrid, like this:

     < DataGrid ItemsSource ="{ Binding Path =MyDataCollection}"                   
                  SelectedItem="{ Binding Path =SelectedItem}"                   
                  SelectionUnit ="CellOrRowHeader"  
&#91;/sourcecode&#93;

And finally, created my command:

&#91;sourcecode language="csharp"&#93;
         public RelayCommand MyCommand { get ; private set ; }
&#91;/sourcecode&#93;

In the constructor, initialised it:

&#91;sourcecode language="csharp"&#93;
     this.MyCommand = new RelayCommand (() => this.MyCommandFunc());

Created the function:

         private void MyCommandFunc()
        {
            QueryDB(SelectedItem.ValueOne);
        }         

And, finally, setup the context menu:

<DataGrid.ContextMenu>
    <ContextMenu>                    
         <MenuItem Command ="Copy"/>                    
         <MenuItem Header ="My Funky Command" Command="{Binding MyCommand}"/>
    </ContextMenu>
</DataGrid.ContextMenu>

But this didn’t work! (Queue dramatic music).

The problem is down to this:

SelectionUnit ="CellOrRowHeader"

So, change it to:

SelectionUnit="FullRow" 

And it works again… but now copy doesn’t work, because it copies the FULL ROW!

So, the two features are incompatible?

I did find a workaround which doesn’t breach the separation of concerns. Please note that, although this does work, and does not breach separation of concerns, it may or may not be the best way of doing it. If you have, or know of, a better way the PLEASE leave a comment, or mail me and let me know.

The Workaround

Instead of binding the SelectedItem property, bind the Tag property of the grid:

< DataGrid ItemsSource ="{ Binding Path =MyDataCollection}" Tag="{ Binding Path =SelectedItem}" SelectionUnit ="CellOrRowHeader" [/sourcecode] Now, we need a little code-behind, which must remain agnostic to the ViewModel: [sourcecode language="csharp"] private void DataGrid_SelectedCellsChanged( object sender, SelectedCellsChangedEventArgs e) { (( DataGrid)sender).Tag = ((DataGrid)sender).SelectedCells.FirstOrDefault().Item; } [/sourcecode] Now, because the SelectedItem is bound in the view to the Tag, updating the Tag, updates the SelectedItem. This means that when you call your function (as described above): [sourcecode language="csharp"] private void MyCommandFunc() { QueryDB(SelectedItem.ValueOne); } [/sourcecode] SelectedItem is now set. Conclusion

Whilst I can see why this works the way it does, it doesn’t seem sensible that I need to jump through so many hoops to get this to work without breaking the separation of concern. It may just be a quirk with the DataGrid, but it feels like a hack – which I don’t like. Nevertheless, ATM it’s the only solution that I can come up with that doesn’t mean directly referencing the View from the ViewModel.