Wednesday, June 11, 2014

BluetoothDiscovery on C# via the registry

BluetoothDiscovery

Discover Bluetooth Devices from c# using registry
Fast & easy code to discover blue tooth devices & their COM Ports on Windows 7 & Windows 8.
The idea is to extract out the device id from the registry key path, then match it appropriately to join the name with the port.

For Windows 7: The device id maps to the AssocBdAddr value under the LocalServicesnode.

In the example below:
646E6CC1CAB3 is the device id value that I map to "AssocBdAddr"=hex:b3,ca,c1,6c,6e,64,00,00 under LocalServices.

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\BTHENUM\{00001101-0000-1000-8000-00805f9b34fb}_LOCALMFG&000f\7&5461823&0&646E6CC1CAB3_C00000001\Device Parameters]
"PortName"="COM4"

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\BTHPORT\Parameters\LocalServices\{00001101-0000-1000-8000-00805f9b34fb}\0]
"ServiceName"="MyBluetoothDeviceOnWindows7"
"AssocBdAddr"=hex:b3,ca,c1,6c,6e,64,00,00

For Windows 8: The device id maps to to a sub key Dev_XXXX where XXXX is your device id.

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\BTHENUM\{00001101-0000-1000-8000-00805f9b34fb}_LOCALMFG&000f\7&22abdcbb&0&646E6CC16891_C00000000\Device Parameters]
"PortName"="COM14"

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\BTHENUM\Dev_646E6CC16891\7&13e7003a&0&BluetoothDevice_646E6CC16891]
"FriendlyName"="MyBluetoothDeviceOnWindows8"
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;
 
namespace BlueToothDevices
{
    public class BluetoothDevice
    {
        public string Name { getset; }
        public string Port { getset; }
        public string DeviceId { getset; }
 
        public override string ToString()
        {
            return string.Format("{0} [ {1} ] {2}", Name, Port, DeviceId);
        }
    }
 
    public class Bluetooth
    {
        private const string NullDeviceId = "000000000000";
        private const string BluetoothRegKey = @"SYSTEM\CurrentControlSet\Enum\BTHENUM";
        private const string BluetoothRegKey7 = @"SYSTEM\CurrentControlSet\services\BTHPORT\Parameters\LocalServices";
        private readonly Dictionary<stringBluetoothDevice> _devices = new Dictionary<stringBluetoothDevice>();
        public BluetoothDevice[] Devices { get { return _devices.Values.ToArray(); } }
 
        public static bool VerboseMode { getset; }
        private static void Verbose(string message, params object[] args)
        {
            if (VerboseMode)
                Console.WriteLine("{0}"string.Format(message, args));
        }
 
        public static List<string> DeviceNames
        {
            get
            {
                var devices = new List<string>();
                foreach (var device in GetBluetoothDevices())
                {
                    if (string.IsNullOrEmpty(device.Port))
                        continue;
 
                    devices.Add(string.Format("{0} {1}", device.Name, device.Port));
                }
                return devices;
            }
        }
 
        public static BluetoothDevice[] GetBluetoothDevices()
        {
            var bluetooth = new Bluetooth();
            bluetooth.GetDeviceNames();
            bluetooth.SetDevicePorts();
            return bluetooth.Devices;
        }
 
        private void GetDeviceNamesForWindows7()
        {
            using (var key = Registry.LocalMachine.OpenSubKey(BluetoothRegKey7, false))
            {
                if (key == null)
                {
                    Console.WriteLine("GetLocalServices: missing HKLM {0}", BluetoothRegKey7);
                    return;
                }
 
                foreach (var name in key.GetSubKeyNames())
                {
                    using (var k1 = OpenSubKey(key, name))
                    {
                        if (k1 == null)
                            continue;
 
                        foreach (var pnpName in k1.GetSubKeyNames())
                        {
                            using (var k2 = OpenSubKey(k1, pnpName))
                            {
                                if (k2 == null)
                                    continue;
 
                                var friendlyName = GetValue(k2, "ServiceName");
                                var deviceId = GetDeviceToken(GetValue(k2, "AssocBdAddr"));
                                if (string.IsNullOrEmpty(deviceId))
                                {
                                    Console.WriteLine("GetLocalServices: Missing required deviceId");
                                    continue;
                                }
 
                                var deviceInfo = new BluetoothDevice
                                {
                                    Name = friendlyName,
                                    DeviceId = deviceId,
                                };
                                Verbose("GetLocalServices: Add {0}", deviceInfo);
                                _devices.Add(deviceId, deviceInfo);
                            }
                        }
                    }
                }
            }
        }
 
        private void GetDeviceNames()
        {
            GetDeviceNamesForWindows7();
            GetDeviceNamesForWindows8();
        }
 
        private void GetDeviceNamesForWindows8()
        {
            using (var key = Registry.LocalMachine.OpenSubKey(BluetoothRegKey, false))
            {
                if (key == null)
                {
                    Console.WriteLine("GetDeviceNamesForWindows8: missing HKLM {0}", BluetoothRegKey);
                    return;
                }
 
                foreach (var name in key.GetSubKeyNames())
                {
                    using (var k1 = OpenSubKey(key, name))
                    {
                        if (k1 == null)
                            continue;
 
                        foreach (var pnpName in k1.GetSubKeyNames())
                        {
                            using (var pnpKey = OpenSubKey(k1, pnpName))
                            {
                                if (pnpKey == null)
                                    continue;
 
                                BluetoothDevice deviceInfo;
                                var deviceId = GetDeviceName2(pnpName);
                                if (string.IsNullOrEmpty(deviceId))
                                    continue;
 
                                var friendlyName = GetValue(pnpKey, "FriendlyName", pnpName);
                                if (!_devices.TryGetValue(deviceId, out deviceInfo))
                                {
                                    deviceInfo = new BluetoothDevice();
                                    _devices.Add(deviceId, deviceInfo);
                                }
 
                                if (string.IsNullOrEmpty(deviceInfo.Name))
                                    deviceInfo.Name = friendlyName;
                                deviceInfo.DeviceId = deviceId;
                                Verbose("GetDevicenamesForWindows8: Add {0}", deviceInfo);
                            }
                        }
                    }
                }
            }
        }
 
        private void SetDevicePorts()
        {
            using (var key = Registry.LocalMachine.OpenSubKey(BluetoothRegKey, false))
            {
                if (key == null)
                {
                    Console.WriteLine("SetDevicePorts: missing HKLM {0}", BluetoothRegKey);
                    return;
                }
 
                foreach (var name in key.GetSubKeyNames())
                {
                    using (var k1 = OpenSubKey(key, name))
                    {
                        if (k1 == null)
                            continue;
 
                        foreach (var pnpName in k1.GetSubKeyNames())
                        {
                            using (var pnpKey = OpenSubKey(k1, pnpName))
                            {
                                if (pnpKey == null)
                                    continue;
 
                                BluetoothDevice deviceInfo;
                                var isLocalMfg = name.Contains("LOCALMFG");
                                if (!isLocalMfg)
                                    continue;
 
                                var deviceId = GetDeviceName(pnpName);
                                if (deviceId == NullDeviceId)
                                    continue;
 
                                if (string.IsNullOrEmpty(deviceId))
                                {
                                    Console.WriteLine("SetDevicePorts:Missing DeviceId for {0}\\{1}", name, pnpName);
                                    continue;
                                }
 
                                if (!_devices.TryGetValue(deviceId, out deviceInfo))
                                {
                                    Console.WriteLine("SetDevicePorts: Missing DeviceInfo for device: {0} {1}\\{2}", deviceId, name, pnpName);
                                    continue;
                                }
 
                                if (!string.IsNullOrEmpty(deviceInfo.Port))
                                    continue;
 
                                if (!pnpKey.GetSubKeyNames().Contains("Device Parameters"))
                                {
                                    Console.WriteLine("SetDevicePorts:Missing Device Parameters for {0}\\{1}", name, pnpName);
                                    continue;
                                }
 
                                var portName = GetPortName(pnpKey);
                                if (string.IsNullOrEmpty(portName))
                                {
                                    Console.WriteLine("SetDevicePorts: Missing PortName for {0}", pnpName);
                                    continue;
                                }
                                deviceInfo.Port = portName;
                            }
                        }
                    }
                }
            }
        }
 
        public static string GetDeviceGuid(string value)
        {
            Verbose("GetDeviceGuid: {0}", value);
 
            var idx = value.IndexOf("}");
            if (idx > 0)
            {
                var id = value.Substring(0, idx);
                if (id.Length == 39)
                {
                    Verbose("GetDeviceGuid: returns {0}", id);
                    return id;
                }
            }
            return null;
        }
 
        private static string GetDeviceName(string value)
        {
            //Verbose("GetDeviceName: {0}", value);
 
            var parts1 = value.Split('_');
            if (parts1.Length <= 1)
            {
                Console.WriteLine("GetDeviceName: name parse error on {0}", value);
                return "";
            }
 
            var parts2 = parts1[0].Split('&');
            if (parts2.Length == 0)
            {
                Console.WriteLine("GetDeviceName: name parse error on {0}", value);
                return "";
            }
            var id = parts2[parts2.Length - 1];
 
            Verbose("GetDeviceName: returns {0}", id);
            return id;
        }
 
        private static string GetDeviceName2(string value)
        {
            //Verbose("GetDevicename2: {0}", value);
 
            var parts = value.Split('_');
            if (parts.Length <= 1)
            {
                Console.WriteLine("GetDeviceName2: name parse error on {0}", value);
                return "";
            }
            var deviceId = parts[1];
            if (string.IsNullOrEmpty(deviceId))
            {
                Console.WriteLine("GetDeviceName2: parse error on {0}", value);
                return "";
            }
            Verbose("GetDeviceName2: returns {0}", deviceId);
            return deviceId;
        }
 
        private static string GetPortName(RegistryKey key)
        {
            using (var paramsKey = OpenSubKey(key, "Device Parameters"))
            {
                if (paramsKey == null || !paramsKey.GetValueNames().Contains("PortName"))
                    return null;
 
                var portName = (string)paramsKey.GetValue("PortName");
                Verbose("GetPortName: {0}", portName);
                return portName;
            }
        }
 
        /// <summary>
        /// "AssocBdAddr"=hex:8f,68,c1,6c,6e,64,00,00 to 646E6CC1688F
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string GetDeviceToken(string value)
        {
            if (string.IsNullOrEmpty(value))
                return null;
 
            var parts = value.ToUpper().Split('-');
            if (parts.Length < 6)
                return null;
 
            var result = string.Join("", parts.Take(6).Reverse());
            Verbose("GetDeviceToken: {0}", result);
 
            return result;
        }
 
        private static string GetValue(RegistryKey key, string name, string defaultValue = null)
        {
            if (!key.GetValueNames().Contains(name))
            {
                Console.WriteLine("Missing Value: {0}", name);
                return defaultValue;
            }
            var value = key.GetValue(name);
            var result = value as string;
            if (result != null)
            {
                Verbose("GetValue: {0} {1}", name, result);
                return result;
            }
            var bytes = value as byte[];
            if (bytes != null)
            {
                result = BitConverter.ToString(bytes);
                Verbose("GetValue: {0} {1}", name, result);
                return result;
            }
 
            return null;
        }
 
        private static RegistryKey OpenSubKey(RegistryKey key, string name)
        {
            if (key == null)
                return null;
 
            var parent = key.Name.Replace(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\""");
            Verbose("OpenSubKey: {0}\\{1}", parent, name);
 
            var result = key.OpenSubKey(name, false);
            if (result == null)
                Console.WriteLine("OpenSubKey: {0}\\{1}", parent, name);
            return result;
        }
 
    }
}
 
namespace BlueToothDevices
{
    class Program
    {
        static void Main(string[] args)
        {
            Bluetooth.VerboseMode = args.Length > 0 && args[0] == "true";
            foreach (var device in Bluetooth.GetBluetoothDevices())
            {
                Console.WriteLine("{0}", device);
            }
 
            Bluetooth.VerboseMode = false;
            foreach (var port in Bluetooth.DeviceNames)
            {
                Console.WriteLine("Device: {0}", port);
            }
        }
    }
}
 

Tuesday, June 10, 2014

WPF WaterMark Textbox to show PlaceHolder Text

This technique uses the Background property to show / hide placeholder textbox.
Advantage is that Placeholder is shown event when Textbox has the focus

How it works:
When textbox has a value, background set to White to cover up PlaceHolder text.
When textbox is empty, background becomes Transparent to show PlaceHolder text.

Here is basic example.  For my own purposes I turned this into a UserControl.

    <Grid>
        <Grid.Resources>
            <ux:NotEmptyConverter x:Key="NotEmptyConverter" />
            
            <Style TargetType="{x:Type Control}" x:Key="DefaultStyle">
                <Setter Property="FontSize" Value="20" />
                <Setter Property="Margin" Value="10"/>
                <Setter Property="VerticalAlignment" Value="Center"></Setter>
                <Setter Property="VerticalContentAlignment" Value="Center"></Setter>
            </Style>
 
            <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource DefaultStyle}"></Style>
 
        </Grid.Resources>
 
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" Text="Placeholder Text Is Here" Foreground="DarkGray" />
        <TextBox Grid.Row="0" Name="TextBoxEdit" 
                Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
            <TextBox.Style>
                <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource DefaultStyle}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path=FirstName.Length, FallbackValue=0, TargetNullValue=0}" Value="0">
                            <Setter Property="Background" Value="Transparent"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Path=FirstName, FallbackValue=0, TargetNullValue=0, Converter={StaticResource NotEmptyConverter}}" Value="false">
                            <Setter Property="Background" Value="White"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>
    </Grid>

// Corresponding value converter to detect non-empty string

    public class NotEmptyConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var s = value as string;
            return string.IsNullOrEmpty(s);
        }
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }