Fody and “OnPropertyChanged” – The Unusual behavior

As a WPF Developer, Fody has been an extremely vital component in my aresenal. It takes a lot of burden off me by injecting some of the boiler plate codes. Despite that, there is one unusually behavior of Fody, which I have difficulty in digesting.

For demonstration, let us create an example View for ourselves. For the sake of example here, am relying on Caliburn Micro for my MVVM.

<StackPanel>
    <TextBlock Text="{Binding RandomNumber}" FontSize="16" HorizontalAlignment="Center"/>
    <Button Content="Randomize" x:Name="Randomize"/>
</StackPanel>

The ViewModel, at the moment, looks like the following.

public class ShellViewModel:Screen
{
    private Random _random;

    public ShellViewModel()
    {
        _random = new Random();
    }
    public long RandomNumber { get; set; }

    public void Randomize()
    {
        RandomNumber = _random.Next();
    }
}

This works perfectly fine. If you examine the code via ILSpy, you could notice that Fody has injected the code correctly as one would expect.

public class ShellViewModel : Screen
{
	private Random _random;

	public long RandomNumber
	{
		[CompilerGenerated]
		get
		{
			return <RandomNumber>k__BackingField;
		}
		[CompilerGenerated]
		set
		{
			if (<RandomNumber>k__BackingField != value)
			{
				<RandomNumber>k__BackingField = value;
				OnPropertyChanged(<>PropertyChangedEventArgs.RandomNumber);
			}
		}
	}


	public ShellViewModel()
	{
		_random = new Random();
	}

	public void Randomize()
	{
		RandomNumber = _random.Next();
	}
}

The OnPropertyChanged call here points to the PropertyChangedBase.OnPropertyChanged method of Caliburn Micro (PropertyChangedBase implements INotifyPropertyChanged in Caliburn Micro)

protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
	this.PropertyChanged?.Invoke(this, e);
}

Let us add some more code to our ViewModel and see how Fody behaves. Assume that we need to do some special handling when the Property Changed event is triggered in our ViewModel. So we will go ahead and subscribe in our constructor.

public ShellViewModel()
{
    _random = new Random();
    PropertyChanged += HandlePropertyChanged;
}

private void HandlePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    // Do something here
}

Fody, quite expectedly, handles the change gracefully.

public class ShellViewModel : Screen
{
	private Random _random;

	public long RandomNumber
	{
		[CompilerGenerated]
		get
		{
			return <RandomNumber>k__BackingField;
		}
		[CompilerGenerated]
		set
		{
			if (<RandomNumber>k__BackingField != value)
			{
				<RandomNumber>k__BackingField = value;
				OnPropertyChanged(<>PropertyChangedEventArgs.RandomNumber);
			}
		}
	}

	public ShellViewModel()
	{
		_random = new Random();
		PropertyChanged += HandlePropertyChanged;
	}

	private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
	{
	}

	public void Randomize()
	{
		RandomNumber = _random.Next();
	}
}

So far, so good. This is exactly like one would expect. The injected code still calls the INotifyPropertyChanged implementation(in our base class).

set
{
    if (<RandomNumber>k__BackingField != value)
    {
        <RandomNumber>k__BackingField = value;
        OnPropertyChanged(<>PropertyChangedEventArgs.RandomNumber);
    }
}


But this is also where things start to behave strangely (one could counter argue this, but i still feel the following behavior is strange). Let us rename our PropertyChanged event handler as HandlePropertyChanged.

public ShellViewModel()
{
    _random = new Random();
    PropertyChanged += OnPropertyChanged;
}

private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    // Do something here
}

Let us examine the code generated by Fody now.

public long RandomNumber
{
    [CompilerGenerated]
    get
    {
        return <RandomNumber>k__BackingField;
    }
    [CompilerGenerated]
    set
    {
        if (<RandomNumber>k__BackingField != value)
        {
            <RandomNumber>k__BackingField = value;
            OnPropertyChanged(this, <>PropertyChangedEventArgs.RandomNumber);
        }
    }
}

public ShellViewModel()
{
    _random = new Random();
    PropertyChanged += OnPropertyChanged;
}

private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
}

The code injected by Fody has changed now and instead of calling the implementation of INotifyPropertyChanged, it is invoking the Event Handler of PropertyChanged.

set
{
    if (<RandomNumber>k__BackingField != value)
    {
        <RandomNumber>k__BackingField = value;
        OnPropertyChanged(this, <>PropertyChangedEventArgs.RandomNumber);
    }
}

In a way, if one was to think about it, it is a good behavior. Fody, on sensing that we have subscribing to PropertyChanged handler, ensure we are calling the handler instead. But, then the behavior should have been same when the Handler name was HandlePropertyChanged.

The strange part, which am not comfortable with this, is that this call to Handler occurs only when the Handler is named OnPropertyChanged. In every other case the injected code points to the implementation of INotifyPropertyChanged. I would like to call this a bug – You could expect the behavior to be consistent.

One of the troubles with this behavior (other than the obvious inconsistency) is that is extremely hard to trace this issue (remember – Fody injects the code) unless one is aware of this strange naming convention followed by Fody. I hope Fody does something to correct, if not make the make the behavior more consistent.

Custom Traits in xUnit

One of the implicit key characterstics which define the readability of any Unit Test cases is its ability to be grouped depending on multiple factors.

NUnit uses CategoryAttribute, while MSTest uses the TestCategoryAttribute for grouping tests. With xUnit, you could make use the TraitAttribute to achieve this. However, this is not short of problems of its own.

The most inconvenient part of using Traits are that they are basically a name-value pair of strings. This makes it vurnerable to typos or a headache when decide to change the name. Luckily, xUnit provides us an easy to use extensibility point.

ITraitAttribute and ITraitDiscoverer

You can create your own Custom Traits which could be used to decorate the test cases. For the sake of example, let us create two attributes – FeatureAttribute and BugAttribute which would be used to Categorize Tests cases for Features and Bugs.

[TraitDiscoverer(FeatureDiscoverer.TypeName,TraitDiscovererBase.AssemblyName)]
public class FeatureAttribute:Attribute, ITraitAttribute
{
    public string Id { get; set; }
    public FeatureAttribute(string id) => Id = id;
    public FeatureAttribute() { }
}

The attribute implements the ITraitAttribute interface and has a property to indicate the Id of the Feature. What is more interesting is the TraitDiscoverer attribute. It isn’t sufficient that we have the attributes, but it also needs to be discovered by the Test Explorer. This is where the TraitDiscoverer comes into place.

The attribute accepts two parameters, the fully qualified name of the Discoverer associated with the FeatureAttribute and the assembly which defines it. The Dicoverer for Feature is defined as

public class FeatureDiscoverer : TraitDiscovererBase,ITraitDiscoverer
{
    public const string TypeName = TraitDiscovererBase.AssemblyName + ".Helpers.CustomTraits.FeatureDiscoverer";

    protected override string CategoryName => "Feature";
    public override IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute)
    {
        yield return GetCategory();
        var id = traitAttribute.GetNamedArgument<string>("Id");
        if (!string.IsNullOrEmpty(id))
        {
            yield return new KeyValuePair<string, string>(TypeName, id);
        }
    }
}

public class TraitDiscovererBase : ITraitDiscoverer
{
    public const string AssemblyName = "Nt.Infrastructure.Tests";
    protected const string Category = nameof(Category);
    protected virtual string CategoryName => nameof(CategoryName);

    protected KeyValuePair<string,string> GetCategory()
    {
        return new KeyValuePair<string, string>(Category, CategoryName);
    }
    public virtual IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute)
    {
        return Enumerable.Empty<KeyValuePair<string,string>>();
    }
}

The Discoverer needs to be implement the ITraitDiscoverer which has a single method, GetTraits and returns a collection of KeyValuePairs. That’s all you need. Now you could decorate your Test Cases as the following

[Theory]
[MemberData(nameof(CreateMovieTest_ResponseStatus_200_TestData))]
[Feature("1523")]
public async Task CreateMovieTest_ResponseStatus_200(CreateMovieRequest request, CreateMovieResponse expectedResult)
{
    // Test Case
}

The above is whole lot cleaner than the following

[Trait("Category","Feature")]
[Trait("Feature","1523")]

MahApps HamburgerMenu and Caliburn Micro

MahApps is probably one of the most used UI library among WPF developers, with a galaxy of great controls. Despite that, recently I was surprised to see lack of proper example for Hamburger Menu control, particulary MVVM based. I was also more keen to know how to make best use of capabilities of Caliburn Micro along the way.

Here is how I ended up achieving my objectives.

XAML : Control, Content, ItemTemplate and HeaderTemplate

The first step would be to define the Control in Xaml, along with desired ContendTemplate,ItemTemplate and HeaderTemplate.

<mah:HamburgerMenu x:Name="Menu" DisplayMode="CompactOverlay" SelectedItem="{Binding ActiveItem,Converter={StaticResource SelectedItemConverter}}"
           ItemsSource="{Binding MenuItems,Converter={StaticResource MenuConverter}}"
           cal:Message.Attach="[Event ItemClick]=[Action MenuSelectionChanged($source,$eventArgs)]"
           HamburgerMenuHeaderTemplate="{StaticResource MenuHeaderTemplate}"
           ItemTemplate="{StaticResource MenuItemTemplate}">
    <mah:HamburgerMenu.Content>
       <ContentControl Grid.Column="0" Grid.Row="1" x:Name="ActiveItem" />
    </mah:HamburgerMenu.Content>
</mah:HamburgerMenu>

As you would have noticed couple of converters in here. We will get to that in a moment, but first let us define our MenuItemTemplate and MenuHeaderTemplate.

<!-- Menu Header Template -->
<DataTemplate x:Key="MenuHeaderTemplate">
    <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"  FontSize="16"  Foreground="White"  Text="Sample App" />
</DataTemplate>

<!-- Menu Item Template -->
<DataTemplate x:Key="MenuItemTemplate">
    <Grid x:Name="RootGrid" Height="48" Background="Transparent">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="48" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <ContentControl Grid.Column="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Content="{Binding Icon}"
            Focusable="False" />
        <TextBlock Grid.Column="1"
           VerticalAlignment="Center"
           FontSize="16"
           Text="{Binding Label}" />
    </Grid>
</DataTemplate>

At this point, we need to create our ViewModels for each of the menu items. As we notice from the templates above, there are couple of fields which are required for each of Menu Items – a Title, and a Icon (_Icon is not quite mandatory, depending on the type of Menu Item you are creating).

Destination ViewModels

Let us go ahead and define our ViewModels for Menu Items.

public class PageViewModelBase : Screen
{
    public virtual string Title { get; }
    public virtual object Icon { get; }
}
public class HomePageViewModel : PageViewModelBase
{
    public override string Title => "Home";
    public override object Icon => new PackIconMaterial { Kind = PackIconMaterialKind.Home };
}
public class ProfilePageViewModel : PageViewModelBase
{
    public override string Title => "Profile";
    public override object Icon => new PackIconMaterial { Kind = PackIconMaterialKind.AccountStar };
}

In the above example, we are using the MahApps.Metro.IconPack for Icons, but that is only an implementation detail which could can easily vary. For sake of simplicity, I have omitted the Views here, but that could be found in the example solution given in the end of this article.

The Glue : ValueConverters and ActiveItem

We now have different ViewModels defined, and it is time to create the dynamic collection to bind to our HamburgerMenu Control. So, in our ShellViewModel, we will do the following.

public ShellViewModel()
{
    MenuItems = new List<PageViewModelBase>
    {
        new HomePageViewModel(),
        new ProfilePageViewModel()
    };
}

public IEnumerable<PageViewModelBase> MenuItems { get; }

We are almost there, and now as the final step, we need to write our converters. There are two converters involved,

  • PageToMenuItemConverter – Converts from ViewModel Collection to HamburgerMenuIconItem.
  • SelectedItemConverter – Parse the ViewModel from SelectedItem
public class PageToMenuItemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is IEnumerable<PageViewModelBase> nmItemCollection)
        {
            return nmItemCollection.Select(item => new HamburgerMenuIconItem
            {
                Tag = item,
                Icon = item.Icon,
                Label = item.Title
            });
        }
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class SelectedItemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value is HamburgerMenuIconItem menuItem ? menuItem.Tag : value;
    }
}

That’s all we need. Your menu would be up and running now. You can find the example code in my Github here

Bootstrap 4 Theming – Changing Primary Color

Bootstrap allows you to customize the existing definition greatly. With Bootstrap 4, the way we customize the theming has changed a bit, and that’s exactly what I would like to explain here – step by step.

Step 1: Get the Bootstrap source

Of course you need to get the bootstrap source to customize it. You would then recompile it to get the required css.

We could use npm to get the source.

 npm install bootstrap --save

Step 2: Install Sass compiler

We would rely on Sass to customize the variables and maps for creating our custom bootstap. You could head over to the Sass Website to read the installation instruction in detail. But I have tried to highlight the necessary steps.

You would need to install Ruby first and then follow it up with the following command.

npm install -g sass

Step 3: Arrange your folder structure.

The last step before we actually start customizing is to arrange the folder structure as mentioned by the Bootstrap website.

your-project/
├── scss
│   └── custom.scss
└── node_modules/
    └── bootstrap
        ├── js
        └── scss

Step 4: Customize your theme.

You cutomize your theme by modiying the variables and maps in the bootstrap code. Using your custom.css you import the whole bootstrap source (you could alternatively pick and choose if you are sure).

@import "node_modules/bootstrap/scss/bootstrap";

The way team bootstrap has written the code is that it allows you to customize the variable, provided it is defined before the import.

In this example, we will alter the Primary color to a shade of purpose.

$theme-colors: (
  "primary": #4b1076,
);
@import "node_modules/bootstrap/scss/bootstrap";


Step 5: Compile your scss and include in your application

Now all that is left is to compile your custom.scss and create your css which you can then use in your application. To compile the scss, use the following command.

sass custom.scss custom.css

That’s all you need.

Getting started with DocFx

I have been lately pretty impressed with the DocFx, Microsoft’s static documentation generator. In this blog post, I thought I would outline how easy it is to configure the DocFx for generating documentation for your project.

Assumption : Project already has Xml documentations enabled and the code have the necessary comments.

Step 1 : Install Chocolatey

  • Head over to Chocolatey’s official website and copy the command given in the getting started page.
  • Open the Powershell with Administrative prileges and executed the command copied.

For reference, the command is as follows.

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

Step 2 : Install Docfx

  • Install Doxfx with following command
cinst docfx -y

Step 3 : Generate DocFx Project

  • Generate Sample DocFx Project for your Project Documentation with the following command.
docfx init -q

Or if you would like to dictate the name for your project.

docfx init -q -o <DocumentationProjectName>

This would generate sample project for your purpose.

Step 4: Specify Project Details

The next step is to specify the location of your Project Files. This has to be mentioned in the docfx.json file.

I am including only the relavant part of docfx.json.

"src": [
        {
          "files": "src/infrastructure/Nt.Infrastructure.UI/**.csproj",
          "cwd": "..",
          "src": "../.."
        }

As you can observe, we have used base path using the src and cwd values, and use the files to specify the projects under consideration.

Step 5 : Build and Generate your documentation website.

That’s all the configuration you would need. Rest of the magic would be done by Docfx for you. To build the project and generate the website, you need to use the following command.

docfx --serve

And there you have it, you have your documentation up and running in the development server. You could access it via (http://localhost:8080/).

We will explore more features of DocFx and related configuration in later post, but surely, this would get you started.

Oxyplot : Selectable Point

While OxyPlot continues to be one of the attractive plotting library for .Net developers, there are times when you find longing for features that could make it even more better.

One of such feature is ability to select/highlight a point in a series. While there is a Selectable Property with LineSeries, it doesn’t quite let you highlight the Data Point.

However, this can be easily achieved using another series with has a single point (the point which was selected). I guess it is easier to show the code than describe it. So let’s go ahead and hit Visual Studio.

We would begin by defining couple of Custom Series. For the sake of example, I am considering LineSeries in this case. The first Custom Series we would define would be the series which displays the Selected Item.

public class SelectedLineSeries:LineSeries
{
}

As you can observe, it hardly does anything new. The purpose of the definition is to allow is to easily differenciate between our special Series from Line Series. This would be further clarified when introduce our second custom series, which denotes a series which can be selected.

public class SelectableLineSeries:LineSeries
{
public bool IsDataPointSelectable { get; set; }

public DataPoint CurrentSelection { get; set; }

public OxyColor SelectedDataPointColor { get; set; } = OxyColors.Red;

public double SelectedMarkerSize { get; set; }

public SelectableLineSeries()
{
SelectedMarkerSize = MarkerSize;
MouseDown += SelectableLineSeries_MouseDown;
}

private void SelectableLineSeries_MouseDown(object sender, OxyMouseDownEventArgs e)
{
if (IsDataPointSelectable)
{
var activeSeries = (sender as OxyPlot.Series.Series);
var currentPlotModel = activeSeries.PlotModel;
var nearestPoint = activeSeries.GetNearestPoint(e.Position, false);
CurrentSelection = nearestPoint.DataPoint;

currentPlotModel = ClearCurrentSelection(currentPlotModel);

var selectedSeries = new SelectedLineSeries
{
MarkerSize = MarkerSize + 2,
MarkerFill = SelectedDataPointColor,
MarkerType = MarkerType
};

selectedSeries.Points.Add(CurrentSelection);
currentPlotModel.Series.Add(selectedSeries);
currentPlotModel.InvalidatePlot(true);
}
}

private PlotModel ClearCurrentSelection(PlotModel plotModel)
{
while(plotModel.Series.Any(x=> x is SelectedLineSeries))
{
plotModel.Series.Remove(plotModel.Series.First(x=> x is SelectedLineSeries));
}
return plotModel;
}

}

The core functionality of the Class is defined in the Series Mouse Down event. This ensures that whenever a point is selected, a new series is added to the Parent PlotModel, which has a single Data Point ( same as the currently selected data point).

Well, that’s all our definition is about. Now we can go ahead and define our PlotModel to be used along with Series definition.

var random = new Random();
var collection = Enumerable.Range(1, 5).Select(x => new DataPoint(x, random.Next(100, 400))).ToList();
var series = new SelectableLineSeries
{
IsDataPointSelectable = true,
MarkerFill = OxyColors.Blue,
MarkerType = MarkerType.Square,
LineStyle = LineStyle.Solid,
Color = OxyColors.Blue,
ItemsSource = collection,
MarkerSize = 5
};

GraphModel = new PlotModel();

GraphModel.Axes.Add(new OxyPlot.Axes.LinearAxis
{
Position = Axes.AxisPosition.Bottom
});

GraphModel.Axes.Add(new OxyPlot.Axes.LinearAxis
{
Position = Axes.AxisPosition.Left
});

GraphModel.Series.Add(series);
GraphModel.InvalidatePlot(true);

That’s it. Hit F5 and test your Selectable Line Series. You can access the source code shown in the demo in my Github.

Oxyplot Selectable

Zoom Rectangle with OxyPlot

Let’s stick to OxyPlot for some more time. This time, we would attempt to change the color of Zoom Rectangle. For those who are new to Oxyplot, the control allows you to zoom in a particular location by permitting the user to draw rectangles in the graph. This particular rectangle is known as Zoom Rectangle.

You can enable the Zoom Rectangle by using the PlotController.

public PlotController ChartController { get; set; }

ChartController = new PlotController();
ChartController.BindMouseDown(OxyMouseButton.Left, PlotCommands.ZoomRectangle);

You can now bind the PlotController with your OxyPlot PlotView instance in XAML.

<oxy:PlotView Model="{Binding Model}" Controller="{Binding ChartController,UpdateSourceTrigger=PropertyChanged}"/>

While would enable the Zoom Rectangle, you would ideally would like to do a bit of Customization, for example, changing the appearance(color) of the rectangle.

You can do so by customizing the ZoomRectangleTemplate. Let’s change the default color of the Zoom Rectangle we just created.

<oxy:PlotView Model="{Binding Model}" Controller="{Binding ChartController,UpdateSourceTrigger=PropertyChanged}">
<oxy:PlotView.ZoomRectangleTemplate>
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<Rectangle Fill="Orange" />
</Border>
</ControlTemplate>
</oxy:PlotView.ZoomRectangleTemplate>
</oxy:PlotView>

That’s all you need. You now have your fully customized Zoom Rectangle.

Oxyplot

Caliburn.Micro Template Pack now supports VS 2019

So glad to announce that Caliburn.Micro Template Pack now supports Visual Studio 2019.

Caliburn.Micro Template Pack for WPF contains a comprehensive collection of Project Templates and Code Snippets for developing WPF application using Caliburn.Micro. It also includes Bootstrap templates for SimpleContainer, MEF and Unity.

You can download the template from Visual Studio Marketplace

You can read more on the template here

Oxyplot : Using Datagrid for tooltip

Oxyplot uses Tracker Controls for displaying tooltip. You could extend the display by Customizing the Default Tracker control. In this article, we will look deeper into that Tracker Control and aim to display a Grid in the Tracker Control.

For sake of example, let us assume a Fruit Seller, who wants to plot his sales per day on a Line Series. To begin with, let us create some random data to emulate the sales.

private enum eFruits
{
Apple,
Banana,
Mango,
Jackfruit
}
public IEnumerable<Fruit> Generate()
{
var random = new Random();
return Enumerable.Range(1, 10)
.SelectMany(x =>
{
return ((eFruits[])Enum.GetValues(typeof(eFruits))).Select(f =>
new Fruit
{
Name = f.ToString(),
ItemsSold = random.Next(0, 100),
Date = DateTime.Now.AddDays(x)
});
});
}

In his first attempt, he plots the Line Series, grouping the data by Date on which items where sold.

private void CreatePlotModel(IEnumerable<Fruit> totalSalesDetails)
{
var salesGrouping = totalSalesDetails.GroupBy(x => x.Date);
var yAxis = new LinearAxis
{
Position = AxisPosition.Left
};
var xAxis = new DateTimeAxis
{
Position = AxisPosition.Bottom,
Minimum = DateTimeAxis.ToDouble(salesGrouping.Min(x => x.Key)),
Maximum = DateTimeAxis.ToDouble(salesGrouping.Max(x => x.Key)),
};
DataPlotModel.Axes.Add(yAxis);
DataPlotModel.Axes.Add(xAxis);

var lineSeries = new LineSeries
{
MarkerFill = OxyColors.Blue,
MarkerType = MarkerType.Circle,
TrackerFormatString = "{0}\n{1}: {2:dd.MM.yy}\n{3}: {4:0.###}"
};
lineSeries.Points.AddRange(salesGrouping.Select(x => new DataPoint(DateTimeAxis.ToDouble(x.Key), x.Sum(c => c.ItemsSold))));
DataPlotModel.Series.Add(lineSeries);
}

XAML Part

<oxy:PlotView Model="{Binding DataPlotModel}"/>

This gave him his desired out as the following.

Initial Design

However, this left a lot to be desired. Currently the tooltip would display total sales per day. He knew he could decifer that from the chart itself and hence wanted to make use of tooltip for something more useful. He decided to display the details of sales, ie, number of each fruit sold during the day in the tooltip. This would give him a better insight into the sales.

Grid inside the Tooltip

While he knew the Oxyplot’s default Tracker control could be customized, he soon realized this wasn’t as straightward as he assumed it to be. As always, the problem lies in the details.

<oxy:PlotView Model="{Binding DataPlotModel}">
<oxy:PlotView.DefaultTrackerTemplate>
<ControlTemplate>
<oxy:TrackerControl>
<DataGrid ItemsSource="{Binding}"/>
</oxy:TrackerControl>
</ControlTemplate>
</oxy:PlotView.DefaultTrackerTemplate>
</oxy:PlotView>

The TrackerControl accepted a TrackerHitResult as its DataContext, displaying the TrackerHitResult.Text as tooltip, and there seemed no direct way for him to bind a collection of items to the TrackerControl if he was to customize it to include a DataGrid.

A bit of googling and with a tip from his mentor, he soon realized that there was a way instead. What if he can write a Converter that reads the required information from TrackerHitResult and returns a collection for DataGrid to bind to.

For this, he needs to extend the DataPoint so that it would contain the sub-collection information. This would ensure that the same would be available in the DataContext (TrackerHitResult)

public class ExtendedDataPoint : IDataPointProvider
{
public DateTime Date { get; set; }
public IEnumerable<Fruit> ItemsSold { get; set; }

public DataPoint GetDataPoint()
{
return new DataPoint(DateTimeAxis.ToDouble(Date), ItemsSold.Sum(x=>x.ItemsSold));
}
}

He can now rewrite his CreatePlotModel method to use the ExtendedDataPoint for adding points to the Line Series.

var lineSeries = new LineSeries
{
MarkerFill = OxyColors.Blue,
MarkerType = MarkerType.Circle,
};

lineSeries.ItemsSource = salesGrouping.Select(x => new ExtendedDataPoint
{
Date = x.Key.Date,
ItemsSold = x.ToList()
});

As you can notice, he has removed the TrackerFormatString as well, as he would not require it any longer. The next step would be to write the Converter required for transforming the TrackerHitResult to IEnumerable<Fruit>.

public class TrackerHitResultConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is TrackerHitResult data)
{
return (data.Item as ExtendedDataPoint).ItemsSold;
}
return null;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Since he had already defined his required sub-collection as a part of ExtendedDataPoint, the role of TrackerHitResultConverter became a lot easier. The last piece in the puzzle was to modify the Xaml to use the Converter.

<oxy:PlotView Model="{Binding DataPlotModel}">
<oxy:PlotView.DefaultTrackerTemplate>
<ControlTemplate>
<oxy:TrackerControl Position="{Binding Position}" Background="Transparent" BorderBrush="Transparent">
<DataGrid HeadersVisibility="None" ItemsSource="{Binding Converter={StaticResource TrackerHitResultConverter}}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Date,StringFormat=d}" Header="Date" />
<DataGridTextColumn Binding="{Binding Name}" Header="Fruit"/>
<DataGridTextColumn Binding="{Binding ItemsSold}" Header="Items Sold" />
</DataGrid.Columns>
</DataGrid>
</oxy:TrackerControl>
</ControlTemplate>
</oxy:PlotView.DefaultTrackerTemplate>
</oxy:PlotView>

With that in place, he was ready to Hit F5 and vola, his tooltip was ready.

Final output

Entire source code discussed in this article is available in my Github.

Rectangle Annotation With Rounded Corner in Oxyplot

It has been few months since I started playing around with Oxyplot, and it continues to impresses me. Having said that, there are times when certain challenges are thrown showing light on certain limitation of the tool. One of such limitation and a way to overcome is being discussed in this blog post.

Oxyplot supports a wide range of Annotations including Rectangle Annotation, Text Annotation, Image Annotation, Ellipse Annotation and Line Annotation among others. These by itself are extremely powerful and easy to use. However, recently I came across a business need where I would need to draw a Rectangle Annotation with Rounded Corner.

The Oxyplot library doesn’t allow us an in-build mechanism to do it. However, the good part is, that is not end of the road. We can create our own custom annotation. Before we hit code, it would be a good idea to discuss how we plan to create a RoundedCornerRectangleAnnotation.

Rounded Rectangle OxyPlot

The crux of the idea is to generate the rounded effect by drawing a set of two rectangle and 4 circle/ellipse. The Radius of the circles would be the intended corner radius of the final rectangle.

Okay, that’s enough for idea, let’s go ahead and write some code.

Part of Custom Annotation Class

 private void DrawRoundedRectangle(IRenderContext rc)
        {
            var xMin = double.IsNaN(this.MinimumX) || this.MinimumX.Equals(double.MinValue)
                            ? this.ClipByXAxis
                                ? this.XAxis.ActualMinimum
                                : this.XAxis.InverseTransform(this.PlotModel.PlotArea.Left)
                            : this.MinimumX;
            var xMax = double.IsNaN(this.MaximumX) || this.MaximumX.Equals(double.MaxValue)
                            ? this.ClipByXAxis
                                ? this.XAxis.ActualMaximum
                                : this.XAxis.InverseTransform(this.PlotModel.PlotArea.Right)
                            : this.MaximumX;
            var yMin = double.IsNaN(this.MinimumY) || this.MinimumY.Equals(double.MinValue)
                            ? this.ClipByYAxis
                                ? this.YAxis.ActualMinimum
                                : this.YAxis.InverseTransform(this.PlotModel.PlotArea.Bottom)
                            : this.MinimumY;
            var yMax = double.IsNaN(this.MaximumY) || this.MaximumY.Equals(double.MaxValue)
                            ? this.ClipByYAxis
                                ? this.YAxis.ActualMaximum
                                : this.YAxis.InverseTransform(this.PlotModel.PlotArea.Top)
                            : this.MaximumY;
            var xCornerRadius = (CornerRadius / (XAxis.Maximum - XAxis.Minimum)) * 100;
            var yCornerRadius = (CornerRadius / (YAxis.Maximum - YAxis.Minimum)) * 100;
            this.screenRectangleWithClippedXAxis = new OxyRect(this.Transform(xMin + xCornerRadius, yMin), this.Transform(xMax - xCornerRadius, yMax));
            this.screenRectangleWithClippedYAxis = new OxyRect(this.Transform(xMin, yMin + yCornerRadius), this.Transform(xMax, yMax - yCornerRadius));
            this.screenEllipseLeftBottom = new OxyRect(this.Transform(xMin, yMin), this.Transform(xMin + 2* xCornerRadius, yMin + 2* yCornerRadius));
            this.screenEllipseLeftTop = new OxyRect(this.Transform(xMin, yMax), this.Transform(xMin + 2 * xCornerRadius, yMax - 2 * yCornerRadius));
            this.screenEllipseRightBottom = new OxyRect(this.Transform(xMax, yMin), this.Transform(xMax - 2 * xCornerRadius, yMin + 2 * yCornerRadius));
            this.screenEllipseRightTop = new OxyRect(this.Transform(xMax, yMax), this.Transform(xMax - 2 * xCornerRadius, yMax - 2 * yCornerRadius));
            // clip to the area defined by the axes
            var clippingRectangle = OxyRect.Create(
                this.ClipByXAxis ? this.XAxis.ScreenMin.X : this.PlotModel.PlotArea.Left,
                this.ClipByYAxis ? this.YAxis.ScreenMin.Y : this.PlotModel.PlotArea.Top,
                this.ClipByXAxis ? this.XAxis.ScreenMax.X : this.PlotModel.PlotArea.Right,
                this.ClipByYAxis ? this.YAxis.ScreenMax.Y : this.PlotModel.PlotArea.Bottom);
            rc.DrawClippedRectangle(clippingRectangle,this.screenRectangleWithClippedXAxis,
                                            this.GetSelectableFillColor(this.Fill),
                                            this.GetSelectableColor(this.Stroke),
                                            this.StrokeThickness);
            rc.DrawClippedRectangle(clippingRectangle, this.screenRectangleWithClippedYAxis,
                                            this.GetSelectableFillColor(this.Fill),
                                            this.GetSelectableColor(this.Stroke),
                                            this.StrokeThickness);
            rc.DrawClippedEllipse(clippingRectangle, screenEllipseLeftBottom,
                                            this.GetSelectableFillColor(this.Fill),
                                            this.GetSelectableColor(this.Stroke),
                                            this.StrokeThickness);
            rc.DrawClippedEllipse(clippingRectangle, screenEllipseLeftTop,
                                this.GetSelectableFillColor(this.Fill),
                                this.GetSelectableColor(this.Stroke),
                                this.StrokeThickness);
            rc.DrawClippedEllipse(clippingRectangle, screenEllipseRightBottom,
                                this.GetSelectableFillColor(this.Fill),
                                this.GetSelectableColor(this.Stroke),
                                this.StrokeThickness);
            rc.DrawClippedEllipse(clippingRectangle, screenEllipseRightTop,
                                this.GetSelectableFillColor(this.Fill),
                                this.GetSelectableColor(this.Stroke),
                                this.StrokeThickness);
            if (!string.IsNullOrEmpty(this.Text))
            {
                var textPosition = this.GetActualTextPosition(() => this.screenRectangle.Center);
                rc.DrawClippedText(
                    clippingRectangle,
                    textPosition,
                    this.Text,
                    this.ActualTextColor,
                    this.ActualFont,
                    this.ActualFontSize,
                    this.ActualFontWeight,
                    this.TextRotation,
                    HorizontalAlignment.Center,
                    VerticalAlignment.Middle);
            }
        }

 

You can find the complete class code in Gist Link. The complete source code, including the WPF Wrapper can be found in the forked implementation at my GitHub.

PS:The implementation, of course has its own flaws. You cannot set Stroke for the Rounded Rectangle, hopefully, I would be able to workaround this issue soon.