Search code examples
c#winformsvisual-studio-2013windows-8windows-7

Compile a Visual Studio C# project for two TargetPlatformVersion


I developed a .NET windows application which worked both on Windows 7 and 8.1. Then I added the Toast notification feature that came with Windows 8 (from this question: How can I use the Windows.UI namespace from a regular (Non-Store) Win32 .NET application?). This also worked, I just had to add:

<PropertyGroup> <TargetPlatformVersion>8.0</TargetPlatformVersion> </PropertyGroup>

to the project file.

As I referenced the Windows.winmd file from the Windows 8.1 SDK C:\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Windows.winmd, the executable does not start on Windows 7 anymore! I double-click and that's it. No errors, no messages.

As I did not find any solution online, that's where my question comes up: How do I manage to do both: Offer the toast feature to my users AND make the same .exe run on Windows 7?

Thank you in advance!

EDIT It turns out that though TargetPlatformVersion is set to 8.0, the executable starts on Windows 7 anyway, but crashes as soon as the program tries to load the Windows 8 libraries:

An unhandled exception of type 'System.TypeLoadException' occurred in ToastTester.exe. Additional information: Could not find Windows Runtime type 'Windows.UI.Notifications.ToastNotificationManager'.

on line Application.Run(new Form1());

In Form1.cs in line 9 I've got using Windows.UI.Notifications;

What is the best way to avoid this exception during runtime, even though it is expected that this executable will run in environments like Windows 7 where the Windows.UI.Notifications namespace is definitely not available?


Solution

  • I designed my own workaround for being able to support Windows 8 toasts and at the same time prevent application crashes due to missing libraries when running on Windows 7. Note: I am using the Singleton design pattern (member INSTANCE), but you can always do it otherwise.

    ShellLink.cs is taken from here

    Win8Toaster.cs:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using Windows.Data.Xml.Dom;
    using Windows.UI.Notifications;
    
    namespace ToastManager
    {
        class Win8Toaster
        {
            public const string APPUSERMODELID = "YourCompany.YourApplicationName";
            public static string ShortcutLocation;
            public static ToastNotifier ToastNotifier;
    
            private static Win8Toaster _INSTANCE = null;
            public static Win8Toaster INSTANCE
            {
                get
                {
                    if (_INSTANCE == null)
                    {
                        _INSTANCE = new Win8Toaster();
                    }
                    return _INSTANCE;
                }
            }
    
            public Win8Toaster()
            {
                ShortcutLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\Microsoft\Windows\Start Menu\Programs\YourCompany\YourApplication.lnk");
                //We need a start menu shortcut (a ShellLink object) to show toasts.
                if (!File.Exists(ShortcutLocation))
                {
                    string directory = Path.GetDirectoryName(ShortcutLocation);
                    if (!Directory.Exists(directory))
                    {
                        Directory.CreateDirectory(directory);
                    }
                    using (ShellLink shortcut = new ShellLink())
                    {
                        shortcut.TargetPath = System.Reflection.Assembly.GetEntryAssembly().Location;
                        shortcut.Arguments = "";
                        shortcut.AppUserModelID = APPUSERMODELID;
                        shortcut.Save(ShortcutLocation);
                    }
                }
                ToastNotifier = ToastNotificationManager.CreateToastNotifier(APPUSERMODELID);
            }
    
            public void ShowToast(ToastContent Content)
            {
                XmlDocument ToastContent = new XmlDocument();
                ToastContent.LoadXml("<toast><visual><binding template=\"ToastImageAndText02\"><image id=\"1\" src=\"file:///" + Content.ImagePath + "\"/><text id=\"1\">" + Content.Text1 + "</text><text id=\"2\">" + Content.Text2 + "</text></binding></visual></toast>");
                ToastNotification thisToast = new ToastNotification(ToastContent);
                ToastNotifier.Show(thisToast);
            }                
        }
    }
    

    Toaster.cs

    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
    
    namespace ToastManager
    {
        public static class Toaster
        {
            private static Win8Toaster ActiveToaster;
            public static bool Win8ToasterAvailable = true;
            public static void ShowToast(ToastContent Content)
            {
                if (Win8ToasterAvailable)
                {
                    if (ActiveToaster == null)
                    {
                        if (Environment.OSVersion.Version.Major > 6 || Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2)
                        {
                            try
                            {
                                ActiveToaster = Win8Toaster.INSTANCE;
                            }
                            catch (Exception ex)
                            {
                                Win8ToasterAvailable = false;
                            }
                        }
                        else
                        {
                            Win8ToasterAvailable = false;
                        }
                    }
                    ActiveToaster.ShowToast(Content);
                }
                else
                {
                    //Use alternative notifications because Windows 8 Toasts are not available
                }
            }
        }
        //I also wrote my own toast content structure:
        public class ToastContent
        {
    
            public string ImagePath, Text1, Text2;
            public ToastContent(string ImagePath, string Text1, string Text2)
            {
                this.ImagePath = ImagePath;
                this.Text1 = Text1;
                this.Text2 = Text2;
            }
        }
    }
    

    Now that you've got the necessary classes, here is how to use it (pretty simple, huh?):

    ToastManager.Toaster.ShowToast(new ToastManager.ToastContent(@"..\path\toyour\image.png", "Your Application Name", "Time: " + DateTime.Now.ToLongTimeString()));
    

    This example shows a toast notification with the current system time or nothing if you are on Windows 7.

    A design suggestion:

    I used WinForms to design a notification window which looks similar to that in Windows 8 and simulates the same functions, just with my own forms. Alternatively you can also implement a tray icon and show some notification bubbles.