Unblocking .NET DLLs on Mac Catalina

There’s a new headache in town. If you run self-hosted Azure DevOps agent, or .NET Core project, on a Mac running Catalina (v 10.15+) and Microsoft Edge, you will have noticed a new behavior where the OS prevents the .NET assembly from working.

This is because it has been marked with a “quarantine” file attribute when it was downloaded with the browser. It’s a security measure that I’m familiar with on Windows. I would typically remove this attribute by right clicking on the file, select “Properties”, then check “Unblock”:

Unblocking a ZIP file on Windows.

At this point, I suspected I had a good idea of what was happening, I just needed to figure out how to check for and remove it on a Mac. I reached out to the dev community on twitter and asked around. Thanks to Eric Lawrence who pointed me to the Chromium code change that shows it is indeed quarantining the file(s).

After reviewing how to read and remove file attributes, indeed I found a com.apple.quarantine attribute on the tarball archive file that is downloaded from Azure DevOps. Since it contains all the .NET assemblies, we can just unblock the tar.gz file and all contents will unblock as well.

Solution

Let’s go back to Azure DevOps Agent Pool page, where you download your agent package (see here if you’ve never done this before).

Click the download button to download the tarball file

Now that the file is in the downloads folder, we can use the xattr command to list the attributes. In my case, I’m checking the downloaded compressed file.

xattr vsts-agent-oxs-x64-2.159.2.tar.gz
You’ll see the attributes listed with the xattr command

Bingo! Notice the com.apple.quarantine attribute? That’s the one causing this headache. Now we can see the attribute name, we can remove it by calling the the xattr command again with -d attributeName fileName parameters .

xattr -d com.apple.quarantine vsts-agent-oxs-x64-2.159.2.tar.gz
You will need to give Terminal permission to access the Downloads folder.

Finally list the attributes again to confirm the quarantine has been removed:

Confirm the attribute was removed

Now you can finish extracting the tarball and setting up the agent (or running your .NET core application).

Preparing apps for Windows X and Surface Duo or Neo Devices.

Preface – This is not Microsoft-provided information, just my guess after digging around the Microsoft.UI.Xaml source code after a conversation on Twitter. This is not coming from any MVP-NDA or other NDA source. I will update this post when official information becomes available.

There is suspiciously missing control from the XAML Gallery app – TwoPaneView. It’s in the WinUI 2.2 release, but there’s no documentation, guidance or examples of it being used (yet). The only thing you’ll find is the API reference, which is automatically generated during the build process.

It didn’t go completely unnoticed, another MVP Fons Sonnemans, did find the control in the preview SDK and wrote this blog post. However, now armed with the knowledge of two-screen devices and Windows X on the horizon, I wanted to dig deeper.

API Support

If you look at the Fon’s demo, it might seem like all the control is good for right now is visual state changes that occur within a single Window. If this is what we’ll use for multi-window-single-instance apps, there needs to be some sort of OS level event that bubles useful information up the API. This iswhere my conjecture begins…

I reviewed the source code of the control and found some interesting code in DisplayRegionHelper::GetRegionInfo()

It appears to check if the display region is WindowingEnvironmentKind::Tiled from calling a WinRT API WindowingEnvironment::GetForCurrentView() . Then, the most interesting part that I think supports multi-screen setups, is regions = winrt::Windows::UI::WindowManagement::DisplayRegion::GetRegionsForCurrentView()

Here’s the snippet with the aforementioned lines highlighted:

 winrt::WindowingEnvironment environment{ nullptr };
        try
        {
            environment = winrt::WindowingEnvironment::GetForCurrentView();
        } catch(...) {}

        // Verify that the window is Tiled
        if (environment)
        {
            if (environment.Kind() == winrt::WindowingEnvironmentKind::Tiled)
            {
                winrt::IVectorView<winrt::Windows::UI::WindowManagement::DisplayRegion> regions = winrt::Windows::UI::WindowManagement::DisplayRegion::GetRegionsForCurrentView();
                info.RegionCount = std::min(regions.Size(), c_maxRegions);

                // More than one region
                if (info.RegionCount == 2)
                {
                    winrt::Rect windowRect = WindowRect();

                    if (windowRect.Width > windowRect.Height)
                    {
                        info.Mode = winrt::TwoPaneViewMode::Wide;
                        float width = windowRect.Width / 2;
                        info.Regions[0] = { 0, 0, width, windowRect.Height };
                        info.Regions[1] = { width, 0, width, windowRect.Height };
                    }
                    else
                    {
                        info.Mode = winrt::TwoPaneViewMode::Tall;
                        float height = windowRect.Height / 2;
                        info.Regions[0] = { 0, 0, windowRect.Width, height };
                        info.Regions[1] = { 0, height, windowRect.Width, height };
                    }
                }
            }

This is the basis of my theory, I could be way off. If I’m wrong, what’s the worst thing that happened? I was forced to think about my application in a multi-window environment? Win-win!

Demo

There are no official demos of this that I could find. However, the same source code also had a UITest! I isolated that UI test in a runnable project, that is what you see a recording of in the tweet embedded above. You can download the project from here DuoNeoTest.zip

Note: I did not set the InsiderSDK as the Target SDK. If you do have SDK 18990 installed and have a device running insider preview, just change the target in the project properties.

Postmortem – Child node “2” compilation error after updating to 18362 SDK

One of the more frustrating things for me is not being able to debug a problem. After updating my UWP app’s Target SDK to 18362 (Windows 10 1903), I was no longer able to compile. I got a very vague error. that I had to dig out of the msbuild logs:

Child node "2" exited prematurely. Shutting down. 

So I dug into the msbuild logs and found the original error message

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Microsoft.Windows.UI.Xaml.Build.Tasks.NativeMethodsHelper.Write(…)
   at Microsoft.Xaml.XBF.XbfGenerator.GenerateXbfFromStreams(…)

Which was ultimately caused by this process exception

UNHANDLED EXCEPTIONS FROM PROCESS 22472:
System.IO.IOException: Pipe is broken.
   at System.IO.Pipes.PipeStream.WinIOError(Int32 errorCode)
   at System.IO.Pipes.PipeStream.BeginWriteCore(Byte[] buffer, Int32 offset, Int32 count, AsyncCallback callback, Object state)
   at System.IO.Pipes.PipeStream.WriteCore(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.Pipes.PipeStream.Write(Byte[] buffer, Int32 offset, Int32 count)
   at Microsoft.Build.BackEnd.NodeEndpointOutOfProcBase.RunReadLoop(Stream localReadPipe, Stream localWritePipe, ConcurrentQueue`1 localPacketQueue, AutoResetEvent localPacketAvailable, AutoResetEvent localTerminatePacketPump)

After several hours try trying different things to isolate what was causing the issue, I reached out to the engineering team at Microsoft. They took over and dug deeper, which actually required the WDG group to investigate (big shout out to Alan!).

The problem stems from using a property of a custom base class in XAML. For example, if you have a custom control, for example a Telerik control. You need to have the base class listed in the xmlns of the XAML, even if you aren’t explicitly using it.

To make this simple, let’s use an example. This is the specific scenario that caused my error, but it can happen with any custom control. Look at the code sample below. As you can see on Line 5 the xml namespace is defined as input, and it is used on Line 10 for the RadRangeSlider.

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:input="using:Telerik.UI.Xaml.Controls.Input"
             mc:Ignorable="d"
             d:DesignHeight="400"
             d:DesignWidth="400">
    <Grid>
        <input:RadRangeSlider Minimum="0" Maximum="10"/>
    </Grid>
</UserControl>

Notice the Minimum and Maximum properties, those are actually defined in a base class, not in the RadRangeSlider class… this is what causes the exception in the latest SDK.

This will be fixed in a future UWP SDK release, but for now the workaround is to define the xmlns of the base class, even though you’re not explicitly using it. Here’s the fixed version of that code, notice Line 6.

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:input="using:Telerik.UI.Xaml.Controls.Input"
             xmlns:controls="using:Telerik.UI.Xaml.Controls"
             mc:Ignorable="d"
             d:DesignHeight="400"
             d:DesignWidth="400">
    <Grid>
        <input:RadRangeSlider Minimum="0" Maximum="10"/>
    </Grid>
</UserControl>

I hope the SEO for this post is good enough to go to the top of your Google/Bing search for the eror and this explanation helps someone who encounters the issue in the future. Happy coding!

Draw Your Own Xamarin Pie Chart dynamically

In this tutorial, I’ll show you how to draw your own pie chart just using lines and a list of data items. The code itself is in C#, but the concept can be used on any platform that has a drawing library and ArcSegments.

You can view the entire solution in this GitHub Gist. Okay, let’s get started!

Setting Up

The first thing you’ll need to understand is how the platform’s drawing library makes arcs. In this code, I’ll be using RadPath from Telerik UI for Xamarin most libraries work the same way. RadPath lets you use a custom Geometry which has RadArcSegment object, which has helpful StartAngle and SweepAngle properties.

Let’s start with the data model, with simple Title and Value properties:

public class ChartDataPoint
{
    public string Title { get; set; }
    public double Value { get; set; }
}

Next, let’s create a list that is populated with some activities to represent a 24 hour period.

var dataPoints = new List<ChartDataPoint>
{
    new ChartDataPoint { Title = "Work", Value = 9 },
    new ChartDataPoint { Title = "Commute", Value = 1.5 },
    new ChartDataPoint { Title = "Leisure", Value = 6 },
    new ChartDataPoint { Title = "Sleep", Value = 7.5 },
};

Finally, to finish the setup, create a list of colors we can use for the pie slices.

var colors = new List<Color>
{
    Color.FromHex("#BAB65A"),
    Color.FromHex("#6196D1"),
    Color.FromHex("#3D4268"),
    Color.FromHex("#8A56E2"),
};

Part 1 – Creating the Slices

Now that we have some items, lets move on to generating and drawing the slices. First, we’ll create a container to put the pie and legend into. A Grid is convenient option because we can have two rows, one for the pie and one for the legend.

// Root container to hold the chart and any legend
var container = new Grid();
container.RowDefinitions.Add(new RowDefinition { Height = new GridLength(3, GridUnitType.Star) });
container.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });

Now we can start calulating the slice sizes, to do this, we need a total value to get a percentage of that total. We also need a variable to hold the current position on the 360 degree arc where the slices are rendered.

// Sum up all the values to be displayed
var totalValue = dataPoints.Sum(d => d.Value);

// Variable to keep track of where each slice ended.
double currentPosition = 0;

Now for the guts of the operation. We need iterate over the data points and create the arc segments using the data point’s Value property. The code comments will guide you through what each line does, in a nustshell here’s the lifecycle

  • Calculate the data item’s percentage of the total sum
  • Use that percentage to get what percent of 360 degree pie that slice needs
  • Create the RadArcSegment using the current position for the StartAngle and the angle percentage as the SweepAngle
  • Construct the RadPath using the ArgSegment’s geometry and a color from the colors list
// Iterate over the data points to create slices.
for (int i = 0; i < dataPoints.Count; i++)
{
    // Determine the what percentage that data item's value is of the whole
    double slicePercentage = dataPoints[i].Value / totalValue;

    // Calculate the sweep angle using that percentage amount.
    double sweep = slicePercentage * 360;

    // Create the ArcSegment using the current position and sweep
    var segment = new RadArcSegment
    {
        Center = new Point(0.5, 0.5),
        Size = new Size(1, 1),
        StartAngle = currentPosition,
        SweepAngle = sweep,
    };

    // Important - Calculate the last segment's ending angle in order to have a valid start angle for the next loop.
    currentPosition = currentPosition + sweep - 360;

    // Prepare the required PathFigure and add the ArcSegment
    var figure = new RadPathFigure { StartPoint = new Point(0.5, 0.5) };
    figure.Segments.Add(segment);

    // Create the PathGeometry and add the PathFigure
    var geometry = new RadPathGeometry();
    geometry.Figures.Add(figure);

    // Construct the RadPath
    // - Select a Fill color from the brushes parameter (important: use a modulus to wrap to the beginning)
    // - Use the Geometry created from the value
    var slice = new RadPath
    {
        Fill = new RadSolidColorBrush(colors[i % colors.Count]),
        Geometry = geometry,
        HorizontalOptions = LayoutOptions.Center,
        VerticalOptions = LayoutOptions.Center,
        WidthRequest = 100,
        HeightRequest = 100,
        Margin = new Thickness(0, 20, 0, 0)
    };

    // This isn't necessary, but added for completion.
    Grid.SetRow(slice, 0);

    // Finally, add it to the container.
    container.Children.Add(slice);
}

At this point, you now have a full 360 degree chart, with colored slices for each of the data points representing their percentage of the whole.

Part 2 – Creating the Legend

The next phase of the operation is to create the legend. This needs to create text for each slice, as well as a marker that matches the same color of that slice. This could have been done in the same loop as creating the slice, but having a separate loop lets you decide to use a legend or not.

Again, we iterate over the data points. This time we use the Title property of the data point to create the text. To match the color, we use the same modulus to get index and assign it to a RadBorder that creates a thick bar underneath the text.

// Create a horizontal StackLayout to hold the legend items
var legendPanel = new StackLayout
{
    Orientation = StackOrientation.Horizontal,
    HorizontalOptions = LayoutOptions.Center,
    VerticalOptions = LayoutOptions.Center,
    Margin = new Thickness(0, 16, 0, 0),
    Spacing = 5
};

// Iterate over the data points and create a legend item with a matching color
for (int i = 0; i < dataPoints.Count; i++)
{
    // Use a RadBorder with only a bottom thickness and match the color to the slice
    var legendItem = new RadBorder
    {
        BorderColor = colors[i % colors.Count],
        BorderThickness = new Thickness(0, 0, 0, 2)
    };

    // Create a Label for each data point and use the Title property
    var label = new Label
    {
        Text = dataPoints[i].Title,
        FontSize = 12,
        Margin = new Thickness(0, 0, 0, 2),
        TextColor = Color.DimGray
    };

    legendItem.Content = label;

    legendPanel.Children.Add(legendItem);
}

// Insert the legend panel in the root container's 2nd row.
Grid.SetRow(legendPanel, 1);
container.Children.Add(legendPanel);

The last thing to do is add the entire container to the UI. In this example, I’m just setting the entire page’s content to the container Grid..

this.Content = container;

Wrapping Up

I hope this is useful for those times when you need ultimate control over rendering of a chart. If you need more complex setup, I recommend the RadPieChart itself, which is far more feature complete than drawing a few arcs 🙂

Talk to the View


There are many scenarios where a UI component might not have a Command or bindable property available for certain features that you need to access from the view model. There is a simple, but powerful, thing you can do to allow this and not break MVVM or testing capability, leverage the Interface.

Scenario

Let’s imagine the scenario where we have a special ListView control that has a HighlightItem method that can only be called with a direct reference to that control. There isn’t a Command or DependencyProperty (aka BindableProperty in Xamarin.Forms) alternative, so it has to be done in the code behind.

To keep this simple, here’s the page XAML…

<Page x:Class="MyPage">
    <Page.DataContext>
        <MyPageViewModel x:Name="ViewModel" />
    </Page.DataContext>

    <SpecialListView x:Name="MyListView" />
</Page>

…and here is the page code behind.

public partial class MyPage : Page
{
    public MyPage()
    {
        InitializeComponent();

        // The only way to use HighlightItem method is in the code-behind
        MyListView.HighlightItem(itemToHighlight);
    }
}

As you can see, the HighlightItem method has to be used in the code behind because it has a reference to ‘MyListView’. However, you need to use it in the view model, like this:

public class MyPageViewModel
{
    private void PleaseHighlightItemNow()
    {
        // But, you need to highlight the item from view model
    }
}

Solution

One solution for this is to define an interface.

public interface IHighlightableView
{
    void Highlight(object item);
}

With this, you can add a property to the view model and invoke the method:

public class MyPageViewModel
{
    public IHighlightableView View { get; set; }

    private void PleaseHighlightItem()
    {
        // Call the method on the interface!
        View?.Highlight(itemToHighlight);
    }
}

Now, the interface can be implemented on the page to complete the circle

public partial class MyPage : Page, IHighlightableView
{
    public MyPage()
    {
        InitializeComponent();

        // Assign this page instance to the view model
        ViewModel.View = this;
    }

    private void Highlight(object itemToHighlight)
    {
       // Now you can directly use the UI component's method
        MyListView.HighlightItem(itemToHighlight);
    }
}

Summary

This is a simple approach that will work if you don’t want to (or are not allowed to) use an existing MVVM framework’s built-in DependencyInjection and IoC features. Enjoy!

Using PowerShell to Install an SDK in a DevOps Build Pipeline

I have an open source UWP project that relies on the Ad SDK and Engagement SDK to be installed to Visual Studio. This normally isn’t a problem, because you can just run the MSI on your PC (see installation instructions here and here).

I decided to move the project into DevOps so that I get some of that CI/CD goodness to build and publish new releases automatically to the Microsoft Store. However, there was a problem, when trying to build the project in a DevOps build pipeline, you’ll get the following error:

This means that the Hosted VS2017 Agent that DevOps uses to build a UWP project doesn’t have the Extensions SDKs installed. I have two choices to move forward:

  • Use a private Agent (this uses your local PC to build the project via a server connection )
  • Find a way to install the SDK into the Hosted agent

I did try the first option. I set up the Windows Agent on my Surface Book, connected it to DevOps and it works nicely (this is why you see that early successful build early in the first screenshot above). However, it’s not a solution for me because it means my PC has to be always on and network-connected.

My friend, and MVP peer, Oren Novotny mentioned that you can actually use a PowerShell script to download the MSI file and install it to the Hosted VS2017, this was the break I was looking for!

To implement this, I downloaded the MSI files I needed from the Visual Studio Marketplace and put them into an Azure Blob. It provides me with two reliable URLs to download the files in the PowerShell script.

Next, I wrote the very simple script. It has two phases: a download phase and an install phase (with a quiet switch “/q”). Here’s what that looks like:

# Predefined Variables
$adSdkUrl = "https://dvlup.blob.core.windows.net/general-app-files/MSIs/MicrosoftAdvertisingSDK.msi"
 $servicesSdkUrl = "https://dvlup.blob.core.windows.net/general-app-files/MSIs/MicrosoftStoreServicesSDK.msi"
 $adSdkPath = Join-Path $env:TEMP "MicrosoftAdvertisingSDK.msi"
 $servicesSdkPath = Join-Path $env:TEMP "MicrosoftStoreServicesSDK.msi"

# Download the files to local temp folder
 Write-Output "downloading $adSdkUrl…"
 Invoke-WebRequest -Uri $adSdkUrl -OutFile $adSDKPath
 Write-Output "downloading $servicesSdkUrl…"
 Invoke-WebRequest -Uri $servicesSdkUrl -OutFile $servicesSdkPath

# Install the SDKs (use the "qn" flag to install silently)
 Write-Output "installing $adSdkPath…"
 Start-Process $adSdkPath -ArgumentList "/q" -Wait
 Write-Output "installing $servicesSdkPath…"
 Start-Process $servicesSdkPath -ArgumentList "/q" -Wait

Next, you need to add a Powershell step to your DevOps Build pipeline and paste in the script. Here’s a screenshot to help guide you:

Now, when the build is triggered, it will download and install the missing SDKs before building the project! Here’s the result of that build step in the Hosted agent’s console:

I hope this post finds you as soon as you need help. It took me several days, a dozen different documentation articles (some are hyperlinked above) and my MVP peers to find a good simple solution that didn’t require my PC .

LoginDialog for easy Live SDK OAuth

Almost every developer I speak with agrees that getting a nicely working OAuth flow can be difficult. After much trial and error, I’ve built a UWP ContentDialog that makes this easy and has built-in refresh token support.

Using the LoginDialog

This is very simple to use, when the app launches, instantiate the dialog by passing your Live SDK ClientID and let the LoginDialog do the heavy lifting:

// Pass your app's OAuth ClientId (sometimes called an AppId)
var loginDialog = new LoginDialog("YourAppClientId");

// *** Perform login **** //
// Case 1 - If the user was previously signed in, there is a refresh_token stored and no user-entered credentials are needed.
// Case 2 - If the access token fails, or if it's the first sign-in, the user will see a popup to enter credentials.
await loginDialog.SignInAsync();

if(!string.IsNullOrEmpty(loginDialog.Authentication))
{
    // This will be the access token you can use for API calls.
    var accessToken = loginDialog.Authentication;

    // Make your API calls with the accessToken, for example:
    var user = await apiService.GetUSerProfileAsync(accessToken);
}

This works just like a MessageDialog, but instead will show a WebView for the user to sign in, or automatically sign them in silently if they were previously logged in.

Get the full source code in this GitHub Gist.

See this tweet for a video of what it looks like when it automatically logs you in and here’s a screenshot what it looks like when the dialog needs to appear:

Dialog Visible

Operational Summary

Explaining OAuth 2.0 is outside the scope of this article, but to summarize there are a couple round trip to the authentication endpoints.

  1. Load the sign-in landing page and the user signs in with their credentials.
  2. In the redirect after successful sign-in, you’ll get an access_token and a refresh_token for that authenticated user.

The access_token is what is used in all of your API calls that verifies the application is operating on the behalf of that user. The access_token expires after a preset amount of time (e.g. 60 minutes), after which time you have to request another access_token.

Here is where the refresh_token comes in handy. You could go back to step #1 above, but this would require the user re-enter their credentials. Instead, you can just use the refresh_token  you already have for the user to get a new access_token without ever interrupting them to show a WebView.

This is what the LoginDialog does for you, when you call SignInAsync() the following happens:

  • Checks to see if there is a refresh_token stored locally. If there isn’t one, show the WebView and have the user sign in for the first time.
  • If there is a refresh_token stored locally, then use that to automatically get a new access_token without showing the dialog

Since this is general OAuth 2.0 workflow, you could probably adapt the dialog to work for other OAuth 2 endpoints by changing the dialog’s _signInUri, _signOutUri and _redirectUrl values.

Wrapping Up

The LoginDialog lets you have the least amount of interruption to the user while quickly getting an access token for your API use.

The RS5 Update Resources and Tutorials

My friends at Microsoft have put together an amazing compilation of resources of all the new stuff available in the Windows 10 October 2018 Update (aka RS5). Below is a list of some of those resources relevant to developers and enthusiasts, enjoy!

SDK Downloads

New Samples and Sample Updates

  • XAML Hosting API Sample uses the new XAML hosting API and C++/WinRT to demonstrate usage of XAML controls within a Win32 application.
  • Customer Orders Database Sample has been updated to use the new Data Grid and other new controls in RS5.
  • Photo Editor C++/WinRT Sample has been updated to build with the RS5 SDK and to use the new single_threaded_observable_vector function to create an IObservableVector for data binding. An additional performance update will be coming out in a couple weeks.
  • Data binding and MVVM is a new topic that was identified as a need based on the Code First program. The topic describes the benefits of using the MVVM design pattern and how it has been applied to the sample apps.

Videos

Application Developer Resources

Driver Developer Resources

Error XF001: Xamarin.Forms targets have been imported multiple times

If you’re in the process of updating a Xamarin.Forms app to a more modern style project set up (NET Standard 2.0, PackageReference, etc), you may get the following error.

 

Error XF001: Xamarin.Forms targets have been imported multiple times. Please check your project file and remove the duplicate import(s).

 

This can be because of the move to using PackageReference for your NuGet packages and a simple fix awaits you.

  1. Close Visual Studio and navigate to the Solution in File Explorer
  2. Delete the hidden .vs folder
  3. Go into each affected project sub folder and delete the following files; project_name.nuget.props and project_name.nuget.targets
  4. Open the solution in Visual Studio, do a Clean and Rebuild, 

 

You should no longer see the error and be able to deploy.

 

 

 

 

Custom Events and Xamarin.Forms Effect

This post will walk you through setting up a Xamarin Platform Effect and show you how to wire up an event handler so you can leverage native APIs in the case that the Xamarin.Forms wrapper doesn’t yet have. For this example, I’ll use the Telerik UI for Xamarin RadCalendar.

tl;dr Full source code on GitHub here.

I get frequently asked how to enable RangeSelection (aka Multiple Selection) for the RadCalendar. The native platform calendar controls all have Range Selection available, so we only need to create a  Platform Effect to access that native feature.

Note: If you’re not familiar with Effects, I recommend visiting this article first to understand the fundamentals (it’s a quick tutorial).

The main things I need are:

  • DateTime StartDate
  • DateTime EndDate
  • DateRangeChangedEventArgs (class)
  • DateRangeChanged (event)
  • DateRangeChanged (delegate)

I’ll walk you through the different parts below, you can also see just the relevant classes in this GitHub Gist or get everything in this GitHub repo

Class Library

First, let’s get the event args out of the way because we’ll need this defined before writing the Effect.

(view code) Portable/Effects/DateRangeChangedEventArgs.cs

2018-07-16_2020

Now we can move on to the Effect definition that lives in the class library project.  The class defines the rest of items I listed above, I’ve called out how the event is invoked, thus subscribers to the vent will have their event handlers executed.

(view code) Portable/Effects/RangeSelectionEffect.Forms.cs

2018-07-16_2020

With this set up , we can add the Effect to the Xamarin.Forms XAML RadCalendar instance:

(view code) Portable/MainPage.xaml

2018-07-16_2033

Code behind, this is just to set the start and end dates to test the Effect:

(view code) Portable/MainPage.xaml.cs

2018-07-16_2035

But we can’t run it just yet. It’s time to implement the native Effect classes., it’s where the magic happens. I’ll go through each platform separately instead of hitting you over the head with it all at once.

UWP

The calendar control for UWP is the UI for UWP RadCalendar.  In the documentation, we can see it supports multiple selection by flipping the SelectionMode flag to Multiple.

The next thing to consider is how to actually set the date range. This is done using the SelectedDateRange property to an instance of CalendarDateRange. This is what we needed the event for! You’ll see that when the Effect is Attached to the control

(view code) UWP/Effects/RangeSelectionEffect.Uwp.cs

2018-07-16_2018

iOS

For iOS the native control is a UI for Xamarin.iOS TKCalendar and in a similar fashion as UWP, we find the Selection modes to support Range Selection.

Notice we need to use the native control’s selection property and convert from DateTime to NSDate

(view code) iOS/Effects/RangeSelectionEffect.iOS.cs

2018-07-16_2029

Android

And finally, the same approach is used for the native Android calendar, UI for Xamarin.Android RadCalendarView.

Notice we need to use the native control’s selection property and convert from DateTime to Java Calendar

(view code) Android/Effects/RangeSelectionEffect.Android.cs

2018-07-16_2030

And that’s it! All three platform’s Calendar control now will show range selection.

Disclaimer

This isn’t production-ready code, it’s proof of concept and there are no defensive techniques in place (i.e. try/catch). I wanted to keep it as simple as possible to focus on the concepts.

If you want to use the code for your app, I’m happy I could help (MIT license). Just please don’t copy-paste it all and call it a day, then ping me on Twitter later and say “it’s broken!”.  The native platform logic should be inside try catch blocks and I would make sure the DateTime conversions are accurate for your needs.

Enjoy!