WPF using BackgroundWorker vs using Task

Today I’m expanding on the ExampleAppinator post that I made last week which will be the foundation to my examples going forward.  In this I’m going to show an example that simply outputs the contents of the KitchenSink tables into grids…1 set using BackgroundWorker to create threads for doing the fetching of data, the other set is using a Task.

I’ll post the ending source code to the WPF example app at the end of this post.

The first thing I noticed when I put this together is that when there wasn’t a lot of data in the table, it really took little to no time to ever get anything out of the database so it was nealry impossible to see any difference in BackgroundWorker vs Task…which is what lead to me creating KitchenSinkClogger in the ExampleAppinator post.  I created that and let it run for the weekend…it filled up my database with about 250000 records.  Perfect, now I have some meat to sink my teeth into.

Now when I launched the test WPF app, I ran into some real perf problems.  At this stage of the test I had 12 DataGrid’s being loaded up simultaniously, 6 using BackgroundWorker threads, 6 using Task threads.  When I launched it, for about 5 solid minutes nothing happened but churning in the debugger.  I paused it in the debugger a couple times just to verify that there were worker threads churning in the background…and as far as I can tell, none of them should have been on the UI thread so the UI should have never locked up…I’m a bit confused by that one.

From here I had a thought that maybe the app was becoming unresponsive because I was trying to load up 12 DataGrid’s with 250000 records each and it was a memory issues…so I decided to add pagers to my DataGrids…and since that’s a total pain in the backside with normal built in WPF controls I decided to switch over to using Telerik controls at this point since they have a ready made RadDataPager object I could use.  About 10 minutes later…everything was switched over to RadBusyIndicator’s and RadGridView’s with a RadDataPager in place and as I tested out each individual grid they were all working smooth…until…I turned them all on to load at the same time again.  Again the app was brought down to it’s knees in perf.

So this is where I’m at right now as of 5/11/2016.  I think my next move is to make a post on the Telerik forums now that I’m using their controls and see if I get lucky with an answer on how to make my little test more performant…but in all honesty I don’t think it has anything to do with the controls.  I think it has something to do with the threads…and that I’m obviously attaching to the UI thread somewhere.

Update on 5/17/2016 – I got a reply from Telerik and they weren’t able to help figure out where I’m getting the performance hit…next I’m going to try disabling all the grids but one and see what kind of perf I get there…then start adding them back in one at a time…all the while monitoring threads to see if I can notice anything strange going on.

As always I encourage anyone reading this to leave a comment if you have questions or feedback…I know I don’t know what I’m doing…but I’m totally willing to learn new techniques.  🙂

Update on 5/25/2016 – Yes I know I haven’t posted in about a week…I’ve gotten busy at work again which cuts into my post preparation time so :P.  Quality posts take time to put together…and since mine suck it takes less time…but still a lot of time.

Update on 5/31/2016 – I did some work on this today and started by narrowing down the grids being returned from the database to just one.  I had to kill my database with over 1 million records in it and I’m currently rebuilding it…but at the moment I get about 11k records returned so fast that I can’t even see the Busy Indicator come up…so now I’m going to wait for the database to fill up again so I can see that.  Trying to make sure that each piece is working individually.  Once I can prove that the busy indicator comes up for a BackgroundWorker thread…then I’m going to try returning the same set of information using a Task thread to see if there’s any noticible difference.  Anyway…that’s where I’m at with this for now.  Stay tuned…I’ll get this figured out eventually.

Update on 6/1/2016 – So I got 500k records in my database now and tried the grid using the BackgroundWorker thread again.  Low and behold the Busy Indicator shows up…woohoo.  Now onto seeing if I get the same results using a Task instead.

Update on 6/7/2016 – Today I made some great progress.  I learned what’s really hanging up the BW thread and the Task threads both is that I’m creating a whole bunch of them that are all using the same resources…the network…so there’s a ton of waiting and locking and syncronizing that the OS is doing with the threads and it causes the UI to seem like it’s getting hung up because the system is busy doing all this work sync’ing background threads.  What I learned to do is put a lock on the parent DLL object I have for all my Linq classes and models with each BW thread that I create.  This way I’m managing the sync’ing by forcing all the threads to wait until usage of the parent object free’s up.  The UI displays the Busy Indicator…all the worker threads spin up just fine…and the UI stays responsive.  The 1 down side of this that I noticed is now you have to wait for each table to load once at a time…even though the busy indicator is there and gives a good experience to the user, I feel there could be some more done here to improve the data retrieval experience.  Also I couldn’t retrieve a table with 5 million records.  Got an OOM expcetion thrown from Linq.  Stupid Linq.  😛  So I’m off to learn some data virtualization techniques so I can apply them to my example app and see if I can actually get back to my original theme to this post…which is better?  BW or Task?  Man, this post is crossing lots of different disciplines.  I like it!  Stay tuned!!!  🙂  I’ll be posting an update to the source code once I get the data virtualization part figured out…

MainWindow XAML:

<Window Height="350"
        mc:Ignorable="d"
        Title="MainWindow"
        Width="525"
        x:Class="CSharpWPFAppinator.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:local="clr-namespace:CSharpWPFAppinator"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <Grid>
        <TabControl x:Name="KitchenSinkTabControl">
            <TabItem    Header="BW - Engine"
                        x:Name="BWEngineTab" />
            <TabItem    Header="BW - History"
                        x:Name="BWHistoryTab" />
            <TabItem    Header="BW - Lifeform"
                        x:Name="BWLifeformTab" />
            <TabItem    Header="BW - Planet"
                        x:Name="BWPlanetTab" />
            <TabItem    Header="BW - Species"
                        x:Name="BWSpeciesTab" />
            <TabItem    Header="BW - Vehicle"
                        x:Name="BWVehicleTab" />
            <TabItem    Header="Task - Engine"
                        x:Name="TaskEngineTab" />
            <TabItem    Header="Task - History"
                        x:Name="TaskHistoryTab" />
            <TabItem    Header="Task - Lifeform"
                        x:Name="TaskLifeformTab" />
            <TabItem    Header="Task - Planet"
                        x:Name="TaskPlanetTab" />
            <TabItem    Header="Task - Species"
                        x:Name="TaskSpeciesTab" />
            <TabItem    Header="Task - Vehicle"
                        x:Name="TaskVehicleTab" />
        </TabControl>
    </Grid>
</Window>

MainWindow CS Code Behind:

using System.Windows;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            KitchenSinkDLL.KitchenSinkDLL.ConnectionString = @"Data Source=cmd-roswell;Initial Catalog=KitchenSink;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

            foreach(TabItem item in KitchenSinkTabControl.Items)
            {
                switch(item.Name)
                {
                    case "BWEngineTab":
                        BWEngineUserControl bwEngineControl = new BWEngineUserControl();
                        bwEngineControl.RefreshData();
                        item.Content = bwEngineControl;
                        break;
                    case "BWHistoryTab":
                        BWHistoryUserControl bwHistoryControl = new BWHistoryUserControl();
                        bwHistoryControl.RefreshData();
                        item.Content = bwHistoryControl;
                        break;
                    case "BWLifeformTab":
                        BWLifeformUserControl bwLifeformControl = new BWLifeformUserControl();
                        bwLifeformControl.RefreshData();
                        item.Content = bwLifeformControl;
                        break;
                    case "BWPlanetTab":
                        BWPlanetUserControl bwPlanetControl = new BWPlanetUserControl();
                        bwPlanetControl.RefreshData();
                        item.Content = bwPlanetControl;
                        break;
                    case "BWSpeciesTab":
                        BWSpeciesUserControl bwSpeciesControl = new BWSpeciesUserControl();
                        bwSpeciesControl.RefreshData();
                        item.Content = bwSpeciesControl;
                        break;
                    case "BWVehicleTab":
                        BWVehicleUserControl bwVehicleControl = new BWVehicleUserControl();
                        bwVehicleControl.RefreshData();
                        item.Content = bwVehicleControl;
                        break;
                    case "TaskEngineTab":
                        TaskEngineUserControl taskEngineControl = new TaskEngineUserControl();
                        taskEngineControl.RefreshData();
                        item.Content = taskEngineControl;
                        break;
                    case "TaskHistoryTab":
                        TaskHistoryUserControl taskHistoryControl = new TaskHistoryUserControl();
                        taskHistoryControl.RefreshData();
                        item.Content = taskHistoryControl;
                        break;
                    case "TaskLifeformTab":
                        TaskLifeformUserControl taskLifeformControl = new TaskLifeformUserControl();
                        taskLifeformControl.RefreshData();
                        item.Content = taskLifeformControl;
                        break;
                    case "TaskPlanetTab":
                        TaskPlanetUserControl taskPlanetControl = new TaskPlanetUserControl();
                        taskPlanetControl.RefreshData();
                        item.Content = taskPlanetControl;
                        break;
                    case "TaskSpeciesTab":
                        TaskSpeciesUserControl taskSpeciesControl = new TaskSpeciesUserControl();
                        taskSpeciesControl.RefreshData();
                        item.Content = taskSpeciesControl;
                        break;
                    case "TaskVehicleTab":
                        TaskVehicleUserControl taskVehicleControl = new TaskVehicleUserControl();
                        taskVehicleControl.RefreshData();
                        item.Content = taskVehicleControl;
                        break;
                    default:
                        break;
                }
            }
        }
    }
}

BackgroundWorkerUserControl:

using KitchenSinkDLL.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Telerik.Windows.Controls;
using Xceed.Wpf.Toolkit;

namespace CSharpWPFAppinator
{
    public class BackgroundWorkerUserControl<T> : UserControl
    {
        public BackgroundWorkerUserControl()
        {
            BackgroundWorkerGrid = new Grid();
            BackgroundWorkerGrid.ColumnDefinitions.Add(new ColumnDefinition());
            BackgroundWorkerGrid.RowDefinitions.Add(new RowDefinition());
            BackgroundWorkerGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });

            BackgroundWorkerRadGridView = new RadGridView();
            Grid.SetColumn(BackgroundWorkerRadGridView, 0);
            Grid.SetRow(BackgroundWorkerRadGridView, 0);
            BackgroundWorkerGrid.Children.Add(BackgroundWorkerRadGridView);

            BackgroundWorkerRadDataPager = new RadDataPager();
            BackgroundWorkerRadDataPager.AutoEllipsisMode = AutoEllipsisModes.Both;
            BackgroundWorkerRadDataPager.DisplayMode = PagerDisplayModes.All;
            BackgroundWorkerRadDataPager.NumericButtonCount = 10;
            BackgroundWorkerRadDataPager.PageSize = 40;
            BackgroundWorkerRadDataPager.VerticalAlignment = VerticalAlignment.Bottom;
            BackgroundWorkerRadDataPager.Source = BackgroundWorkerRadGridView.Items;
            Grid.SetColumn(BackgroundWorkerRadDataPager, 0);
            Grid.SetRow(BackgroundWorkerRadDataPager, 1);
            BackgroundWorkerGrid.Children.Add(BackgroundWorkerRadDataPager);

            BackgroundWorkerRadBusyIndicator = new RadBusyIndicator();
            BackgroundWorkerRadBusyIndicator.Content = BackgroundWorkerGrid;

            Content = BackgroundWorkerRadBusyIndicator;

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            BackgroundWorker = new BackgroundWorker();
            BackgroundWorker.DoWork += BackgroundWorkerDoWork;
            BackgroundWorker.RunWorkerCompleted += BackgroundWorkerCompleted;
        }

        private BackgroundWorker BackgroundWorker { get; set; }

        private Grid BackgroundWorkerGrid { get; set; }

        private RadBusyIndicator BackgroundWorkerRadBusyIndicator { get; set; }

        private RadDataPager BackgroundWorkerRadDataPager { get; set; }

        private RadGridView BackgroundWorkerRadGridView { get; set; }

        private DateTime LastHistoryUpdate { get; set; }

        private void BackgroundWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Dispatcher.BeginInvoke(new Action<IEnumerable<T>>(RefreshBackgroundWorkerDataGrid), e.Result);
        }

        private void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            e.Result = KitchenSinkDLL.KitchenSinkDLL.ReadTable<T>();
        }

        private void RefreshBackgroundWorkerDataGrid(IEnumerable<T> engines)
        {
            BackgroundWorkerRadGridView.ItemsSource = null;

            if (engines != null)
            {
                LastHistoryUpdate = HistoryModel.ReadLastHistoryUpdate();
                BackgroundWorkerRadGridView.ItemsSource = engines;
            }

            BackgroundWorkerRadBusyIndicator.IsBusy = false;
        }

        public void RefreshData()
        {
            if (!BackgroundWorker.IsBusy)
            {
                DateTime currentLastHistoryUpdate = HistoryModel.ReadLastHistoryUpdate();

                if (currentLastHistoryUpdate > LastHistoryUpdate)
                {
                    BackgroundWorkerRadBusyIndicator.IsBusy = true;
                    BackgroundWorker.RunWorkerAsync();
                }
            }
        }
    }
}

TaskUserControl:

using KitchenSinkDLL.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Telerik.Windows.Controls;
using Xceed.Wpf.Toolkit;

namespace CSharpWPFAppinator
{
    public class TaskUserControl<T> : UserControl
    {
        public TaskUserControl()
        {
            TaskGrid = new Grid();
            TaskGrid.ColumnDefinitions.Add(new ColumnDefinition());
            TaskGrid.RowDefinitions.Add(new RowDefinition());
            TaskGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });

            TaskRadGridView = new RadGridView();
            Grid.SetColumn(TaskRadGridView, 0);
            Grid.SetRow(TaskRadGridView, 0);
            TaskGrid.Children.Add(TaskRadGridView);

            TaskRadDataPager = new RadDataPager();
            TaskRadDataPager.AutoEllipsisMode = AutoEllipsisModes.Both;
            TaskRadDataPager.DisplayMode = PagerDisplayModes.All;
            TaskRadDataPager.NumericButtonCount = 10;
            TaskRadDataPager.PageSize = 40;
            TaskRadDataPager.VerticalAlignment = VerticalAlignment.Bottom;
            TaskRadDataPager.Source = TaskRadGridView.Items;
            Grid.SetColumn(TaskRadDataPager, 0);
            Grid.SetRow(TaskRadDataPager, 1);
            TaskGrid.Children.Add(TaskRadDataPager);

            TaskRadBusyIndicator = new RadBusyIndicator();
            TaskRadBusyIndicator.Content = TaskGrid;

            Content = TaskRadBusyIndicator;

            if (DesignerProperties.GetIsInDesignMode(this))
                return;
        }

        private Grid TaskGrid { get; set; }

        private RadBusyIndicator TaskRadBusyIndicator { get; set; }

        private RadDataPager TaskRadDataPager { get; set; }

        private RadGridView TaskRadGridView { get; set; }

        private DateTime LastHistoryUpdate { get; set; }

        private IEnumerable<T> Records { get; set; }

        private void FetchDataForControl()
        {
            Records = KitchenSinkDLL.KitchenSinkDLL.ReadTable<T>();
        }

        private void RefreshTaskDataGrid()
        {
            TaskRadGridView.ItemsSource = null;

            if (Records != null)
            {
                LastHistoryUpdate = HistoryModel.ReadLastHistoryUpdate();
                TaskRadGridView.ItemsSource = Records;
            }
        }

        public async Task RefreshData()
        {
            TaskRadBusyIndicator.IsBusy = true;

            await Task.Run(() =>
            {
                DateTime currentLastHistoryUpdate = HistoryModel.ReadLastHistoryUpdate();

                if (currentLastHistoryUpdate > LastHistoryUpdate)
                {
                    FetchDataForControl();
                }
            });

            RefreshTaskDataGrid();
            TaskRadBusyIndicator.IsBusy = false;
        }
    }
}

BWEngineUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.BWEngineUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="BWEngineGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

BWEngineUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for BWEngineUserControl.xaml
    /// </summary>
    public partial class BWEngineUserControl : UserControl
    {
        public BWEngineUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            EngineUserControl = new BackgroundWorkerUserControl<EngineModel>();
            Grid.SetColumn(EngineUserControl, 0);
            Grid.SetRow(EngineUserControl, 0);
            BWEngineGrid.Children.Add(EngineUserControl);
        }

        private BackgroundWorkerUserControl<EngineModel> EngineUserControl { get; set; }

        public void RefreshData()
        {
            EngineUserControl.RefreshData();
        }
    }
}

TaskEngineUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.TaskEngineUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="TaskEngineGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

TaskEngineUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for TaskEngineUserControl.xaml
    /// </summary>
    public partial class TaskEngineUserControl : UserControl
    {
        public TaskEngineUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            EngineUserControl = new TaskUserControl<EngineModel>();
            Grid.SetColumn(EngineUserControl, 0);
            Grid.SetRow(EngineUserControl, 0);
            TaskEngineGrid.Children.Add(EngineUserControl);
        }

        private TaskUserControl<EngineModel> EngineUserControl { get; set; }

        public async Task RefreshData()
        {
            await EngineUserControl.RefreshData();
        }
    }
}

BWHistoryUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.BWHistoryUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="BWHistoryGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

BWHistoryUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for BWHistoryUserControl.xaml
    /// </summary>
    public partial class BWHistoryUserControl : UserControl
    {
        public BWHistoryUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            HistoryUserControl = new BackgroundWorkerUserControl<HistoryModel>();
            Grid.SetColumn(HistoryUserControl, 0);
            Grid.SetRow(HistoryUserControl, 0);
            BWHistoryGrid.Children.Add(HistoryUserControl);
        }

        private BackgroundWorkerUserControl<HistoryModel> HistoryUserControl { get; set; }

        public void RefreshData()
        {
            HistoryUserControl.RefreshData();
        }
    }
}

TaskHistoryUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.TaskHistoryUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="TaskHistoryGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

TaskHistoryUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for TaskHistoryUserControl.xaml
    /// </summary>
    public partial class TaskHistoryUserControl : UserControl
    {
        public TaskHistoryUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            HistoryUserControl = new TaskUserControl<HistoryModel>();
            Grid.SetColumn(HistoryUserControl, 0);
            Grid.SetRow(HistoryUserControl, 0);
            TaskHistoryGrid.Children.Add(HistoryUserControl);
        }

        private TaskUserControl<HistoryModel> HistoryUserControl { get; set; }

        public async Task RefreshData()
        {
            await HistoryUserControl.RefreshData();
        }
    }
}

BWLifeformUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.BWLifeformUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="BWLifeformGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

BWLifeformUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for BWLifeformUserControl.xaml
    /// </summary>
    public partial class BWLifeformUserControl : UserControl
    {
        public BWLifeformUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            LifeformUserControl = new BackgroundWorkerUserControl<LifeformModel>();
            Grid.SetColumn(LifeformUserControl, 0);
            Grid.SetRow(LifeformUserControl, 0);
            BWLifeformGrid.Children.Add(LifeformUserControl);
        }

        private BackgroundWorkerUserControl<LifeformModel> LifeformUserControl { get; set; }

        public void RefreshData()
        {
            LifeformUserControl.RefreshData();
        }
    }
}

TaskLifeformUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.TaskLifeformUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="TaskLifeformGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

TaskLifeformUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for TaskLifeformUserControl.xaml
    /// </summary>
    public partial class TaskLifeformUserControl : UserControl
    {
        public TaskLifeformUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            LifeformUserControl = new TaskUserControl<LifeformModel>();
            Grid.SetColumn(LifeformUserControl, 0);
            Grid.SetRow(LifeformUserControl, 0);
            TaskLifeformGrid.Children.Add(LifeformUserControl);
        }

        private TaskUserControl<LifeformModel> LifeformUserControl { get; set; }

        public async Task RefreshData()
        {
            await LifeformUserControl.RefreshData();
        }
    }
}

BWPlanetUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.BWPlanetUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="BWPlanetGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

BWPlanetUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for BWPlanetUserControl.xaml
    /// </summary>
    public partial class BWPlanetUserControl : UserControl
    {
        public BWPlanetUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            PlanetUserControl = new BackgroundWorkerUserControl<PlanetModel>();
            Grid.SetColumn(PlanetUserControl, 0);
            Grid.SetRow(PlanetUserControl, 0);
            BWPlanetGrid.Children.Add(PlanetUserControl);
        }

        private BackgroundWorkerUserControl<PlanetModel> PlanetUserControl { get; set; }

        public void RefreshData()
        {
            PlanetUserControl.RefreshData();
        }
    }
}

TaskPlanetUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.TaskPlanetUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="TaskPlanetGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

TaskPlanetUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for TaskPlanetUserControl.xaml
    /// </summary>
    public partial class TaskPlanetUserControl : UserControl
    {
        public TaskPlanetUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            PlanetUserControl = new TaskUserControl<PlanetModel>();
            Grid.SetColumn(PlanetUserControl, 0);
            Grid.SetRow(PlanetUserControl, 0);
            TaskPlanetGrid.Children.Add(PlanetUserControl);
        }

        private TaskUserControl<PlanetModel> PlanetUserControl { get; set; }

        public async Task RefreshData()
        {
            await PlanetUserControl.RefreshData();
        }
    }
}

BWSpeciesUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.BWSpeciesUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="BWSpeciesGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

BWSpeciesUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for BWSpeciesUserControl.xaml
    /// </summary>
    public partial class BWSpeciesUserControl : UserControl
    {
        public BWSpeciesUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            SpeciesUserControl = new BackgroundWorkerUserControl<SpeciesModel>();
            Grid.SetColumn(SpeciesUserControl, 0);
            Grid.SetRow(SpeciesUserControl, 0);
            BWSpeciesGrid.Children.Add(SpeciesUserControl);
        }

        private BackgroundWorkerUserControl<SpeciesModel> SpeciesUserControl { get; set; }

        public void RefreshData()
        {
            SpeciesUserControl.RefreshData();
        }
    }
}

TaskSpeciesUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.TaskSpeciesUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="TaskSpeciesGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

TaskSpeicesUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for TaskSpeciesUserControl.xaml
    /// </summary>
    public partial class TaskSpeciesUserControl : UserControl
    {
        public TaskSpeciesUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            SpeciesUserControl = new TaskUserControl<SpeciesModel>();
            Grid.SetColumn(SpeciesUserControl, 0);
            Grid.SetRow(SpeciesUserControl, 0);
            TaskSpeciesGrid.Children.Add(SpeciesUserControl);
        }

        private TaskUserControl<SpeciesModel> SpeciesUserControl { get; set; }

        public async Task RefreshData()
        {
            await SpeciesUserControl.RefreshData();
        }
    }
}

BWVehicleUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.BWVehicleUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="BWVehicleGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

BWVehicleUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for BWVehicleUserControl.xaml
    /// </summary>
    public partial class BWVehicleUserControl : UserControl
    {
        public BWVehicleUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            VehicleUserControl = new BackgroundWorkerUserControl<VehicleModel>();
            Grid.SetColumn(VehicleUserControl, 0);
            Grid.SetRow(VehicleUserControl, 0);
            BWVehicleGrid.Children.Add(VehicleUserControl);
        }

        private BackgroundWorkerUserControl<VehicleModel> VehicleUserControl { get; set; }

        public void RefreshData()
        {
            VehicleUserControl.RefreshData();
        }
    }
}

TaskVehicleUserControl XAML:

<UserControl    d:DesignHeight="300"
                d:DesignWidth="300"
                mc:Ignorable="d"
                x:Class="CSharpWPFAppinator.TaskVehicleUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:local="clr-namespace:CSharpWPFAppinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
    <Grid x:Name="TaskVehicleGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>
</UserControl>

TaskVehicleUserControl CS Code Behind:

using KitchenSinkDLL.Models;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace CSharpWPFAppinator
{
    /// <summary>
    /// Interaction logic for TaskVehicleUserControl.xaml
    /// </summary>
    public partial class TaskVehicleUserControl : UserControl
    {
        public TaskVehicleUserControl()
        {
            InitializeComponent();

            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            VehicleUserControl = new TaskUserControl<VehicleModel>();
            Grid.SetColumn(VehicleUserControl, 0);
            Grid.SetRow(VehicleUserControl, 0);
            TaskVehicleGrid.Children.Add(VehicleUserControl);
        }

        private TaskUserControl<VehicleModel> VehicleUserControl { get; set; }

        public async Task RefreshData()
        {
            await VehicleUserControl.RefreshData();
        }
    }
}
WPF using BackgroundWorker vs using Task

Setting a Custom WPF ToolTip and how to keep it shown…

Ok, so to be perfectly honest I was a little bored at work and decided to experiment with my super duper omega wonder app (now with kitchen sink features).  My experiment was to put some ToolTip pop ups of historical data about whatever cell the mouse happened to bre hovering over…so I started whipping up a test app to figure out how to do that…and you the lucky reader can follow my journey.  I should be charging for this, I know but we can chalk it up to my humanitarian contribution to our species for now.  *drip drip drip of sarcasim*

Ok, so in our test app we need a WPF window with a basic generic grid on it.  I’ve been using Telerik WPF controls, but everything I do in this post should work just as well with standard .NET WPF controls as well.  Telerik isn’t doing anything special to ToolTips and AFAIK they just expose the underlying .NET stuff.  Anyway, my examples will all be with Telerik…so take it FWIW.

First I’m going to make up some ficticious relational data.  It’s totally meaningless but will be useful to test things with.  So I’m going to be tracking scores of players who participate in multiple sports.  Here are the classes I’ve defined to store the data.

Player class:

public class Player
{
    public string UniqueName { get; set; }

    public string SportName { get; set; }

    public string Score { get; set; }

    public DateTime AchievedDate { get; set; }
}

SportContest class and the classes inheriting from it:

public class SportContest
{
    public List&amp;lt;Player&amp;gt; Contestants { get; set; }

    public IEnumerable&amp;lt;Player&amp;gt; GetResults()
    {
        if (Contestants == null)
        {
            return null;
        }
        else
        {
            return Contestants.Where(c =&gt; c.SportName == this.GetType().Name);
        }
    }
}

public class Hockey : SportContest
{
    public void AddScore(string playerUniqueName)
    {
        if(Contestants == null)
        {
            Contestants = new List&lt;Player&gt;();
        }

        Player player = Contestants.FirstOrDefault(c =&gt;
            c.UniqueName == playerUniqueName &amp;&amp;
            c.SportName == this.GetType().Name);

        if (player == null)
        {
            player = new Player();
            player.UniqueName = playerUniqueName;
            player.SportName = this.GetType().Name;
            player.Score = "1 goal";
            player.AchievedDate = DateTime.Now;

            Contestants.Add(player);
        }
        else
        {
            int scoreValue = Convert.ToInt32(player.Score.Substring(0, player.Score.IndexOf(" ")));
            player.Score = $"{++scoreValue} goals";
        }
    }
}

public class Baseball : SportContest
{
    public void AddScore(string playerUniqueName)
    {
        if (Contestants == null)
        {
            Contestants = new List&lt;Player&gt;();
        }

        Player player = Contestants.FirstOrDefault(c =&gt;
            c.UniqueName == playerUniqueName &amp;&amp;
            c.SportName == this.GetType().Name);

        if (player == null)
        {
            player = new Player();
            player.UniqueName = playerUniqueName;
            player.SportName = this.GetType().Name;
            player.Score = "1 run";
            player.AchievedDate = DateTime.Now;

            Contestants.Add(player);
        }
        else
        {
            int scoreValue = Convert.ToInt32(player.Score.Substring(0, player.Score.IndexOf(" ")));
            player.Score = $"{++scoreValue} runs";
        }
    }
}

public class Football : SportContest
{
    public void AddScore(string playerUniqueName, int pointsScored)
    {
        if (Contestants == null)
        {
            Contestants = new List&lt;Player&gt;();
        }

        Player player = Contestants.FirstOrDefault(c =&gt;
            c.UniqueName == playerUniqueName &amp;&amp;
            c.SportName == this.GetType().Name);

        if (player == null)
        {
            player = new Player();
            player.UniqueName = playerUniqueName;
            player.SportName = this.GetType().Name;
            player.Score = $"{pointsScored} {(pointsScored &gt; 1 ? " points" : " point")}";
            player.AchievedDate = DateTime.Now;

            Contestants.Add(player);
        }
        else
        {
            int scoreValue = Convert.ToInt32(player.Score.Substring(0, player.Score.IndexOf(" ")));
            scoreValue = scoreValue + pointsScored;
            player.Score = $"{scoreValue} points";
        }
    }
}

public class Basketball : SportContest
{
    public void AddScore(string playerUniqueName, int pointsScored)
    {
        if (Contestants == null)
        {
            Contestants = new List&lt;Player&gt;();
        }

        Player player = Contestants.FirstOrDefault(c =&gt;
            c.UniqueName == playerUniqueName &amp;&amp;
            c.SportName == this.GetType().Name);

        if (player == null)
        {
            player = new Player();
            player.UniqueName = playerUniqueName;
            player.SportName = this.GetType().Name;
            player.Score = $"{pointsScored} {(pointsScored &gt; 1 ? " points" : " point")}";
            player.AchievedDate = DateTime.Now;

            Contestants.Add(player);
        }
        else
        {
            int scoreValue = Convert.ToInt32(player.Score.Substring(0, player.Score.IndexOf(" ")));
            scoreValue = scoreValue + pointsScored;
            player.Score = $"{scoreValue} points";
        }
    }
}

SportResults class:

public class SportResults
{
    public SportResults()
    {
        Contestants = new List&amp;lt;Player&amp;gt;();

        FillHockeyResults();
        FillBaseballResults();
        FillFootballResults();
        FillBasketballResults();
    }

    public List&amp;lt;Player&amp;gt; Contestants { get; set; }

    public Hockey Hockey { get; set; }

    public Baseball Baseball { get; set; }

    public Football Football { get; set; }

    public Basketball Basketball { get; set; }

    public void FillHockeyResults()
    {
        Random rnd = new Random(DateTime.Now.Millisecond);

        Hockey = new Hockey();
        Hockey.Contestants = Contestants;

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Hockey.AddScore("Albert");
        }

        for(int i = 0; i &lt; rnd.Next(); i++)
        {
            Hockey.AddScore("Beau");
        }

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Hockey.AddScore("Charles");
        }

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Hockey.AddScore("David");
        }

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Hockey.AddScore("Eugene");
        }
    }

    public void FillBaseballResults()
    {
        Random rnd = new Random(DateTime.Now.Millisecond);

        Baseball = new Baseball();
        Baseball.Contestants = Contestants;

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Baseball.AddScore("Beau");
        }

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Baseball.AddScore("Charles");
        }

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Baseball.AddScore("David");
        }

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Baseball.AddScore("Eugene");
        }

        for (int i = 0; i &lt; rnd.Next(); i++)
        {
            Baseball.AddScore("Farley");
        }
    }

    public void FillFootballResults()
    {
        Random rndTimesScored = new Random(DateTime.Now.Millisecond);
        Random rndPointsScored = new Random(DateTime.Now.Millisecond);

        Football = new Football();
        Football.Contestants = Contestants;

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Football.AddScore("Charles", rndPointsScored.Next(1, 7));
        }

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Football.AddScore("David", rndPointsScored.Next(1, 7));
        }

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Football.AddScore("Eugene", rndPointsScored.Next(1, 7));
        }

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Football.AddScore("Farley", rndPointsScored.Next(1, 7));
        }

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Football.AddScore("Godrick", rndPointsScored.Next(1, 7));
        }
    }

    public void FillBasketballResults()
    {
        Random rndTimesScored = new Random(DateTime.Now.Millisecond);
        Random rndPointsScored = new Random(DateTime.Now.Millisecond);

        Basketball = new Basketball();
        Basketball.Contestants = Contestants;

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Basketball.AddScore("David", rndPointsScored.Next(1, 3));
        }

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Basketball.AddScore("Eugene", rndPointsScored.Next(1, 3));
        }

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Basketball.AddScore("Farley", rndPointsScored.Next(1, 3));
        }

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Basketball.AddScore("Godrick", rndPointsScored.Next(1, 3));
        }

        for (int i = 0; i &lt; rndTimesScored.Next(); i++)
        {
            Basketball.AddScore("Harry", rndPointsScored.Next(1, 3));
        }
    }
}

There…if you use the above code…all you have to do is instantiate a new SportResults object and you’ll have a bunch of ficticious data with some relational data between the players.  Now to display that data.  I whipped up a pretty basic WPF app with a RadTabControl and some RadGridViews to display the data.  Here’s what my MainWindow.xaml \ cs files look like as well as the custom user control I created to display results for reach sport.

SportResultsUserControl XAML:

<UserControl    d:DesignHeight="300" 
                d:DesignWidth="300" 
                mc:Ignorable="d" 
                x:Class="ToolTipinator.PlayerStatsUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                xmlns:local="clr-namespace:ToolTipinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <telerik:RadGridView    AutoGenerateColumns="False" 
                            ColumnWidth="*" 
                            x:Name="PlayerStatsRadGridView">
        <telerik:RadGridView.Columns>
            <telerik:GridViewDataColumn DataMemberBinding="{Binding SportName}" 
                                        Header="Sport" />
            <telerik:GridViewDataColumn DataMemberBinding="{Binding Score}" 
                                        Header="Score" />
            <telerik:GridViewDataColumn DataMemberBinding="{Binding AchievedDate}" 
                                        Header="Achieved Date" />
        </telerik:RadGridView.Columns>
    </telerik:RadGridView>
</UserControl>

SportResultsUserControl CS:

public partial class SportResultsUserControl : UserControl
{
    public SportResultsUserControl(IEnumerable&amp;lt;Player&amp;gt; contestants)
    {
        InitializeComponent();

        SportResultsRadGridView.ItemsSource = contestants;
    }
}

MainWindow XAML:

<Window Height="350" 
        Width="525" 
        Title="MainWindow" 
        x:Class="ToolTipinator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <telerik:RadTabControl  Margin="5, 5, 5, 5" 
                                x:Name="ToolTipinatorTabControl">
            <telerik:RadTabItem BorderBrush="Black" 
                                BorderThickness="1" 
                                Header="Hockey" 
                                x:Name="HockeyRadTabItem" />
            <telerik:RadTabItem BorderBrush="Black" 
                                BorderThickness="1" 
                                Header="Baseball" 
                                x:Name="BaseballRadTabItem" />
            <telerik:RadTabItem BorderBrush="Black" 
                                BorderThickness="1" 
                                Header="Football" 
                                x:Name="FootballRadTabItem" />
            <telerik:RadTabItem BorderBrush="Black" 
                                BorderThickness="1" 
                                Header="Basketball" 
                                x:Name="BasketballRadTabItem" />
        </telerik:RadTabControl>
    </Grid>
</Window>

MainWindow CS:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        SportResults = new SportResults();

        ((RadTabItem)ToolTipinatorTabControl.Items[0]).Content = new SportResultsUserControl(SportResults.Hockey.GetResults());
        ((RadTabItem)ToolTipinatorTabControl.Items[1]).Content = new SportResultsUserControl(SportResults.Baseball.GetResults());
        ((RadTabItem)ToolTipinatorTabControl.Items[2]).Content = new SportResultsUserControl(SportResults.Football.GetResults());
        ((RadTabItem)ToolTipinatorTabControl.Items[3]).Content = new SportResultsUserControl(SportResults.Basketball.GetResults());
    }

    public SportResults SportResults { get; set; }
}

Ok…now we should all have a spiffy little WPF app that has 4 tabs one for each sport of Hockey, Baseball, Football, and Basketball.

You may notice that David and Eugene have played in each sport…and some other’s have played in 2 or 3 different sports while some have only played in a single sport.  That’s where the relational data comes in and what I’ll be using in my ToolTip.

What I’m going to do next is set it up so when you hover over a player’s name, you’ll get a ToolTip popup that will display their scores in other sports.  Pretty short and sweet, but it gives a good example of how to set it up.  To do this I’m going to make a custom user control just for the ToolTip.

PlayerStatsUserControl XAML:

<UserControl    d:DesignHeight="300" 
                d:DesignWidth="300" 
                mc:Ignorable="d" 
                x:Class="ToolTipinator.PlayerStatsUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                xmlns:local="clr-namespace:ToolTipinator"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <telerik:RadGridView    AutoGenerateColumns="False" 
                            ColumnWidth="*" 
                            x:Name="PlayerStatsRadGridView">
        <telerik:RadGridView.Columns>
            <telerik:GridViewDataColumn DataMemberBinding="{Binding SportName}" 
                                        Header="Sport" />
            <telerik:GridViewDataColumn DataMemberBinding="{Binding Score}" 
                                        Header="Score" />
            <telerik:GridViewDataColumn DataMemberBinding="{Binding AchievedDate}" 
                                        Header="Achieved Date" />
        </telerik:RadGridView.Columns>
    </telerik:RadGridView>
</UserControl>

PlayerStatsUserControl CS:

public partial class PlayerStatsUserControl : UserControl
{
    public PlayerStatsUserControl(IEnumerable&amp;lt;Player&amp;gt; contestantResults)
    {
        InitializeComponent();

        PlayerStatsRadGridView.ItemsSource = contestantResults;
    }
}

Nothing overly complex.  The magic happens in a new event handler that will need to be added to the SportResultsUserControl for RowLoaded.  I also added an argument to the constructor to set the sport name for the SportResults object.  Here’s what my CS for my SportResultsUserControl looks like now.

SportResultsRadGridView CS:

public partial class SportResultsUserControl : UserControl
{
    public SportResultsUserControl(string sportName, IEnumerable&amp;lt;Player&amp;gt; contestants)
    {
        InitializeComponent();

        if (DesignerProperties.GetIsInDesignMode(this))
            return;

        SportName = sportName;
        SportResultsRadGridView.ItemsSource = contestants;
    }

    public string SportName { get; set; }

    private void SportResultsRadGridView_RowLoaded(object sender, RowLoadedEventArgs e)
    {
        GridViewRow currentRow = null;

        if (e.Row.GetType() == typeof(GridViewRow))
        {
            currentRow = (GridViewRow)e.Row;
        }

        if (currentRow != null)
        {
            var nameCell = currentRow.Cells.FirstOrDefault(c =&gt; c.Column.UniqueName == "UniqueName");

            if (nameCell != null)
            {
                string cellValue = ((GridViewCell)nameCell).Value.ToString();

                if (cellValue != null)
                {
                    IEnumerable&lt;Player&gt; contestantResults = MainWindow.SportResults.Contestants.Where(c =&gt; c.UniqueName == cellValue &amp;&amp; c.SportName != SportName);
                    PlayerStatsUserControl playerStatsToolTip = new PlayerStatsUserControl(contestantResults);
                    playerStatsToolTip.Width = 800;
                    nameCell.ToolTip = playerStatsToolTip;
                }
            }
        }
    }
}

In order for the above to work, I also had to turn the SportResults property that was a memeber of MainWindow into a static property so I could access it elsewhere as well as adding in the sport names to the constructor calls for the SportResultsUserControl…again, here’s what the CS for my MainWindow looks like now…

MainWindow CS:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        ToolTipService.ShowDurationProperty.OverrideMetadata(
            typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));

        MainWindow.SportResults = new SportResults();

        ((RadTabItem)ToolTipinatorTabControl.Items[0]).Content = new SportResultsUserControl(SportResults.Hockey.GetType().Name, SportResults.Hockey.GetResults());
        ((RadTabItem)ToolTipinatorTabControl.Items[1]).Content = new SportResultsUserControl(SportResults.Baseball.GetType().Name, SportResults.Baseball.GetResults());
        ((RadTabItem)ToolTipinatorTabControl.Items[2]).Content = new SportResultsUserControl(SportResults.Football.GetType().Name, SportResults.Football.GetResults());
        ((RadTabItem)ToolTipinatorTabControl.Items[3]).Content = new SportResultsUserControl(SportResults.Basketball.GetType().Name, SportResults.Basketball.GetResults());
    }

    public static SportResults SportResults { get; set; }
}

You may notice the call to ToolTipService in the MainWindow constructor as well now.  This will cause the ToolTip to stay open for about 47 days…so it’s not forever, but I’m pretty sure if a user cannot get the info they need out of a ToolTip within 47 days you have bigger problems with your app.  😉

Ok…that’s it for today’s journey.  Thanks for reading and as always I hope this helps someone.  Enjoy!  🙂

Setting a Custom WPF ToolTip and how to keep it shown…

IsolatedStorageProvider v PersistenceManager: Dawn of Persistence

Yeah…this title is in tribute to the soon to be released Batman v Superman: Dawn of Justice movie.  It looks like it’ll be pretty decent.

So this post originated from a need to persist my wonder apps’ column order and size between launchings of the app.  Telerik has created the PersistenceFramework to handle these sort of situations, but in my opinion the documentation could use a little sprucing up to help clear up when to use the various pieces of it…hence why I’m posting about it.

When I started digging into the PersistenceFramework, I first tried to use the PersistenceManager control and quickly found that even though it seemed really easy to implement…using it the way their documentation suggested seemed to be broken.  I’ll show the example code below as they said to do it in their documentation (maybe I read it wrong, but it seemed pretty straight forward…)

MainWindow XAML Code:
<Window Height="350"
        Title="MainWindow"
        Width="525"
        x:Class="PersistenceManagerator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation">
    <Grid>
        <telerik:RadGridView    AutoGenerateColumns="False"
                                ColumnWidth="*"
                                Loaded="PersistenceExampleRadGridView_Loaded"
                                NewRowPosition="None"
                                RowIndicatorVisibility="Collapsed"
                                Unloaded="PersistenceExampleRadGridView_Unloaded"
                                x:Name="PersistenceExampleRadGridView">
            <telerik:RadGridView.Columns>
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject1}"
                                            Header="Property 1" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject2}"
                                            Header="Property 2" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject3}"
                                            Header="Property 3" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject4}"
                                            Header="Property 4" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject5}"
                                            Header="Property 5" />
            </telerik:RadGridView.Columns>
        </telerik:RadGridView>
    </Grid>
</Window>

MainWindow XAML C# Code Behind:
using System.IO;
using System.Windows;
using Telerik.Windows.Persistence;

namespace PersistenceManagerator
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private Stream stream;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void PersistenceExampleRadGridView_Loaded(object sender,
RoutedEventArgs e)

        {
            stream.Position = 0L;
            PersistenceManager manager = new PersistenceManager();
            manager.Load(PersistenceExampleRadGridView, stream);
        }

        private void PersistenceExampleRadGridView_Unloaded(object sender,
RoutedEventArgs e)

        {
            PersistenceManager manager = new PersistenceManager();
            stream = manager.Save(PersistenceExampleRadGridView);
        }
    }
}

App XAML:
<Application    Exit="Application_Exit"
StartupUri="MainWindow.xaml"
                x:Class="PersistenceManagerator.App"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Telerik.Windows.Themes.Office2013;component/Themes/System.Windows.xaml" />
                <ResourceDictionary Source="/Telerik.Windows.Themes.Office2013;component/Themes/Telerik.Windows.Controls.xaml" />
                <ResourceDictionary Source="/Telerik.Windows.Themes.Office2013;component/Themes/Telerik.Windows.Controls.Input.xaml" />
                <ResourceDictionary Source="/Telerik.Windows.Themes.Office2013;component/Themes/Telerik.Windows.Controls.GridView.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

App XAML C# Code Behind:
using System.IO;
using System.Windows;
using Telerik.Windows.Persistence;

namespace PersistenceManagerator
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private Stream stream;

        public App()
        {
            this.InitializeComponent();
        }

        private void Application_Exit(object sender, ExitEventArgs e)
        {
            PersistenceManager manager = new PersistenceManager();
            stream = manager.Save(((MainWindow)App.Current.MainWindow).PersistenceExampleRadGridView);
        }
    }
}

I bolded where the problem is with the Telerik instruction.  You can’t just use a Stream object without it being initialized…and you can’t just new one up since there’s no constructor for it…so when it’s used within the grid’s Loaded event, it throws a NullReferenceException…so I wrote up a post and asked them to help me figure that one out…and while I waited for their response I discovered IsolatedStorageProvider.  This looked promising.  Here’s what my out of box attempt at using IsolatedStorageProvider looked like…

MainWindow XAML:
<Window Height="350"
        Title="MainWindow"
        Width="525"
        x:Class="IsolatedStorageProviderator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <telerik:RadGridView    AutoGenerateColumns="False"
                                ColumnWidth="*"
                                Loaded="IsolatedStorageExampleRadGridView_Loaded"
                                NewRowPosition="None"
                                RowIndicatorVisibility="Collapsed"
                                telerik:PersistenceManager.StorageId="IsolatedStorageExampleRadGridView"
                                Unloaded="IsolatedStorageExampleRadGridView_Unloaded"
                                x:Name="IsolatedStorageExampleRadGridView">
            <telerik:RadGridView.Columns>
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject1}"
                                            Header="Property 1" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject2}"
                                            Header="Property 2" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject3}"
                                            Header="Property 3" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject4}"
                                            Header="Property 4" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject5}"
                                            Header="Property 5" />
            </telerik:RadGridView.Columns>
        </telerik:RadGridView>
    </Grid>
</Window>

MainWindow XAML C# Code Behind:
using System.Windows;
using Telerik.Windows.Persistence.Storage;

namespace IsolatedStorageProviderator
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void IsolatedStorageExampleRadGridView_Loaded(
object sender, RoutedEventArgs e)

        {
            IsolatedStorageProvider storage =
new IsolatedStorageProvider();

            storage.LoadFromStorage(
new string[] { "IsolatedStorageExampleRadGridView" });

        }

        private void IsolatedStorageExampleRadGridView_Unloaded(
object sender, RoutedEventArgs e)

        {
            IsolatedStorageProvider storage =
new IsolatedStorageProvider();

            storage.SaveToStorage(
new string[] { "IsolatedStorageExampleRadGridView" });

        }
    }
}

App XAML:
<Application    Exit="Application_Exit"
                StartupUri="MainWindow.xaml"
                x:Class="IsolatedStorageProviderator.App"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Telerik.Windows.Themes.Office2013;component/Themes/System.Windows.xaml" />
                <ResourceDictionary Source="/Telerik.Windows.Themes.Office2013;component/Themes/Telerik.Windows.Controls.xaml" />
                <ResourceDictionary Source="/Telerik.Windows.Themes.Office2013;component/Themes/Telerik.Windows.Controls.Input.xaml" />
                <ResourceDictionary Source="/Telerik.Windows.Themes.Office2013;component/Themes/Telerik.Windows.Controls.GridView.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

App XAML C# Code Behind:
using System.Windows;
using Telerik.Windows.Persistence.Storage;

namespace IsolatedStorageProviderator
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public App()
        {
            this.InitializeComponent();
        }

        private void Application_Exit(object sender, ExitEventArgs e)
        {
            IsolatedStorageProvider storage =
new IsolatedStorageProvider();

            storage.SaveToStorage(
new string[] { "IsolatedStorageExampleRadGridView" });

        }
    }
}

This worked great for persisting resized columns between app launches…but still did not work for persisting reordered columns.  At this point in our EXCITING story, Telerik had gotten back to me and clued me in on the little fact that out of the box, the PersistenceFramework did not perseve the order of the columns.  Ah…yes, that would explain why my example fails at that.  They also told me that I’d need to implement a CustomPropertyProvider for the RadGridView in order to do this…so off I went to figure that out.  So I needed my own custom object for containing the info I wanted to preserve.  Here’s what I ended up with…

ColumnProxy C# Code:
using Telerik.Windows.Controls;

namespace IsolatedStorageProviderator
{
    public class ColumnProxy
    {
        public string UniqueName { get; set; }

        public int DisplayOrder { get; set; }

        public string Header { get; set; }

        public GridViewLength Width { get; set; }
    }
}

Then I created a new class for my CustomPropertyProvider…

GridViewCustomPropertyProvider C# Code:
using System.Collections.Generic;
using Telerik.Windows.Controls;
using Telerik.Windows.Persistence.Services;

namespace IsolatedStorageProviderator
{
    public class GridViewCustomPropertyProvider : ICustomPropertyProvider
    {
        public CustomPropertyInfo[] GetCustomProperties()
        {
            return new CustomPropertyInfo[]
            {
                new CustomPropertyInfo("Columns", typeof(List<ColumnProxy>)),
            };
        }

        public void InitializeObject(object context) { }

        public object InitializeValue(
CustomPropertyInfo customPropertyInfo, object context)

        {
            return null;
        }

        public object ProvideValue(
CustomPropertyInfo customPropertyInfo, object context)

        {
            RadGridView gridView = context as RadGridView;

            switch (customPropertyInfo.Name)
            {
                case "Columns":
                {
                    List<ColumnProxy> columnProxies = new List<ColumnProxy>();

                    foreach (GridViewColumn column in gridView.Columns)
                    {
                        columnProxies.Add(new ColumnProxy()
                        {
                            UniqueName = column.UniqueName,
                            Header = column.Header.ToString(),
                            DisplayOrder = column.DisplayIndex,
                            Width = column.Width,
                        });
                    }

                    return columnProxies;
                }
            }

            return null;
        }

        public void RestoreValue(
CustomPropertyInfo customPropertyInfo,
object context,
object value)

        {
            if(value != null && value.ToString() != "None")
            {
                RadGridView gridView = context as RadGridView;

                switch (customPropertyInfo.Name)
                {
                    case "Columns":
                    {
                        List<ColumnProxy> columnProxies = value as List<ColumnProxy>;

                        foreach (ColumnProxy proxy in columnProxies)
                        {
                            GridViewColumn column =
gridView.Columns[proxy.UniqueName];

                            column.DisplayIndex = proxy.DisplayOrder;
                            column.Header = proxy.Header;
                            column.Width = proxy.Width;
                        }
                    }
                    break;
                }
            }
        }
    }
}

And lastly I had to add in a little something to tell the PersistenceManager to use my CustomPropertyProvider…

Extra code added to MainWindow XAML C# Code Behind:
public MainWindow()
{
    InitializeComponent();

    ServiceProvider.RegisterPersistenceProvider<ICustomPropertyProvider>(typeof(RadGridView), new GridViewCustomPropertyProvider());
}

The part in bold is what I added to the MainWindow code behind.  After I added the above code my wonder app’s columns size and order are now preserved between launchings (as well as column headers, but these weren’t editable so it’s probably not neccessary).

Hope this helps someone…enjoy!  🙂

 

IsolatedStorageProvider v PersistenceManager: Dawn of Persistence

ForeignKeyReferenceAlreadyHasValueException…nuff said…

The full title of this post should be “System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException: Operation is not valid due to the current state of the object.” but that would have been a mouthful…I mean ForeignKeyReferenceAlreadyHasValueException kinda just flows out right?  Anyway…

This at first fooled me into thinking it had something to do with the RadGridView control I was using from Telerik UI for WPF…however when I dug a little deeper it had more to do with the Linq to SQL classes that I was using.  They were getting confused about foreign key relationships during the update of their values.  The article I linked seemed to have a good method of explaining what’s going on and how to work around it.  The workaround is instead of setting the value of the foreign key related column…set the object property that gets created for it in the Linq to SQL class.

http://sandeep-tada.blogspot.com/2014/03/systemdatalinqforeignkeyreferencealread.html

Hope this helps someone.  🙂

ForeignKeyReferenceAlreadyHasValueException…nuff said…

Getting the NewRowPosition cake and eating it too…

Ah…a double post day!  I just bumped into this today so I figured I’d write about it in real time…oh yeah…from Bing to my brain to WordPress…

So I’ve been building this super duper massively all encompassing wonder app that’s supposed to be able to, you know…like time travel and crap…ok, so that’s a stretch goal feature…but it pretty much does everything else.  I’ve been using a Visual Studio WPF app as a template and using Telerik UI for WPF controls for pretty much everything else.  The specific control I’m blogging about is a RadGridView control.  This info should also apply to the Silverlight version of the control with the same name…but I’ve never actually used it to test so it’s just theoretically the same since it appears they both derive from the same base code…so…it should work.  *shrugs*

One of the things this mega app does is add rows to a table in a database…woah!!!!  Unheard of…I know.  Now what I will say up front is that the RadGridView made it super easy to add a row…it was very quick to implement and easy to understand.  Here’s what my little test grid looks like with just the very minimal default code.  Add functionality is technically enabled, but there’s no visible controls to use it (you can use the keyboard to put the grid into insert mode, but I’m not going to go into that here).

Example RadGridView XAML Code (sorry if the formatting looks bad, doing the best I can…feel free to give some suggestions on how to make it look better):
<telerik:RadGridView    AutoGenerateColumns="False"
                        ColumnWidth="*"
                        x:Name="AddNewRowExampleRadGridView">
    <telerik:RadGridView.Columns>
        <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject1}"
                                    Header="Property 1" />
        <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject2}"
                                    Header="Property 2" />
        <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject3}"
                                    Header="Property 3" />
        <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject4}"
                                    Header="Property 4" />
        <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject5}"
                                    Header="Property 5" />
    </telerik:RadGridView.Columns>
</telerik:RadGridView>

Example RadGridView:
RadGridView-AddNewRowDefault

What was a little tricky was figuring out how to fiddle with it’s default UI that it displays for adding a new row.  Here’s what it looks like when adding in the property needed to enable the default built in UI.

XAML Code (just posting the additional parts):
<telerik:RadGridView    AutoGenerateColumns="False"
                        ColumnWidth="*"
                        NewRowPosition="Top"
                        x:Name="AddNewRowExampleRadGridView">

Example:
RadGridView-AddNewRowTop

You may or may not notice the additional “Click here to add new item” section under the column headers now.  Yup…that’s the default UI that comes along with the NewRowPosition property.  There’s also the values “Bottom” and “None” that you can use…”None” is the default value.

Personally I’m fine with the default, but the folks I’m making the app for didn’t like it and just wanted a button that would insert a new row when they were ready to do it rather than have the “Click here to add new item” row always taking up space on the grid.  Understandable…so what I first did was set NewRowPosition=”None” which makes sure the “Click here to add new item” row never shows up.  Then I added a button and wired it up to flip the grid into insert mode when clicked…I’m going to repost the entire example code now because I also added some RowDefinitions for the Grid container that ultimately contains the RadGridView.

XAML Code:
<Window x:Class="RadGridViewinator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
        Title="RadGridView - Add New Row Example"
        WindowStartupLocation="CenterScreen"
        WindowState="Maximized">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <telerik:RadButton  Click="AddNewRowButton_Click"
                            Content="Add New Row"
                            Grid.Row="0"
                            Height="30"
                            HorizontalAlignment="Left"
                            Margin="5, 5, 5, 5"
                            Width="100"
                            x:Name="AddNewRowButton" />
        <telerik:RadGridView    AutoGenerateColumns="False"
                                ColumnWidth="*"
                                Grid.Row="1"
                                NewRowPosition="None"
                                x:Name="AddNewRowExampleRadGridView">
            <telerik:RadGridView.Columns>
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject1}"
                                            Header="Property 1" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject2}"
                                            Header="Property 2" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject3}"
                                            Header="Property 3" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject4}"
                                            Header="Property 4" />
                <telerik:GridViewDataColumn DataMemberBinding="{Binding PropertyFromYourBoundObject5}"
                                            Header="Property 5" />
            </telerik:RadGridView.Columns>
        </telerik:RadGridView>
    </Grid>
</Window>

C# Event Code for XAML Code Behind:
private void AddNewRowButton_Click(object sender, RoutedEventArgs e)
{
// calling .BeginInsert on a RadGridView flips it into insert mode

    AddNewRowExampleRadGridView.BeginInsert();
}

Example:
RadGridView-AddNewRowNoneWithButton

Yup…that’s the look they were wanting…however what quickly became obvious is that nothing happened when clicking the “Add New Row” button…and I’ll bet some of you are already figuring out why.  I still had the NewRowPosition=”None” set…so no matter if the grid was in insert mode or not, there was not going to be a UI displayed for doing the insert…and actually there’s 3 reasons why it doesn’t work yet.  I also still need wire up telling the grid what kind of new object to add to the grid by using the “AddingNewDataItem” event and then I need to tell the grid what to do with the new data after it’s been collected by using the “RowEditEnded” event.  Here’s what the code changes look like for the relevant pieces of code.

XAML Code:
<telerik:RadGridView    AddingNewDataItem="AddNewRowExampleRadGridView_AddingNewDataItem" 
                       AutoGenerateColumns="False"
                       ColumnWidth="*"
                       Grid.Row="1"
                       NewRowPosition="None"                        RowEditEnded="AddNewRowExampleRadGridView_RowEditEnded"
                       x:Name="AddNewRowExampleRadGridView">

C# Event Code for XAML Code Behind:
private void AddNewRowButton_Click(object sender, RoutedEventArgs e)
{
    AddNewRowExampleRadGridView.NewRowPosition =
        GridViewNewRowPosition.Top;
    AddNewRowExampleRadGridView.BeginInsert();
}

private void AddNewRowExampleRadGridView_AddingNewDataItem(object sender,
Telerik.Windows.Controls.GridView.GridViewAddingNewEventArgs e)

{
    // put whatever object you're binding your grid to here
    e.NewObject = new object();
}

private void AddNewRowExampleRadGridView_RowEditEnded(object sender,
Telerik.Windows.Controls.GridViewRowEditEndedEventArgs e)

{
    if (e.EditAction == GridViewEditAction.Cancel)
    {
        AddNewRowExampleRadGridView.NewRowPosition =
            GridViewNewRowPosition.None;
        return;
    }

    if (e.EditOperationType == GridViewEditOperationType.Insert)
    {
        // add e.NewData to the database
    }
    else
    {
        // update e.NewData in the database
    }

    AddNewRowExampleRadGridView.NewRowPosition =
        GridViewNewRowPosition.None;

}

If you notice in the above code behind, I added code to set the NewRowPosition back to “Top” just before I flip the grid into insert mode and then back to “None” when I’m finished to achieve the desired effect.  The rest of the implementation details will vary depending on what kind of object you’re using and how you’re binding to your data source…but this should paint the basic picture of how I managed to make my customers happy.  Hope it helps…enjoy!

Getting the NewRowPosition cake and eating it too…