private static void OnNetworkTableChange(ITable table, string key, Value v, NotifyFlags flags)
 {
     try
     {
         //multiple objects could be bound to this key
         foreach (INotifyPropertyChanged source in propertyLookup.Keys)
         {
             OneToOneConversionMap <string, string> conversionMap = propertyLookup[source];
             ITable boundTable    = customTables[source];
             object bindingSource = (source is DependencyNotifyListener) ? (object)(source as DependencyNotifyListener).source : source;
             if (table.ToString() != boundTable.ToString())
             {
                 continue;
             }
             if (conversionMap.TryGetBySecond(key, out string property))
             {
                 //the property that changed is bound to this object
                 //grab the converter and use it if needed
                 IValueConverter converter = conversionMap.GetConverterByFirst(property);
                 PropertyInfo    inf       = bindingSource.GetType().GetProperty(property);
                 //issue using v for some reason
                 object value = NetworkUtil.ReadValue(boundTable.GetValue(key, null));
                 if (converter != null)
                 {
                     //in an NTConverter (required in API) the null values are never used so we don't need to set them
                     object attemptedVal = converter.ConvertBack(value, null, null, null);
                     //in case the conversion was invalid
                     if (attemptedVal != DependencyProperty.UnsetValue)
                     {
                         value = attemptedVal;
                     }
                 }
                 //correct any type inconsistencies (eg if we want to display an integer from the network, which only stores doubles)
                 if (value != null && value.GetType() != inf.PropertyType)
                 {
                     Type targetType = inf.PropertyType;
                     if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable <>))
                     {
                         targetType = targetType.GetGenericArguments()[0];
                     }
                     //anything still here can make an invalid cast to let them know to use a converter
                     value = Convert.ChangeType(value, targetType);
                 }
                 //write to the object
                 assignmentDispatch.Invoke(() => inf.SetValue(bindingSource, value));
             }
         }
     }
     catch (InvalidOperationException e)
     {
         if (!e.Message.StartsWith("Collection was modified"))
         {
             throw e;
         }
     }
 }
        /// <summary>
        /// Creates a binding between a dependency property and a network table entry
        /// </summary>
        /// <typeparam name="TLocal">The type of the entry in the dashboard</typeparam>
        /// <typeparam name="TNetwork">The type of the entry in the network table</typeparam>
        /// <param name="source">The object to bind to</param>
        /// <param name="property">The property to bind. Attached properties will throw an exception</param>
        /// <param name="networkPath">The full path of the entry in the network table</param>
        /// <param name="converter">The conversion mapping between the network values and local values</param>
        /// <param name="localOverride">Whether the local dashboard values should take precedence when the binding occurs</param>
        public static void Create <TLocal, TNetwork>(DependencyObject source, DependencyProperty property, string networkPath,
                                                     NTConverter <TLocal, TNetwork> converter, bool localOverride = false)
        {
            if (!IsRunning)
            {
                throw new InvalidOperationException("Can only create bindings while the network table is running");
            }
            Tuple <ITable, string> networkSource = NormalizeKey(networkPath);

            networkPath = networkSource.Item2;
            //because of additional work that needs to be done to bind the value, simpler to reimplement
            DependencyNotifyListener listener = new DependencyNotifyListener(source);

            if (!propertyLookup.ContainsKey(listener))
            {
                propertyLookup[listener] = new OneToOneConversionMap <string, string>();
                //this is a new item being bound, have it notify us of updates
                listener.PropertyChanged += OnLocalValueChange;
            }
            if (propertyLookup[listener].TryAdd(property.Name, networkPath))
            {
                customTables[listener] = networkSource.Item1;
                //this means there were no binding conflicts
                //bind the dependency property to be notified of changes to it
                listener.BindProperty(property);
                //map a conversion if needed; null is valid
                propertyLookup[listener].MapConversionByFirst(property.Name, converter);
                //make the values consistent by forcing an update.
                //first check if the dashboard has a value at all
                Value data = networkSource.Item1.GetValue(networkPath, null);
                if (data == null || localOverride)
                {
                    //send the local value to the network by "updating" the local value
                    PropertyChangedEventArgs e = new PropertyChangedEventArgs(property.Name);
                    OnLocalValueChange(listener, e);
                }
                else
                {
                    //pull the dashboard from the local value by "updating" the network value
                    OnNetworkTableChange(networkSource.Item1, networkPath, data, NotifyFlags.NotifyUpdate);
                }
            }
        }
        /// <summary>
        /// Creates a binding between an observable object property and a network table entry of a different type
        /// </summary>
        /// <typeparam name="TLocal">The type of the entry in the dashboard</typeparam>
        /// <typeparam name="TNetwork">The type of the entry in the network table</typeparam>
        /// <param name="source">The object to bind to</param>
        /// <param name="property">The case-sensitive name of the property to bind</param>
        /// <param name="networkPath">The full path of the entry in the network table</param>
        /// <param name="converter">The conversion mapping between the network values and local values</param>
        /// <param name="localOverride">Whether the local dashboard values should take precedence when the binding occurs</param>
        public static void Create <TLocal, TNetwork>(INotifyPropertyChanged source, string property, string networkPath,
                                                     NTConverter <TLocal, TNetwork> converter, bool localOverride = false)
        {
            if (!IsRunning)
            {
                throw new InvalidOperationException("Can only create bindings while the network table is running");
            }
            Tuple <ITable, string> networkSource = NormalizeKey(networkPath);

            networkPath = networkSource.Item2;
            //add these to our dictionary
            if (!propertyLookup.ContainsKey(source))
            {
                propertyLookup[source] = new OneToOneConversionMap <string, string>();
                //this is a new item being bound; have it notify us of updates
                source.PropertyChanged += OnLocalValueChange;
            }
            if (propertyLookup[source].TryAdd(property, networkPath))
            {
                customTables[source] = networkSource.Item1;
                //this means there were no binding conflicts
                //map a conversion if needed; null is a valid argument
                propertyLookup[source].MapConversionByFirst(property, converter);
                //make the values consistent by forcing an update.
                //first check if the dashboard has a value at all
                object data = networkSource.Item1.GetValue(networkPath, null);
                if (data == null || localOverride)
                {
                    //send the local value to the network by "updating" the local value
                    PropertyChangedEventArgs e = new PropertyChangedEventArgs(property);
                    OnLocalValueChange(source, e);
                }
                else
                {
                    //pull the dashboard from the local value by "updating" the network value
                    OnNetworkTableChange(networkSource.Item1, networkPath, Value.MakeValue(data), NotifyFlags.NotifyUpdate);
                }
            }
        }
        private static void OnLocalValueChange(object sender, PropertyChangedEventArgs e)
        {
            INotifyPropertyChanged obj = sender as INotifyPropertyChanged;
            OneToOneConversionMap <string, string> map = propertyLookup[obj];
            ITable boundTable    = customTables[obj];
            object bindingSource = (obj is DependencyNotifyListener) ? (object)(obj as DependencyNotifyListener).source : obj;

            //first, verify that the property that changed is bound to something
            if (map.TryGetByFirst(e.PropertyName, out string networkPath))
            {
                //grab the converter and use it if needed
                IValueConverter converter = map.GetConverterByFirst(e.PropertyName);
                PropertyInfo    inf       = bindingSource.GetType().GetProperty(e.PropertyName);
                object          value     = inf.GetValue(bindingSource);
                if (converter != null)
                {
                    //in an NTConverter (required in API) the null values are never used so we don't need to set them
                    object attemptedVal = converter.Convert(value, null, null, null);
                    //in case the conversion was invalid
                    if (attemptedVal != DependencyProperty.UnsetValue)
                    {
                        value = attemptedVal;
                    }
                }
                //apparently network table STRONGLY dislikes null values
                if (value != null)
                {
                    //write to the dashboard
                    Value data = Value.MakeValue(value);
                    boundTable.PutValue(networkPath, data);
                    //the network table doesn't know any better and won't try to notify us of this change
                    //therefore, bindings won't be updated again, so we should initiate a network table update
                    //this is ok, because we only write to network table on effective value changes
                    OnNetworkTableChange(boundTable, networkPath, data, NotifyFlags.NotifyUpdate);
                }
            }
        }