Recommended way to test for iOS Version specific feature at runtime - c#

I'm targetting IOS 4.3 and 5.0 with an app built against the 5.0 SDK and would like to add support for the Twitter functionality introduced in iOS5 only when the app runs on a iOS5 device. What is the recommended way to reliably test for the availability of these OS features at runtime without having your app crash?
I know you do this using respondsToSelector in Objective-C but how is it done in C#?

With recent MonoTouch versions you can use the following code:
if (UIDevice.CurrentDevice.CheckSystemVersion (5, 0)) {
window.RootViewController = navigation;
} else {
window.AddSubview (navigation.View);
}
Otherwise you can get a string from UIDevice.CurrentDevice.SystemVersion and do some checks with your own code.

Follow up to comments, including mine...
If you want to check by feature you can do something like:
MonoTouch.Twitter.TWRequest req = new MonoTouch.Twitter.TWRequest ();
if (req.Handle == IntPtr.Zero) {
Console.WriteLine ("No Twitter support before iOS5");
}
What happens is that the selector to create the TWRequest instance will return null and the .NET object will be created in an invalid (unusable) state that you can query with the Handle property. Again YMMV, testing is key :-)

Related

Toast Notifications in Win Forms .NET 4.5

I've searched through a number of different posts to do with creating toast notifications from a Win Form however when these through I get an error when generating the toast notification.
System.Exception: Element not found. (Exception from
HRESULT:0x80070490).
I have edited the csproj file and added the following:
<PropertyGroup>
<TargetPlatformVersion>10.0.10586</TargetPlatformVersion>
</PropertyGroup>
and added the references to Windows.Data and Windows.UI and also a reference to System.Runtime.dll as per the suggestions in Windows.UI.Notifications is missing
using Windows.Data.Xml.Dom;
using Windows.UI.Notifications;
using System.Windows.Forms;
using System;
namespace ToastNotify
{
class Notify
{
public void GenerateToast(string header, string content)
{
ToastTemplateType toastTemplate = ToastTemplateType.ToastImageAndText02;
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);
XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
toastTextElements[0].AppendChild(toastXml.CreateTextNode(header));
toastTextElements[1].AppendChild(toastXml.CreateTextNode(content));
XmlNodeList toastImageElements = toastXml.GetElementsByTagName("image");
((XmlElement)toastImageElements[0]).SetAttribute("src", "..\\..\\Resources\\icon.ico");
IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
((XmlElement)toastNode).SetAttribute("duration", "long");
ToastNotification toast = new ToastNotification(toastXml);
try
{
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}
Any suggestions as to where I am going wrong?
You should explicitly provide applicationId for CreateToastNotifier.
Like this:
private const String APP_ID = "Microsoft.Samples.DesktopToastsSample";
...
ToastNotificationManager.CreateToastNotifier(APP_ID).Show(toast);
But I have bad news. Starting from Windows 10 1709 WinForms applications just does not show toast notifications. Before that Show(toast) was working, but now it neither throw an exception, nor show any toast notification.
I'm still figuring this out.
As noted by Prateek Shrivastava there are (new) limitations.
Have a look here https://learn.microsoft.com/en-us/uwp/api/windows.ui.notifications.toastnotificationmanager.createtoastnotifier
Update:
Here is the step by step guide, to create a setup with APP_ID so notifications will work on all Windows 10 versions:
Send a local toast notification from desktop C# apps
Update:
It works again in Windows 10 1903 without setup.
Use This and make sure you are setting the Image (Icon) full path if you want to show an icon otherwise just pass null.
public static void GenerateToast(string appid, string imageFullPath, string h1, string h2, string p1)
{
var template = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText04);
var textNodes = template.GetElementsByTagName("text");
textNodes[0].AppendChild(template.CreateTextNode(h1));
textNodes[1].AppendChild(template.CreateTextNode(h2));
textNodes[2].AppendChild(template.CreateTextNode(p1));
if (File.Exists(imageFullPath))
{
XmlNodeList toastImageElements = template.GetElementsByTagName("image");
((XmlElement)toastImageElements[0]).SetAttribute("src", imageFullPath);
}
IXmlNode toastNode = template.SelectSingleNode("/toast");
((XmlElement)toastNode).SetAttribute("duration", "long");
var notifier = ToastNotificationManager.CreateToastNotifier(appid);
var notification = new ToastNotification(template);
notifier.Show(notification);
}
I need to chime in here, because I'm working on a WinForms app (.Net Framework 4.7*) that's failing to build on a GitHub build worker because at some point, support for toast notifications was hacked in with some weird references to these WinMetadata\Windows.*.winmd files.
Woe be to anyone who has found this StackOverflow question.
You're probably targeting the .Net Framework 4.x because you're working on a Win Forms app. .Net Framework 4.8 is supported on Windows 7, and Windows 7 does not have ToastNotifications which is probably the class you're looking for. Hence the problem.
I think the only solution is the one in the updated answer that's most upvoted, which is just target the more modern UWP framework that will only work on Windows 10 and up. But you're still trying to send toast notifications from a framework that's not designed to support it. At some point in your code, you're probably doing a check if the platform you're running on is Windows 10 or newer. Not even a compiler statement, just straight up C# or VB.Net. That's not how the .Net Framework is designed to work - it's meant to be platform-agnostic.
My only advice is; setup a new build target for Windows 10+ and setup your project following the advice from Stephen. That should be the least-hackish way of making WinForms do neat Windows 10 tricks.

Referencing WinRT/UWP libraries in a .NET desktop application while maintaining support for Windows 7

I am trying to reference "Windows.Networking.Connectivity" classes in my desktop application. I am basically interested in handling metered connections in my app.
Basically what I am trying to do is simple:
var connectionCost = NetworkInformation.GetInternetConnectionProfile().GetConnectionCost();
if (connectionCost.NetworkCostType == NetworkCostType.Unknown
|| connectionCost.NetworkCostType == NetworkCostType.Unrestricted)
{
//Connection cost is unknown/unrestricted
}
else
{
//Metered Network
}
The only method I know of that allows a desktop application to reference UWP assemblies is by manually editing the project file and adding the following line to the csproj file:
<TargetPlatformVersion>8.0</TargetPlatformVersion>
Applying the code and "hack" works fine but the problem is that doing so will prevent my app from running on Windows 7 which I need to support.
I was wondering if there is a way to reference UWP assemblies in a desktop application without having to drop support for Windows 7.
And since for the time being I only want to check if a connection is metered, I am open to suggestions about how to get this information without referencing Windows assemblies.
I found a way to use reflection and call UWP methods without having to specify a target platform. For my case this is what I did:
var networkInfoType = Type.GetType("Windows.Networking.Connectivity.NetworkInformation, Windows, ContentType=WindowsRuntime");
var profileType = Type.GetType("Windows.Networking.Connectivity.NetworkInformation, Windows, ContentType=WindowsRuntime");
var profileObj = networkInfoType.GetTypeInfo().GetDeclaredMethod("GetInternetConnectionProfile").Invoke(null, null);
dynamic profDyn = profileObj;
var costObj = profDyn.GetConnectionCost();
dynamic dynCost = costObj;
var costType = (NetworkCostType)dynCost.NetworkCostType;
if (costType == NetworkCostType.Unknown
|| costType == NetworkCostType.Unrestricted)
{
//Connection cost is unknown/unrestricted
}
else
{
//Metered Network
}

Remote Desktop ActiveX control

I've got a C# application that I've been using for years to script remote desktop connections. It's always been built on the AxMsRdpClient3 (note the 3, which I guess is some kind of version number). There are features in AxMsRdpClient8 (version 8) that I want to be able to use, but as I understand it, this requires Remote Desktop version 8 to be installed. Not all users have that installed (or even can install it on Windows XP/Vista), though.
So as Sheng Jiang suggested, I am creating the control at runtime now and I have code that looks like this:
try
{
AxMsRdpClient8 rdp8 = new AxMsRdpClient8();
rdp8.BeginInit();
// set some properties here
rdp8.EndInit(); // throws Exception on machines without version 8 installed
}
catch (Exception ex)
{
AxMsRdpClient3 rdp3 = new AxMsRdpClient3();
rdp3.BeginInit();
// set some properties here
rdp3.EndInit();
}
As expected, rdp8.EndInit() throws an exception on machines that do not have Remote Desktop version 8 installed. The problem is that after we try to create the AxMSRDPClient8, the rdp3.EndInit() fails as well (class not registered) on older machines. If I don't attempt to create the AxMSRDPClient8 first, the AxMSRDPClient3 initializes and works correctly.
each version of RDP activeX has a different clsid. You need to detect the OS version and create an activex at runtime with the class id corresponding to the lowest OS version you plan to support.
If your code depends on late binding, better rewrite your code to work with IMsRdpClient* and IMsRdpClientNonScriptable*. For example MsRdpClient8NotSafeForScripting supports the following interfaces:
up to version 8 of IMsRdpClient*
up to version 5 of IMsRdpClientNonScriptable
IMsTscNonScriptable
IMsRdpPreferredRedirectionInfo
IMsRdpExtendedSettings
MsRdpClient3NotSafeForScripting supports
version 2 of IMsRdpClient*
IMsRdpClientNonScriptable
IMsTscNonScriptable.
if you want to detect whether your activex support a particular interface version, just cast the ActiveX's instance
to the interface. when a cast fails you know the interface is not supported.
protected void CreateRdpActiveX()
{
try
{
string clsid=GetRdpActiveXClsIdByOSVersion();
Type type = Type.GetTypeFromCLSID(clsid, true);
this.axRdp = new AxHost (type.GUID.ToString());
((ISupportInitialize)(axRdp)).BeginInit();
SuspendLayout();
this.panel1.Controls.Add(axRdp);
((ISupportInitialize)(axRdp)).EndInit();
ResumeLayout(false);
var msRdpClient8 = axRdp.GetOcx() as IMsRdpClient8;
if(msRdpClient8!=null)
{
var advancedSettings9 =msRdpClient8.AdvancedSettings9 as IMsRdpClientAdvancedSettings8;
if(advancedSettings9!=null)
advancedSettings9.BandwidthDetection=true;
}
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.Message);
}
}

Why does the WP8 Tiles example use reflection?

Use of the new WP8 Tiles uses reflection instead of instantiating and calling the methods directly as shown below.
http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj720574(v=vs.105).aspx
Type shellTileType = Type.GetType("Microsoft.Phone.Shell.ShellTile, Microsoft.Phone");
shellTileType.GetMethod("Update").Invoke(tileToUpdate, new Object[] { UpdateTileData });
I'm wondering if there is a specific reason reflection is being used this situation. Is it cause the WP < 7.8 won't have a reference to the ShellTile Type and thus the VM will error? If the VM never accesses this part of the code wouldn't that be good enough?
You can target your app for one of WP7 or WP8 platfroms.
In WP7 SDK there is no FlipTileData class, so you won't be able to compele code wich uses this class.
But you can run your WP7 app on WP8 device, so you can create Wilde Tiles using reflection.
You just need to check the OS version before:
private static Version TargetedVersion = new Version(8, 0);
public static bool IsTargetedVersion
{
get
{
return Environment.OSVersion.Version >= TargetedVersion;
}
}
Otherwise, if you target your app for WP8 platform only - feel free to use FlipTileData and other classes without reflection. Here you can find the example.
In WP7 SDK there is no API for these new Tile types, they are only available in WP7.8 and WP8. So if you want to use new tile sizes on WP7.8 devices or in WP7 application running on WP8 device, you have to use reflection.
Of course in WP8 app you can use the API directly with no problems.

Future-proofing the .NET version detection

Not to beat the dead horse, however, I am looking for a way to detect the installed .NET frameworks. It seems like the provided solutions (in the links) are all good up until the point a new version of the framework is released and then all bets are off. The reason for this is that the detection relies on the registry keys and it seems that v4 of the framework has broken the convention and one now has to take extra steps to detect v4.
Is there a way to detect the .NET framework that will also work when .NET v5 appears.
EDIT: Ok, for future generations of frustrated .NET Version seekers, here is the code to make it happen:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;
using Microsoft.Win32;
private List<string> GetInstalledDotNetFrameworks()
{
string key = string.Empty;
string version = string.Empty;
List<string> frameworks = new List<string>();
var matches = Registry.LocalMachine
.OpenSubKey(#"SOFTWARE\Microsoft\NET Framework Setup\NDP")
.GetSubKeyNames().Where(keyname => Regex.IsMatch(keyname, #"^v\d"));
// special handling for v4.0 (deprecated) and v4 (has subkeys with info)
foreach (var item in matches)
{
switch (item)
{
case "v4.0": // deprecated - ignore
break;
case "v4":// get more info from subkeys
key = #"SOFTWARE\Microsoft\NET Framework Setup\NDP\" + item;
string[] subkeys = Registry.LocalMachine
.OpenSubKey(key)
.GetSubKeyNames();
foreach (var subkey in subkeys)
{
key = #"SOFTWARE\Microsoft\NET Framework Setup\NDP\" + item + #"\" + subkey;
version = Registry.LocalMachine
.OpenSubKey(key)
.GetValue("Version").ToString();
version = string.Format("{0} ({1})", version, subkey);
frameworks.Add(version);
}
break;
case "v1.1.4322": // special case, as the framework does not follow convention
frameworks.Add(item);
break;
default:
try
{
// get the Version value
key = #"SOFTWARE\Microsoft\NET Framework Setup\NDP\" + item;
version = Registry.LocalMachine
.OpenSubKey(key)
.GetValue("Version").ToString();
frameworks.Add(version);
}
catch
{
// most likely new .NET Framework got introduced and broke the convention
}
break;
}
}
// sort the list, just in case the registry was not sorted
frameworks.Sort();
return frameworks;
}
In short, you can use this approximately (see below for more complete solution):
Microsoft.Win32.Registry.LocalMachine
.OpenSubKey(#"SOFTWARE\Microsoft\NET Framework Setup\NDP")
.GetSubKeyNames().Where(keyname=>Regex.IsMatch(keyname,#"^v\d"))
On my machine, this returns: v2.0.50727, v3.0, v3.5, v4, v4.0. Subkeys could be used to detect service packs (which are probably relevant). Also, using the key SOFTWARE\Microsoft\.NETFramework returns v2.0.50727, v3.0 and v4.0.30319 - ehhh, lovely, slightly different!
There's no guarantee this pattern will hold, but it's a pretty reasonable bet :-). http://support.microsoft.com/kb/318785 has some more info on the details of the registry describing the versioning, and in particular, you may need to check for Install - but that's tricky as v4.0 demonstrates.
Edit: I've extended this to detect arbitrary sub-key's of the registry that include installation info so as to detect v4 Client and Full profiles correctly. Also, the RegistryKey type is IDisposable, and it looks like the Dispose method is indeed doing something (registry key unlocking).
var versionList = new List<string>();
using(var ndpKey=Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\NET Framework Setup\NDP")) {
Action<RegistryKey, Action<RegistryKey,string>> processKids = (node, action) => {
foreach(var childname in node.GetSubKeyNames())
using(var child = node.OpenSubKey(childname))
action(child,childname);
};
Action<RegistryKey, Func<RegistryKey, bool>> visitDescendants = null;
visitDescendants = (regkey, isDone) => {
if(!isDone(regkey))
processKids(regkey, (subkey, subkeyname)=>visitDescendants(subkey,isDone));
};
processKids(ndpKey, (versionKey, versionKeyName) => {
if(Regex.IsMatch(versionKeyName,#"^v\d")) {
visitDescendants(versionKey, key => {
bool isInstallationNode = Equals(key.GetValue("Install"), 1) && key.GetValue("Version") != null;
if(isInstallationNode)
versionList.Add(
key.Name.Substring(ndpKey.Name.Length+1)
+ (key.GetValue("SP")!=null ? ", service pack "+ key.GetValue("SP"):"")
+ " ("+key.GetValue("Version") +") "
);
return isInstallationNode;
});
}
});
}
versionList then contains:
v2.0.50727, service pack 2 (2.0.50727.4927)
v3.0, service pack 2 (3.0.30729.4926)
v3.5, service pack 1 (3.5.30729.4926)
v4\Client (4.0.30319)
v4\Full (4.0.30319)
Do you expect we can tell you the future? :) Why do you need that in the first place? I mean, if you write an app for v4, what difference does it make if v5 is installed or not? You can specify in the app.config what versions you support, but you can't know in advance what the next version will be, or even if your app will run on that. Whenever a new framework comes out, you'll have to test your app, and decide if you want to migrate or not. If you migrate, then you make changes to the app.config and possibly the code, too, and release a new version. If you don't, then you'll still require that an older framework version be installed. It's not like v5 comes out and people will start uninstalling all the previous frameworks. I still have v1.1 and v2 on my machine and I guess they'll stick around for a while.
I am in agreement with fejesjoco. Why do you even want to detect a future version that your code has not been compiled against?
If you look in the Framework folder (C:\Windows\Microsoft.NET\Framework) you will see that all previous versions of the Framework are install along with the most recent version. If your code is compiled against 4.0 and 5.0 comes out it will still have a folder for 4.0.
If you could give us a bit more context as to why you want to detect future versions we may be able to help you better.
Include an auto-update feature in the detection tool?

Categories

Resources