Browsed by
Author: rudyhuyn

Be careful with Path.GetExtension() and Path.GetFileName()

Be careful with Path.GetExtension() and Path.GetFileName()

Path.GetExtension as well as Path.GetFileName are very useful, but you will have some issues if you don’t read the document.

One important thing to know about them is that they are very restricted and related to NTFS file system. It’s safe to use with NTFS/FAT32 paths and not with URI, HFS+ (OsX) and almost all LINUX paths.

Capture

Invalid characters are: quote (“), less than (<), greater than (>), pipe (|), backspace (\b), null (\0), tab (\t). So if your string contains one of these characters (including the file path!!), Path.GetExtension or Path.GetFileName will return an exception. This restriction doesn’t make a lot of sense in my opinion, why verify if the file path contains an invalid character if the only thing we want is the file extension?

Some example of paths not compatible with Path.GetExtension:

  • test/Document <1>.txt
  • work|2010/meeting.docx
  • http://www.bing.com/test.txt
  • images/family <3/WP2016_01.png

Capture
So, if your app communicates with a Linux server for example, or if you want to retrieve the file extension of an URI or an other file system, you will have some issues with them.

In order to solve this issue, I’ve written a helper, compatible with URI as well as other filesystems, implementing Path.GetExtension, Path.GetFileName but also RemoveInvalidChars():


using System;
using System.IO;
using System.Linq;
using Windows.Networking.Sockets;

namespace Huyn
{
    public static class PathHelper
    {

        #region GetExtension

        static readonly char[] _filePathSeparators = { '/', '\\', ':' };
        static readonly char[] _uriSeparators = { '/' };

        /*
       * Equivalent of Path.GetExtension but doesn't throw an exception when the string contains an invalid character (" < > | etc...)
       */
        public static string GetExtension(this string path)
        {
            return GetExtensionInternal(path, _filePathSeparators);
        }

        public static string GetExtension(this Uri uri)
        {
            return GetExtensionInternal(uri.LocalPath, _uriSeparators);
        }

        public static string GetExtension(this string path, char[] separators)
        {
            if (separators != null && separators.Contains('.'))
            {
                throw new ArgumentException("separators can't contain '.'");
            }

            return GetExtensionInternal(path, separators);
        }


        private static string GetExtensionInternal(this string path, char[] separators)
        {
            if (path == null)
                return null;

            var length = path.Length;
            for (var i = length - 1; i >= 0; --i)
            {
                var ch = path[i];
                if (ch == '.')
                {
                    return i != length - 1 ? path.Substring(i, length - i) : string.Empty;
                }
                else if (separators != null && separators.Contains(ch))
                {
                    break;
                }
            }
            return string.Empty;
        }

        #endregion

        #region GetFileName

        public static string GetFileName(this string path)
        {
            return GetFileNameInternal(path, _filePathSeparators);
        }

        public static string GetFileName(this Uri uri)
        {
            return GetFileNameInternal(uri.LocalPath, _uriSeparators);
        }

        public static string GetFileName(this string path, char[] separators)
        {
            if (separators != null && separators.Contains('.'))
            {
                throw new ArgumentException("separators can't contain '.'");
            }

            return GetFileNameInternal(path, separators);
        }


        private static string GetFileNameInternal(string path, char[] separators)
        {
            if (path != null)
            {

                var length = path.Length;
                for (int i = length - 1; i >= 0; --i)
                {
                    var ch = path[i];
                    if (separators.Contains(ch))
                        return path.Substring(i + 1, length - i - 1);
                }
            }
            return path;
        }

        #endregion


        public static string RemoveInvalidChars(string filename, char? replacedLetter = null)
        {
            if (filename == null)
                return null;

            var invalidChars = Path.GetInvalidFileNameChars();

            if (replacedLetter == null)
            {
                return new string(filename
                    .Where(x => !invalidChars.Contains(x))
                    .ToArray());
            }
            else
            {
                return new string(filename
                    .Select(x => invalidChars.Contains(x) ? replacedLetter.Value : x)
                    .ToArray());
            }
        }
    }
}


Don’t hesitate to send me feedback on twitter: @rudyhuyn

More analytic data for your UWP apps

More analytic data for your UWP apps

If you go on the developer.microsoft.com/dashboard, click on your app then on analytics, you’re able to see some stats about your downloads:

capture

 

You can see stats for each OS, but what about if you want to see stats about type of devices: mobile, desktop, xbox, hololens?

Stats by device type

To have this kind of information, you can go on:

https://developer.microsoft.com/dashboard/analytics/reports/overview

then click on change filter

capture

Select your application and click on device type:

capture

You will be able to filter your stats by type of devices, including tablets!

Be careful, there is a little bug right now, Xbox acquisitions are listed in “Other” instead of “Console” for acquisitions.

Once done, for each type of devices, you will have access to some very useful information:

  • Acquisitions graph (30 days)
  • Total lifetime app acquisitions
  • Failure hits (72 hours)
  • Total user sessions (30 days)
  • Ratings (30 days)
  • Top add-ons (30 days)

OS + device type?

There is no easy way to have these stats, but you can do it yourself, go on:

 

https://developer.microsoft.com/dashboard/analytics/reports/export?reportFilter=acq&productId=

and download the .tsv file (csv file with tabs) and open it with Excel, you will be able to create your own charts depends of these criteria (if your application is popular, it can take some minutes to create them)

 

Merry Christmas!

PluralNet: Pluralization for your .Net application

PluralNet: Pluralization for your .Net application

Intro: http://www.rudyhuyn.com/blog/2016/09/28/pluralization-the-missing-part-of-net/

I created a new library named PluralNet, managing plural forms for you, very easy to use and compatible with RESW files as well as RESX files.

Contrary to other ios/android libraries, this one will also manage decimal numbers, the code is a little more complex but very useful.

How to use it?

A Nuget package named PluralNet is available, so install it from Visual Studio or using the following command line:

Install-Package PluralNet

How to manage plural forms in your resource files

It was very important to not break the resw or resx format, so, instead of one entry in your resource file, you Will need to create one entry for each plural form used by the language (ONE, OTHER, FEW?, MANY?, ZERO?, TWO?).

To make it simple and compatible with all localization tools, I used the same terminology than unicode  (also used by Android and iOS):

English (2 plural forms):

capture

Polish (4 plural forms)

capture

In order to use the correct plural rules, the library needs to know the language used by the app and not by the system (for example a polish phone displaying an english application need to use the english rule instead of polish).

To do that, you need to add an extra entry in your RESW file (if not already done by Visual Studio):

capture

The library will use this value to select the correct rules.

Manage pluralization code-behind

If you use RESW:

To make it easy to use, I created a extension to ResourceLoader, so instead of

ResourceLoader.GetForCurrentView().GetString("TimeStampDay");

you must use:

ResourceLoader.GetForCurrentView().GetPlural("TimeStampDay", <NUMBER>);

And optionally use string.Format(…) if your string supports formatting.

If you use RESX:

AppResources.TimeStampDay;

you must use:

AppResources.ResourceManager.GetPlural("TimeStampDay", <NUMBER>);

And optionally use string.Format(…) if your string supports formatting.

XAML

To use pluralization XAML-side, you can use the converter PluralConverter:


<TextBlock Text="{x:Bind NumberDays, Converter={StaticResource PluralConverter}, ConverterParameter=TimeStampDay}" />

If your localized string uses formatting, {0} will be automatically replaced by the number.

You can also create your own Converter if you want a different behavior.

Platforms

The library supports:

  • Silverlight 5
  • Windows Phone Silverlight 8.0
  • Windows 8 and 8.1 WinRT apps
  • Windows Phone 8.1 WinPRT apps
  • UWP
  • ASP.Net Core
  • .Net Desktop apps (winform, wpf, etc…)
  • Xamarin Android and iOS

Language supported

Afrikaans, Akan, Albanian, Amharic, Arabic, Armenian, Assamese, Asturian, Asu, Azerbaijani, Bambara, Basque, Belarusian, Bemba, Bena, Bengali, Bihari, Bodo, Bosnian, Breton, Bulgarian, Burmese, Catalan, Central Atlas Tamazight, Central Kurdish, Chechen, Cherokee, Chiga, Chinese, Colognian, Cornish, Croatian, Czech, Danish, Divehi, Dutch, Dzongkha, English, Esperanto, Estonian, European Portuguese, Ewe, Faroese, Filipino, Finnish, French, Friulian, Fulah, Galician, Ganda, Georgian, German, Greek, Gujarati, Gun, Hausa, Hawaiian, Hebrew, Hindi, Hungarian, Icelandic, Igbo, Inari Sami, Indonesian, Inuktitut, Irish, Italian, Japanese, Javanese, Jju, Kabuverdianu, Kabyle, Kako, Kalaallisut, Kannada, Kashmiri, Kazakh, Khmer, Korean, Koyraboro Senni, Kurdish, Kyrgyz, Lakota, Langi, Lao, Latvian, Lingala, Lithuanian, Lojban, Lower Sorbian, Lule Sami, Luxembourgish, Macedonian, Machame, Makonde, Malagasy, Malay, Malayalam, Maltese, Manx, Marathi, Masai, Metaʼ, Moldavian, Mongolian, Nahuatl, Nama, Nepali, Ngiemboon, Ngomba, North Ndebele, Northern Sami, Northern Sotho, Norwegian, Norwegian Bokmål, Norwegian Nynorsk, Nyanja, Nyankole, N’Ko, Oriya, Oromo, Ossetic, Papiamento, Pashto, Persian, Polish, Portuguese, Prussian, Punjabi, Romanian, Romansh, Rombo, Root, Russian, Rwa, Saho, Sakha, Samburu, Sami languages [Other], Sango, Scottish Gaelic, Sena, Serbian, Serbo-Croatian, Shambala, Shona, Sichuan Yi, Sinhala, Skolt Sami, Slovak, Slovenian, Soga, Somali, South Ndebele, Southern Kurdish, Southern Sami, Southern Sotho, Spanish, Swahili, Swati, Swedish, Swiss German, Syriac, Tachelhit, Tagalog, Tamil, Telugu, Teso, Thai, Tibetan, Tigre, Tigrinya, Tongan, Tsonga, Tswana, Turkish, Turkmen, Tyap, Ukrainian, Upper Sorbian, Urdu, Uyghur, Uzbek, Venda, Vietnamese, Volapük, Vunjo, Walloon, Walser, Welsh, Western Frisian, Wolof, Xhosa, Yiddish, Yoruba, Zulu

Source code

The source code is available under MIT license:

https://github.com/rudyhuyn/PluralNet

How to help?

If you detect an issue, want to add a language, don’t hesitate to submit a push request on github!

 

 

Pluralization: the missing part of .Net

Pluralization: the missing part of .Net

Plural forms are a lot more complex than we think, there is no generic rules to manage plurals, each languages have their specific rules. A lazy way is to ignore them:

3 item(s) in your cart

As a designer, I hate that, it’s laziness.

So a lot of applications write a code similar to this one:

if(numberOfItem == 1)
   return SINGULAR;
else
   return PLURAL;

It’s better but still wrong, except if you only support english, did you know that this rule is incorrect in French for example?

Contrary to English, the number 0 is singular as well as all numbers between 0 and 2 (excluded) so the correct code for french is:

if(number<2)
   return SINGULAR;
else
   return PLURAL;

Let take an other language, Hindi for instance. In Hindi, 0.3 is singular (plural in english, singular in french) whereas 1.8 is plural (plural in english, singular in french)

One more time, rules are different, here is the algorithm for Hindi:

if(number >= 0 && number <= 1)
   return SINGULAR;
else
   return PLURAL;

More than 2 forms

So rules are different between languages, but not all languages are binary, for some languages there is more than 2 plural forms, Czech has a third form for example:


if(number >= 2 && number <= 4)
   return FEW;
else if(number ==1)
   return SINGULAR;
else
   return PLURAL;

But there is more complicated, let’s take a look to Polish:

if ((n % 10).IsBetween(2, 4) && !(n % 100).IsBetween(12, 14))
{
   return FEW;
}
if (n != 1 && (n % 10).IsBetween(0, 1) ||
(n % 10).IsBetween(5, 9) ||
(n % 100).IsBetween(12, 14))
{
   return MANY;
}
if (n == 1)
{
   return ONE;
}
return OTHER;

4 different forms and some very complex rules, a real challenge but wait, there is more complicated.

Decimal numbers

We need also to manage decimal numbers. For a lot of readers, the number 98.41 and 98.45 will have the same plural form, but not for everyone, some languages use the last number of the fractional part to make a choice, 98.41 can be singular for some languages (because ending with 1) contrary to 98.45 (because ending with a 5).

Moreover, some languages make a distinction between 1 (singular) and and 1.0 (plural)…

The rules

Unicode released a very interesting page to understand the differences between all languages, it was one of my main sources to create this library:

http://www.unicode.org/cldr/charts/25/supplemental/language_plural_rules.html

So how it works with .Net?

This is the issue, contrary to other platforms (ios, android. etc..), .Net doesn’t manage pluralization out-of-the-box and no third-party libraries are available.

Solution

I created a new library named PluralNet, managing almost all existing languages (including regional languages), very easy to use and compatible with RESW files as well as RESX files.

More information here: http://www.rudyhuyn.com/blog/2016/09/28/pluralnet

AdaptivePanel: a smarter way to restrict the size of your controls

AdaptivePanel: a smarter way to restrict the size of your controls

One of the big challenge when you write an UWP application is to adapt your UI to 5″ screens (phones) as well as to 84″ screens (Surface Hub).

Let be honest: it’s not the easiest task, especially because the framework misses some controls made for your adaptive UI, for example MinWidth and MaxWidth (+ MinHeight/MaxHeight or course).

In order to simplify this article, we will only focus on the horizontal plan (MinWidth, Width, MaxWidth and HorizontalAlignment), of course, all the content can be also apply to the vertical plan (Height, MinHeight, MaxHeight and VerticalAlignment).

Here is some scenarios almost impossible to solve with the standard controls, we will then try to fix them with a new control:

Problem 1: Use the maximum of space, with alignment to the left/right and with a maxWidth

If you write something like:


<Grid Background="White" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" MaxWidth="600" MaxHeight="400">
</Grid>

Capture

The Grid will be aligned to the center, using the maximum of the space but limited to MaxWidth, so whatever the content of the Grid, the width will be only limited by the size of the parent items (the window for example).

Because we use MaxWidth instead of Width, even if the window is resized, the content of the Grid continue to fit well:

Capture

But what if we want to align the control to the left or right?


<Grid Background="White" HorizontalAlignment="Right"
VerticalAlignment="Stretch" MaxWidth="600" MaxHeight="400">
</Grid>

 

Here is the result:

Capture

It’s not really the behavior we expected… This time the width of the control depends first of the content (and not the MaxWidth). The only way to force the width of the Grid is to set Width, but our UI will break if we resize the window:

Capture2

Problem to solve: Have a way to say Stretch the content not only aligned to the center but also to the left and right.

Problem 2: Use MinWidth and window resizing:

Use MinWidth to force a control to not be too small whatever the content is a very common trick, especially with dynamic contents (GridView, etc…), but a drawback is that you can break your adaptive UI. Let cut a long story short with an example:


<Grid Background="White" HorizontalAlignment="Right"
MinWidth="600">
</Grid>

Whatever the content, the width of our Grid will always be 600px or higher. But what happens if we decrease the size of the window to 500px or if we display this UI on a mobile phone?

CaptureConclusion: every times you use MinWidth, there is a risk to break your adaptive-design.

Problem to solve: improve MinWidth to take into account the size available and not force the width when there’s no enough space available.

Solution: AdaptivePanel, a smart control for adaptive-design

In order to fix these 2 annoying issues, I created a new control named “AdaptivePanel”, it’s a standard Panel but with 2 extra properties: VerticalBehavior and HorizontalBehavior.

Using these two properties, you can override the behavior of MinWidth and MaxWidth to improve the adaptability of your UI.

Here is the 3 possible values :

  • Standard: the control will behave similarly to all other controls
  • StretchToMaxSizeIfPossible: the control will try to use the maximum space available taking into account the size of the parent and the MaxWidth property (or MaxHeight)
  • MinSizeOnlyIfAvailable: the control will use the minimum space available taking into account the size of the parent and the MinWidth property (or MinHeight)

 

So let try to solve our 2 problems:

Problem 1

(Use the maximum of space, with alignment to the left/right and with a maxWidth).

You can easily solve this issue using the behavior StretchToMaxSizeIfPossible:


<huyn:AdaptivePanel Background="White" HorizontalAlignment="Right"
MaxWidth="600" HorizontalBehavior="StretchToMaxSizeIfPossible">
</huyn:AdaptivePanel>

Your control will use the maximum of the size available and take into account the size of the parent.

Problem 2

(Use MinWidth and window resizing)

You can solve this issue using MinSizeOnlyIfAvailable with the following code:


<huyn:AdaptivePanel Background="White" HorizontalAlignment="Right"
MinWidth="600" HorizontalBehavior="MinSizeOnlyIfAvailable">
</huyn:AdaptivePanel>

Whatever the size of the window or the parent item, your control will always be fully visible.

Source code of AdaptivePanel

https://github.com/rudyhuyn/AdaptivePanel-UWP/blob/master/AdaptivePanel/Controls/AdaptivePanel.cs

Sample project

https://github.com/rudyhuyn/AdaptivePanel-UWP

 

Delay an action: Debounce and throttle

Delay an action: Debounce and throttle

It can be sometimes interesting to delay an action, for many reasons, but performance is the principal one. Let take an example: you have a searchbox and you need to make a request each time the content changes, if the user types very quickly, it will be totally useless to make a request at every KeyUp events.

Solution: Debouncing

What is deboucing?

The origin is electronic. Mechanical switches don’t make or break a connection cleanly due to microscopic conditions on the contact surface. This is referred to as “Switch Bounce” and can cause problems in digital circuits.

switch_debounce_04_lrg

In our case, we will do exactly the same thing but with actions: we will wait until the user stops typing during an interval.

Here is a simple class to do it:

using System;
using Windows.UI.Xaml;

namespace Huyn.Utils
{
public class Delayer
{
private DispatcherTimer _timer;
public Delayer(TimeSpan timeSpan)
{
  _timer = new DispatcherTimer() { Interval = timeSpan };
  _timer.Tick += Timer_Tick;
}

public event RoutedEventHandler Action;

private void Timer_Tick(object sender, object e)
{
  _timer.Stop();
  if (Action != null)
    Action(this, new RoutedEventArgs());
}

public void ResetAndTick()
{
  _timer.Stop();
  _timer.Start();
}

public void Stop()
{
  _timer.Stop();
}
}
}

And usage:

_searchWithDelay = new Delayer(TimeSpan.FromSeconds(2));
_searchWithDelay.Action += (sender, e) =>
{
  Search();
};

private void Page_KeyUp(CoreWindow coreWindow, KeyEventArgs e)
{
  searchWithDelay.ResetAndTick();
}

 

What about Throttle?

Throttle versus Debounce

Contrary to Debounce, Throttle will not wait the end of an action, but it will limit the number of call of this action. One example: with a delay of 4 seconds, if an user types a key every second, Throttle will call the action every 2 seconds whereas Debounce will wait that the user stops to type during 4 seconds.

How to implement it?

It’s very easy to implement, we only need to add the method:


public void ResetAndTick()
{
  _timer.Start();
}

and use it:

private void Page_KeyUp(CoreWindow coreWindow, KeyEventArgs e)
{
  searchWithDelay.ResetAndTick();
}

Conclusion

This code is very simple, but don’t hesitate to use it to improve performance of your app!

 

Optimize your UWP app without laziness

Optimize your UWP app without laziness

UWP apps are powerful, but due to adaptive design and the many form factors to support, your pages can be a lot more complex than with Windows phone for instance.

Let’s take an example, I recently release 6tin as a UWP app, it supports 5 different UI to adapt the user experience to many factors: device family, orientation, theme, user interaction (mouse/touch/gamepad) and of course screen size and density.

Here is an example of 2 different states:

00

In blue, controls specific to an UI.

01

Except the central part, all other controls aren’t used other UI, almost 50% of the visible view and 80% of the complexity is not used by some visual states

So imagine if I load ALL these controls on my phone, including controls specific to big screens, the app will takes a lot of time and RAM to load for nothing…

How I can fix it?

Visibility is not a solution

With XAML, we have 2 different states for visibility: Visible and Collapsed, but whatever the value, the item will be loaded, added to the visual tree and will use RAM. So how can I prevent for example the ad for big screens to load on phone?

2 solutions:

  • create these controls on code-behind
  • have a way to prevent the system to load some parts of our UI

The first solution is interesting, but not a good solution in production, your code will be hard to read.

So can I have a way to prevent the loading?

Yes, thanks to a new property brought by Windows 10:

x:DeferLoadStrategy=”Lazy

This property will prevent the system to parse a part of the xaml and load it if the visibility property equals Collapsed, for example:

<MediaElement x:Name=”Player” x:DeferLoadStrategy=”Lazy” Visibility=”Collapsed” />

The system will not load the MediaElement except if you change the visibility property, to summarize, the control will not exist, this.Player will be equal to null, no RAM will be used, etc…

Moreover, if you use DeferLoadStrategy on a Panel, the Panel as well as all its descendants will not be loaded/parsed/etc..

This is what I did on 6tin, the left panel is “Lazy” and Collapsed, so if I display this page on a phone, no CPU/GPU will be used to load/render all these useless controls, my page will use less memory and will be rendered faster.

So, how can I display them if they don’t exist?

doctor-who-spoilers-silence-431x300

I said the control doesn’t exist, but there is 2 ways to activate it.

Imagine we have a Grid named DesktopMenu:

<Grid x:Name=”DesktopMenu” x:DeferLoadStrategy=”Lazy” Visibility=”Collapsed”>

…. a lot of descendants

</Grid>

On code-behind this.DesktopMenu will be equals to null, so impossible to write: this.DesktopMenu.Visibility=Visibility.Visible

Visual States

The easiest solution is to use visual states, even if this.DesktopMenu == null, setters and storyboards will have access to the item.


<VisualState x:Name="WideState">
<VisualState.Setters>
<Setter Target="DesktopMenu.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>

Important fact: once the item visible, the property this.DesktopMenu will not be null anymore!

FindName

An other solution is to use FindName:


var myGrid=(Grid)myPage.FindName("DesktopMenu")

myGrid.Visibility=Visibility.Visible;

In fact, it’s exactly what the visual state do.

Best practices

Code-behind

Because of the Lazy system, an item can be null, so never write something like:


DesktopPanel.Width=200;

If the visibility has never be changed, this.DesktopPanel will be null and your application will crash, the best practice is to write:


(DesktopPanel??FindName("DesktopPanel")).Width=200;

Be careful will this usage, as soon as you use FindName, the object will be loaded as well as all its descendants. So for example, never writes something like:


if(DesktopPanel??FindName("DesktopPanel")).Visibility==Visibility.Collapsed)

If DesktopPanel equals null, FindName will be called and DesktopPanel (and its descendants) will be loaded for nothing. Instead write:


if(DesktopPanel==null || DesktopPanel.Visibility==Visibility.Collapsed)

Optimize all screens

Take advantage of the Lazy system and use it on ALL controls not used by ALL visual states. If a control/panel is collapsed on 1 or many visual states, set  x:DeferLoadStrategy=”Lazy” Visibility=”Collapsed” and change its visibility on visual states.

Never write something like:

<Grid>
<Grid x:Name="SmallMenu">
.... UI specific to small screens
</Grid>
<Grid x:Name="DesktopMenu" x:DeferLoadStrategy="Lazy" Visibility="Collapsed">
.... UI specific to wide screens
</Grid>
</Grid>

 

<VisualState x:Name="WideState">
<VisualState.Setters>
<Setter Target="DesktopMenu.Visibility" Value="Visible" />
<Setter Target="SmallMenu.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="NormalState"/>

This UI is optimized for small screens but not for wide screens, if you start your app on a wide screen, the SmallMenu will be loaded then collapsed, this step is totally useless and take ressources.

The default state must not be related to a specific UI, it must be the greatest common divisor of all your UI, only items used by ALL states must be visible, others must be collapsed and lazy.

If we change our previous example, DesktopMenu and SmallMenu must be collapsed and Lazy (cause not used on all our UI) and we will display them with visual states

<Grid>
<Grid x:Name="SmallMenu" x:DeferLoadStrategy="Lazy" Visibility="Collapsed">
.... UI specific to small screens
</Grid>
<Grid x:Name="DesktopMenu" x:DeferLoadStrategy="Lazy" Visibility="Collapsed">
.... UI specific to wide screens
</Grid>
</Grid>

 

<VisualState x:Name="WideState">
<VisualState.Setters>
<Setter Target="DesktopMenu.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="NormalState">
<VisualState.Setters>
<Setter Target="SmallMenu.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>

Conclusion

Don’t be lazy with lazy

Gérer la synchronisation de ses agents avec l’application

Gérer la synchronisation de ses agents avec l’application

Il existe de nombreux scénarios où un développeur ne souhaite pas lancer un background agent (périodique par exemple) lorsque son application est lancée, ceci pour différentes raisons:

  • l’application en foreground réalise un traitement équivalent à celui dans le background agent, voire + complet
  • résoudre des problématiques d’accès de fichiers
  • obiwan kenobi

C’est une question que je vois régulièrement sur les forums, malheureusement, aucune solution n’est indiquée si ce n’est quelques idées et propositions, mais rien de concret.

Donc corrigeons cela pour la nouvelle année !

La solution idéale ?

C’est un peu un rêve… étant donné que winrt gère les conditions sur les background agents:


builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));

Il ferait sens d’avoir un équivalent pour qu’un agent ne se lance que si l’application n’est pas déjà lancée, par exemple:


builder.AddCondition(new SystemCondition(SystemConditionType.ApplicationNotRunning));

Malheureusement ce genre de condition n’est toujours pas disponible ce qui est plutôt regrettable… Nous allons donc devoir bricoler une solution maison car soyons honnête, la solution se rapproche vraiment du bricolage, mais faute de mieux…

Le secret: les mutex nommés

Pour résoudre notre problématique, nous allons utiliser ce qu’on appelle les Mutex nommés, ces mutex (ou Semaphore(1) pour ceux qui sont plus familier avec cette notion) ont la particularité de porter un nom/identifiant et d’être multi-process.

Attention toutefois, les applications “Metro” étant sandboxées, ce mutex se limite au scope de l’application, aucun risque donc d’avoir des soucis de synchronisation si 2 applications utilisent le même nom/id.

mutex

Implémentation

Nous allons donc commencer par créer deux variables, un mutex et un booléen:


private static Mutex _agentMutex=new Mutex(false,"SyncAgentsMutex");
private static bool _applicationRunning;

Ce booléen est un peu redondant avec le Mutex, mais cela est fait exprès, il va permettre de stocker l’état du Mutex, la classe .Net ne nous permettant pas d’avoir accès à cette information directement.

Nous allons maintenant gérer la prise du Mutex afin d’indiquer que l’application est en cours d’exécution


private static void ApplicationIsLaunched()
{
if (_applicationRunning)
return;
lock (_agentMutex)
{
if (_applicationRunning)
return;
_agentMutex.WaitOne();
_applicationRunning = true;
}
}

l’implémentation est classique, un lock pour être sûr de ne pas appeler deux fois WaitOnce (ce qui pourrait bloquer l’application au second appel) et une double vérification du booléen à l’extérieur et à l’intérieur du lock, schéma plutôt classique.

Occupons nous maintenant de la fonction de libération du Mutex. Contrairement aux cas précédent, j’ai volontairement choisi de ne pas faire une vérification avant le lock, je veux être sûr et certain qu’un ApplicationIsLaunched n’est pas en cours, ce qui pourrait nous causer quelques problèmes de synchronisation.


private static void ApplicationIsSuspended()
{
lock (_agentMutex)
{
if (!_applicationRunning)
return;
_agentMutex.ReleaseMutex();
_applicationRunning = false;
}
}

Enfin, nous allons créer une fonction pour vérifier l’état de l’application


public static bool IsApplicationLaunched()
{
lock (_agentMutex)
{
if (_agentMutex.WaitOne(1))
{
_agentMutex.ReleaseMutex();
return false;
}
return true;
}
}

WaitOne(1) va nous permettre de tester l’état du Mutex, pour résumer, on va essayer d’acquérir le Mutex avec un timeout d’1 milliseconde (soit presque rien), si on réussi à le récupérer, cela signifie que l’application n’est pas en cours d’exécution, on demande alors à relâcher le mutex aussitôt.

Précision important, je n’ai pas utiliser le booléen _applicationRunning. En effet, mon application et mes agents sont dans des processus différent, autant le Mutex est synchronisé (car nommé), autant le booléen est propre à chaque processus. Comme évoqué précédemment, le booléen est là pour sécuriser le Mutex côté application, et uniquement de ce côté.

Dernière étape, deux fonctions pour enregistrer l’application et gérer automatiquement l’état de celle-ci

 

        public static void Init(Application app)
        {
            app.Resuming += App_Resuming;
            app.Suspending += App_Suspending;
            ApplicationIsLaunched();
        }

        private static void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
        {
            ApplicationIsSuspended();
        }

        private static void App_Resuming(object sender, object e)
        {
            ApplicationIsLaunched();
        }

Comment l’utiliser ?

Côté application

Dans le constructeur de votre application (App.xaml.cs), appelez


AgentSync.Init(this);

le plus tôt possible (première ligne si possible) et c’est tout.

Côté agent

Dès la première ligne de votre agent, dans la méthode Run(…), appelez la méthode suivante :


if(AgentSync.IsApplicationLaunched())
return;

Cela évitera tout traitement si votre application est en cours d’utilisation.

Code complet

using System.Threading;
using Windows.UI.Xaml;

namespace Sixtin.Services.Utils
{
    public class AgentSync
    {
        private static Mutex _agentMutex=new Mutex(false,"SyncAgentsMutex");
        private static bool _applicationRunning;

        public static void Init(Application app)
        {
            app.Resuming += App_Resuming;
            app.Suspending += App_Suspending;
            ApplicationIsLaunched();
        }

        private static void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
        {
            ApplicationIsSuspended();
        }

        private static void App_Resuming(object sender, object e)
        {
            ApplicationIsLaunched();
        }

            private static void ApplicationIsLaunched()
            {
                if (_applicationRunning)
                    return;
                lock (_agentMutex)
                {
                    if (_applicationRunning)
                        return;
                    _agentMutex.WaitOne();
                    _applicationRunning = true;
                }
            }

        private static void ApplicationIsSuspended()
        {
            lock (_agentMutex)
            {
                if (!_applicationRunning)
                    return;
                _agentMutex.ReleaseMutex();
                _applicationRunning = false;
            }
        }

        public static bool IsApplicationLaunched()
        {
            lock (_agentMutex)
            {
                if (_agentMutex.WaitOne(1))
                {
                    _agentMutex.ReleaseMutex();
                    return false;
                }
                return true;
            }
        }
    }
}

Display an AppBarButton on the left inside a CommandBar

Display an AppBarButton on the left inside a CommandBar

With Windows 8.1, it was easy to add a button to the left side or the right side of a CommandBar using PrimaryCommands and SecondaryCommands.

But Windows 10 uses a different template for CommandBar, similar to the one used by Windows Phone 8.1:

  • primary commands as a button (aligned to the right)
  • secondary commands as a menuitem

Sans titre

So, here is the question: How can I align a button to the left?

Have a button to the left can be very interesting for many scenarios, one of them is the hamburger menu, an other one is to highlight a specific feature, like I did on 6tag.

Sans titre

So, how can I do that?

In order to do that, you need to use the property Content of the CommandBar, this property will allow you to add one FrameworkElement to the left, like for example… an AppBarItem!

Note: contrary to a lot of other controls, the default property for children nodes is not Content but PrimaryCommand, this is IMO the reason why nobody finds it.

<Page.BottomAppBar>
<CommandBar>
<CommandBar.Content>
<AppBarButton Icon="Home"/>
</CommandBar.Content>
<AppBarButton Icon="Delete"/>
<AppBarButton Icon="Calendar"/>
</CommandBar>
</Page.BottomAppBar>

Sans titre

But if I need many buttons?

As I said, Content property needs a FrameworkElement, so you can easily use a StackPanel and include many AppBarItem on it:


<Page.BottomAppBar>
<CommandBar>
<CommandBar.Content>
<StackPanel>
<AppBarButton Icon="Home"/>
<AppBarButton Icon="Like"/>
</StackPanel>
</CommandBar.Content>
<AppBarButton Icon="Delete"/>
<AppBarButton Icon="Calendar"/>
</CommandBar>
</Page.BottomAppBar>

What about the label of the AppBarItem?

Sans titre

A bug is remaining, if you use a label, it will be visible even if the CommandBar is not opened, to fix that, we will bind the property AppBarItem.IsCompact to the CommandBar.IsOpen property. Of course, we need to invert the value of the boolean.


<AppBarItem IsCompact="{Binding IsOpen,ElementName=MyCommandBar,Converter={StaticResource InvertBooleanConverter}}"/>

What about globalization and right-to-left languages?

I’ve tested the same code with arabian language for example, and no problems, the Content is aligned to the right whereas PrimaryCommands are on the left

Sans titre

 

Conclusion

This property was not easy to find, due to a poor MSDN documentation

Capture

but whatever, don’t hesitate to use it in your apps, and for example think about it when you want to use an hamburger menu on your phone app, buttons on the top are not always easy to use with some phones (1520, 625, 950 XL)

Detect if your 8.1 app runs on Windows 10

Detect if your 8.1 app runs on Windows 10

Here is a little helper I use on my apps to detect if the OS is Windows 10 or higher.

For this purpose, I use the property “Status” of the class Package, why? Cause it’s a new property only available on Windows 10. So using reflection, I can test if this property is available or not and therefore, detect if the app runs on Windows 10.

Voici une petite astuce pour détecter si votre application 8.1 tourne sur Windows 10 ou sur une version plus ancienne de l’OS.

Pour cela, j’utilise la propriété “Status” de la classe “Package”, pourquoi ? Car elle a l’avantage d’être une propriété uniquement disponible sous Windows 10, en testant donc sa présence, on peut affirmer sans soucis que l’OS est Windows 10 ou pas.

using System.Reflection;
using Windows.ApplicationModel;

namespace Huyn.Utils
{
 public class Windows10Helper
 {
  private static bool? _isWindows10;
  public static bool IsWindows10()
  {
    if (!_isWindows10.HasValue)
    {
       _isWindows10 = Package.Current.GetType()
                         .GetRuntimeProperty("Status") != null;
    }
    return _isWindows10.Value;
  }
 }
}