I have a list ob object like this:
public class Device : ObjectBase
{
private int _DeviceNbr;
public int DeviceNbr
{
get { return _DeviceNbr; }
set { _DeviceNbr = value; }
}
private string _DeviceName;
public string DeviceName
{
get { return _DeviceName; }
set { _DeviceName = value; OnPropertyChanged(); }
}
private ObservableCollection<State> _DeviceStates;
public ObservableCollection<State> DeviceStates
{
get { return _DeviceStates; }
set { _DeviceStates = value; OnPropertyChanged(); }
}
}
public class State: ObjectBase
{
public int StateNbr { get; set; }
private string _stateType;
public string StateType
{
get { return _stateType; }
set { _stateType = value; }
}
private int _value;
public int Value
{
get { return _value; }
set { _value = value; OnPropertyChanged(); }
}
}
which I need to bind to a Datagrid.
My approach is to create a customDataGrid which looks like this:
public class CustomGrid : DataGrid
{
public ObservableCollection<ColumnConfig> ColumnConfigs
{
get { return GetValue(ColumnConfigsProperty) as ObservableCollection<ColumnConfig>; }
set { SetValue(ColumnConfigsProperty, value); }
}
public static readonly DependencyProperty ColumnConfigsProperty =
DependencyProperty.Register("ColumnConfigs", typeof(ObservableCollection<ColumnConfig>), typeof(CustomGrid), new PropertyMetadata(new PropertyChangedCallback(OnColumnsChanged)));
static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = d as CustomGrid;
dataGrid.Columns.Clear();
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "Nbr", Binding = new Binding("DeviceNbr") });
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "Device", Binding = new Binding("DeviceName") });
foreach (var columnConfig in dataGrid.ColumnConfigs.Where(c => c.IsVisible))
{
var column = new DataGridTextColumn()
{
Header = columnConfig.ColumnHeader,
Binding = new Binding("DeviceStates")
{
ConverterParameter = columnConfig.ColumnName,
Converter = new DeviceStateConverter(),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode =BindingMode.TwoWay
}
};
dataGrid.Columns.Add(column);
}
}
}
public class DeviceStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ObservableCollection<State> DeviceStates && parameter != null)
{
var DeviceState = DeviceStates.FirstOrDefault(s => s.StateType == parameter.ToString());
if (DeviceState != null)
return DeviceState.Value;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The ViewModel looks like this:
public class MainViewModel : ObjectBase
{
private ObservableCollection<Device> _Devices;
public ObservableCollection<Device> Devices
{
get { return _Devices; }
set { _Devices = value; OnPropertyChanged(); }
}
private ObservableCollection<ColumnConfig> _columnConfigs;
public ObservableCollection<ColumnConfig> ColumnConfigs
{
get { return _columnConfigs; }
set { _columnConfigs = value; OnPropertyChanged(); }
}
public MainViewModel()
{
Devices = new ObservableCollection<Device>();
_columnConfigs = new ObservableCollection<ColumnConfig>()
{
new ColumnConfig(){ ColumnHeader = "On", ColumnName = "On", ColumnWidth= 100, IsVisible= true},
new ColumnConfig(){ ColumnHeader = "Off", ColumnName = "Off", ColumnWidth= 100, IsVisible= true}
};
for ( int i = 0; i <= 100; i++)
{
_Devices.Add(new Device()
{
DeviceNbr = i,
DeviceName = "Device " + i.ToString(),
DeviceStates = new ObservableCollection<State>()
{
new State() { StateType = "On", Value= i},
new State() { StateType = "Off", Value= i+1}
}
});
}
OnPropertyChanged("ColumnConfigs");
OnPropertyChanged("Devices");
}
public void TestStateChange ()
{
Devices[2].DeviceName = "Device X";
Devices[2].DeviceStates[0].Value = 5;
// OnPropertyChanged("Devices");
}
}
And the XAML like this:
<local:CustomGrid
AutoGenerateColumns="False"
ColumnConfigs="{Binding ColumnConfigs}"
ItemsSource="{Binding Devices, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Here is the result:
Application
Now the problem is that the binding for the Devicesstates does not work.
In the ViewModel is a method called "TestStateChange" where tried to change that state. The state in the DeviceStatesCollection changed like expected but it doesn't reflect to the view. Can someone please provide me some help?
UPDATE
The binding works but PropertyChanged Event when changing the value of a state does not fire.
public void TestStateChange()
{
foreach (var device in Devices)
{
foreach (var state in device.DeviceStates)
{
state.Value = state.Value + 1;
}
device.OnPropertyChanged("DeviceStates");
}
}
So I have to raise the PropertyChangedEvent on the "parent" collection of the state. That's weird.
The only think that I can now think of, is to implement an event in the State class and let the parent collection object subscribe to it.
Does someone has a better idea?
If your State object implements INotifyPropertyChanged (which all your bindable data objects should), and if your datagrid column binds directly to a State object (i.e. via a binding path that contains an index) then your UI will update as you expect.
You could subscribe to the State property change events from the parent Device object and then re-notify from there, but that is a long winded way to do it and means that you have to subscribe to the CollectionChanged event of the ObservableCollection that contains the State objects so that you can attach/unattach from the PropertyChange event on each State object (sometimes this will be the only way, but avoid it if you can).
I am trying to create my own checkbox column (replacing the default one), in order to move to more complex data columns later-on, and I have the following code:
public class MyCheckBoxColumn : DataGridBoundColumn
{
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var cb = new CheckBox();
var bb = this.Binding as Binding;
var b = new Binding { Path = bb.Path, Source = cell.DataContext };
cb.SetBinding(ToggleButton.IsCheckedProperty, b);
return cb;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
var cb = new CheckBox();
var bb = this.Binding as Binding;
var b = new Binding { Path = bb.Path, Source = ToggleButton.IsCheckedProperty };
cb.SetBinding(ToggleButton.IsCheckedProperty, b);
return cb;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var cb = editingElement as CheckBox;
return cb.IsChecked;
}
protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
{
var cb = editingElement as CheckBox;
if (cb != null) cb.IsChecked = (bool)uneditedValue;
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
var cb = editingElement as CheckBox;
BindingExpression binding = editingElement.GetBindingExpression(ToggleButton.IsCheckedProperty);
if (binding != null) binding.UpdateSource();
return true;// base.CommitCellEdit(editingElement);
}
}
And my custom DataGrid:
public class MyDataGrid : DataGrid
{
protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
{
try
{
var type = e.PropertyType;
if (type == typeof(bool))
{
var col = new MyCheckBoxColumn();
col.Binding = new Binding(e.PropertyName) {Mode = BindingMode.TwoWay};
e.Column = col;
}
else
{
base.OnAutoGeneratingColumn(e);
}
var propDescr = e.PropertyDescriptor as System.ComponentModel.PropertyDescriptor;
e.Column.Header = propDescr.Description;
}
catch (Exception ex)
{
Utils.ReportException(ex);
}
}
}
Now, everything seems nice except for two things:
It seems that the only used method in in MyCheckBoxColumn is the GenerateElement(). All the other methods are not used. I have put breakpoints in them and they never get hit...
I use an ObservableCollection as a data source and, while the rest of the columns notify me when they get changed, this one doesn't.
The odd thing is that the bool value gets changed when you check/uncheck the checkbox, but without notification and without passing through CommitCellEdit().
Does anyone know what is going wrong here?
EDIT :
It seems that if I return a TextBlock from inside GenerateElement() it makes the other methods to be called (the notification problem doesn't get fixed though). But why doesn't this work with with CheckBoxes? How does the default check box column work???
OK. Here is the complete code for a custom CheckBox column. It seems that, in order to have a control like a checkbox as a display (not editing) element in a DataGrid you have to make it hit-test-invisible. Or you can simply use a TextBlock to display some character that resembles a checkmark:
public class MyCheckBoxColumn : DataGridBoundColumn
{
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var cb = new CheckBox() { IsHitTestVisible = false, HorizontalAlignment = HorizontalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center };
var bb = this.Binding as Binding;
var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay };
cb.SetBinding(ToggleButton.IsCheckedProperty, b);
return cb;
// var cb = new TextBlock() { TextAlignment = TextAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center };
// var bb = this.Binding as Binding;
// var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay, Converter = new MyBoolToMarkConverter() };
// cb.SetBinding(TextBlock.TextProperty, b);
// return cb;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
var cb = new CheckBox() { HorizontalAlignment = HorizontalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center };
var bb = this.Binding as Binding;
var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay };
cb.SetBinding(ToggleButton.IsCheckedProperty, b);
return cb;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var cb = editingElement as CheckBox;
if (cb != null) return cb.IsChecked;
return false;
}
protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
{
var cb = editingElement as CheckBox;
if (cb != null) cb.IsChecked = (bool)uneditedValue;
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
// The following 2 lines seem to help when sometimes the commit doesn't happen (for unknown to me reasons).
//var cb = editingElement as CheckBox;
//cb.IsChecked = cb.IsChecked;
BindingExpression binding = editingElement.GetBindingExpression(ToggleButton.IsCheckedProperty);
if (binding != null) binding.UpdateSource();
return true;// base.CommitCellEdit(editingElement);
}
}
//--------------------------------------------------------------------------------------------
public class MyBoolToMarkConverter : IValueConverter
{
const string cTick = "■";
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(bool)) return "";
bool val = (bool)value;
return val ? cTick : "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(string)) return false;
string val = (string)value;
return val == cTick;
}
}
//--------------------------------------------------------------------------------------------
In Dapper (http://code.google.com/p/dapper-dot-net/), is there a way to ignore properties in the model class, namely when using the Insert extension method?
My model class has a set of computed properties which are not persisted in the associated table.
Well, Dapper has no Insert extension method, that is in dapper.contrib, dapper extensions or dapper rainbow.
Dapper itself allows you to do:
Animal a = new Animal {Age = 10, Family = "Canine"}
// only insert Age
cnn.Execute("insert Animal(Age) values (#Age)", a);
To work around for some of the extension classes you can sometimes do:
cnn.InsertExtension("Animal", new{a.Age});
Regardless, you can always fall back to raw Dapper for your complex filtered inserts.
If you are using Dapper.Contrib, check out this code in SqlMapperExtensions:
https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper.Contrib/SqlMapperExtensions.cs#L54-L66
private static List<PropertyInfo> ComputedPropertiesCache(Type type)
{
//...
var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
and https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper.Contrib/SqlMapperExtensions.Async.cs#L147-L165
var computedProperties = ComputedPropertiesCache(type);
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
//...
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
{
//...
So if you add a ComputedAttribute to your properties on your class, Dapper.Contrib won't try to insert them in to the database! You shouldn't have to worry about getting Dapper to ignore those properties, only Dapper.Contrib. Because if you use select * from tablename in your dapper queries,it will only try to map columns that exist. So you just don't create columns for the properties which you marked as [Computed].
Just add the [Computed] attribute to the properties in question.
If you just want to "hide" a property from your Insert/Update statements then there is one official way to do this in dapper extensions:
using DapperExtensions.Mapper;
public class UserMapper : ClassMapper<User>
{
public UserMapper()
{
base.Map(m => m.IsTrialUser).Ignore();
base.AutoMap();
}
}
I wrote a modified version of the SqlMapperExtensions class and added a string[] parameter ExcludeProperties for excluding columns. Using this class you can make calls like:
// Insert
result = (int)cn.Insert<Animal>(box, new string[] { "Errors", "IsValid" });
// Update
cn.UpdateEntity<Animal>(box, new string[] { "Errors", "IsValid" });
In the statements above, the 'Errors' and 'IsValid' properties I added for will not be included in the SQL statements.
Here's the modified extensions class:
SqlMapperExtensions.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Collections.Concurrent;
using System.Reflection.Emit;
using System.Threading;
using System.Runtime.CompilerServices;
namespace Dapper.Contrib.Extensions
{
public static class SqlMapperExtensions
{
public interface IProxy
{
bool IsDirty { get; set; }
}
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeTableName = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type)
{
if (KeyProperties.ContainsKey(type.TypeHandle))
{
return KeyProperties[type.TypeHandle];
}
var allProperties = TypePropertiesCache(type);
var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList();
if (keyProperties.Count == 0)
{
var idProp = allProperties.Where(p => p.Name.ToLower() == "id" || p.Name.ToLower() == (type.Name.ToLower() + "id")).FirstOrDefault();
if (idProp != null)
{
keyProperties.Add(idProp);
}
}
KeyProperties[type.TypeHandle] = keyProperties;
return keyProperties;
}
private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type)
{
if (TypeProperties.ContainsKey(type.TypeHandle))
{
return TypeProperties[type.TypeHandle];
}
var properties = type.GetProperties();
TypeProperties[type.TypeHandle] = properties;
return properties;
}
/// <summary>
/// Returns a single entity by a single id from table "Ts". T must be of interface type.
/// Id must be marked with [Key] attribute.
/// Created entity is tracked/intercepted for changes and used by the Update() extension.
/// </summary>
/// <typeparam name="T">Interface type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param>
/// <returns>Entity of T</returns>
public static T GetEntity<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
string sql;
if (!GetQueries.TryGetValue(type.TypeHandle, out sql))
{
var keys = KeyPropertiesCache(type);
if (keys.Count() > 1)
throw new DataException("Get<T> only supports an entity with a single [Key] property");
if (keys.Count() == 0)
throw new DataException("Get<T> only supports en entity with a [Key] property");
var onlyKey = keys.First();
var name = GetTableName(type);
// TODO: pluralizer
// TODO: query information schema and only select fields that are both in information schema and underlying class / interface
sql = "select * from " + name + " where " + onlyKey.Name + " = #id";
GetQueries[type.TypeHandle] = sql;
}
var dynParms = new DynamicParameters();
dynParms.Add("#id", id);
T obj = null;
if (type.IsInterface)
{
var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary<string, object>;
if (res == null)
return (T)((object)null);
obj = ProxyGenerator.GetInterfaceProxy<T>();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
property.SetValue(obj, val, null);
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
}
else
{
obj = connection.Query<T>(sql, dynParms, transaction: transaction, commandTimeout: commandTimeout).FirstOrDefault();
}
return obj;
}
private static string GetTableName(Type type)
{
string name;
if (!TypeTableName.TryGetValue(type.TypeHandle, out name))
{
//name = type.Name + "s";
name = type.Name;
if (type.IsInterface && name.StartsWith("I"))
name = name.Substring(1);
//NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework
var tableattr = type.GetCustomAttributes(false).Where(attr => attr.GetType().Name == "TableAttribute").SingleOrDefault() as
dynamic;
if (tableattr != null)
name = tableattr.Name;
TypeTableName[type.TypeHandle] = name;
}
return name;
}
/// <summary>
/// Inserts an entity into table "Ts" and returns identity id.
/// </summary>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToInsert">Entity to insert</param>
/// <returns>Identity of inserted entity</returns>
public static long Insert<T>(this IDbConnection connection, T entityToInsert, string[] ExcludeProperties = null, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
//using (var tx = connection.BeginTransaction())
//{
var type = typeof(T);
var name = GetTableName(type);
var sb = new StringBuilder(null);
sb.AppendFormat("insert into {0} (", name);
var allProperties = TypePropertiesCache(type);
if (ExcludeProperties != null)
{
List<PropertyInfo> someProperties = allProperties.ToList();
foreach (PropertyInfo prop in allProperties.ToArray())
{
if (ExcludeProperties.Contains(prop.Name))
{
someProperties.Remove(prop);
}
}
allProperties = someProperties.AsEnumerable();
}
var keyProperties = KeyPropertiesCache(type);
for (var i = 0; i < allProperties.Count(); i++)
{
var property = allProperties.ElementAt(i);
if (keyProperties.Contains(property)) continue;
sb.Append(property.Name);
if (i < allProperties.Count() - 1)
sb.Append(", ");
}
sb.Append(") values (");
for (var i = 0; i < allProperties.Count(); i++)
{
var property = allProperties.ElementAt(i);
if (keyProperties.Contains(property)) continue;
sb.AppendFormat("#{0}", property.Name);
if (i < allProperties.Count() - 1)
sb.Append(", ");
}
sb.Append(") ");
connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout);
//NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE
var r = connection.Query("select ##IDENTITY id");
//tx.Commit();
return (int)r.First().id;
//}
}
/// <summary>
/// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension.
/// </summary>
/// <typeparam name="T">Type to be updated</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToUpdate">Entity to be updated</param>
/// <returns>true if updated, false if not found or not modified (tracked entities)</returns>
public static bool UpdateEntity<T>(this IDbConnection connection, T entityToUpdate, string[] ExcludeProperties = null, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var proxy = entityToUpdate as IProxy;
if (proxy != null)
{
if (!proxy.IsDirty) return false;
}
var type = typeof(T);
var keyProperties = KeyPropertiesCache(type);
if (keyProperties.Count() == 0)
throw new ArgumentException("Entity must have at least one [Key] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("update {0} set ", name);
var allProperties = TypePropertiesCache(type);
if (ExcludeProperties != null)
{
List<PropertyInfo> someProperties = allProperties.ToList();
foreach (PropertyInfo prop in allProperties.ToArray())
{
if (ExcludeProperties.Contains(prop.Name))
{
someProperties.Remove(prop);
}
}
allProperties = someProperties.AsEnumerable();
}
var nonIdProps = allProperties.Where(a => !keyProperties.Contains(a));
for (var i = 0; i < nonIdProps.Count(); i++)
{
var property = nonIdProps.ElementAt(i);
sb.AppendFormat("{0} = #{1}", property.Name, property.Name);
if (i < nonIdProps.Count() - 1)
sb.AppendFormat(", ");
}
sb.Append(" where ");
for (var i = 0; i < keyProperties.Count(); i++)
{
var property = keyProperties.ElementAt(i);
sb.AppendFormat("{0} = #{1}", property.Name, property.Name);
if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and ");
}
var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
return updated > 0;
}
/// <summary>
/// Delete entity in table "Ts".
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToDelete">Entity to delete</param>
/// <returns>true if deleted, false if not found</returns>
public static bool Delete<T>(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var keyProperties = KeyPropertiesCache(type);
if (keyProperties.Count() == 0)
throw new ArgumentException("Entity must have at least one [Key] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("delete from {0} where ", name);
for (var i = 0; i < keyProperties.Count(); i++)
{
var property = keyProperties.ElementAt(i);
sb.AppendFormat("{0} = #{1}", property.Name, property.Name);
if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and ");
}
var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction: transaction, commandTimeout: commandTimeout);
return deleted > 0;
}
class ProxyGenerator
{
private static readonly Dictionary<Type, object> TypeCache = new Dictionary<Type, object>();
private static AssemblyBuilder GetAsmBuilder(string name)
{
var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name },
AssemblyBuilderAccess.Run); //NOTE: to save, use RunAndSave
return assemblyBuilder;
}
public static T GetClassProxy<T>()
{
// A class proxy could be implemented if all properties are virtual
// otherwise there is a pretty dangerous case where internal actions will not update dirty tracking
throw new NotImplementedException();
}
public static T GetInterfaceProxy<T>()
{
Type typeOfT = typeof(T);
if (TypeCache.ContainsKey(typeOfT))
{
return (T)TypeCache[typeOfT];
}
var assemblyBuilder = GetAsmBuilder(typeOfT.Name);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter
var interfaceType = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy);
var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(),
TypeAttributes.Public | TypeAttributes.Class);
typeBuilder.AddInterfaceImplementation(typeOfT);
typeBuilder.AddInterfaceImplementation(interfaceType);
//create our _isDirty field, which implements IProxy
var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder);
// Generate a field for each property, which implements the T
foreach (var property in typeof(T).GetProperties())
{
var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute);
CreateProperty<T>(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId);
}
var generatedType = typeBuilder.CreateType();
//assemblyBuilder.Save(name + ".dll"); //NOTE: to save, uncomment
var generatedObject = Activator.CreateInstance(generatedType);
TypeCache.Add(typeOfT, generatedObject);
return (T)generatedObject;
}
private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
{
var propType = typeof(bool);
var field = typeBuilder.DefineField("_" + "IsDirty", propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty("IsDirty",
System.Reflection.PropertyAttributes.None,
propType,
new Type[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName |
MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + "IsDirty",
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty",
getSetAttr,
null,
new Type[] { propType });
var currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ret);
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy).GetMethod("get_" + "IsDirty");
var setMethod = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy).GetMethod("set_" + "IsDirty");
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
return currSetPropMthdBldr;
}
private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity)
{
//Define the field and the property
var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);
var property = typeBuilder.DefineProperty(propertyName,
System.Reflection.PropertyAttributes.None,
propType,
new Type[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual |
MethodAttributes.HideBySig;
// Define the "get" and "set" accessor methods
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName,
getSetAttr,
propType,
Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName,
getSetAttr,
null,
new Type[] { propType });
//store value in private field and set the isdirty flag
var currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldc_I4_1);
currSetIL.Emit(OpCodes.Call, setIsDirtyMethod);
currSetIL.Emit(OpCodes.Ret);
//TODO: Should copy all attributes defined by the interface?
if (isIdentity)
{
var keyAttribute = typeof(KeyAttribute);
var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { });
var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { });
property.SetCustomAttribute(attributeBuilder);
}
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(T).GetMethod("get_" + propertyName);
var setMethod = typeof(T).GetMethod("set_" + propertyName);
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
}
}
}
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
public TableAttribute(string tableName)
{
Name = tableName;
}
public string Name { get; private set; }
}
}
I wrote a lightweight ORM that's an extension of Dapper. It actually does what you need. If you leave off the "Member" attribute of the property, the ORM will exclude it from the Insert.
You can access it at https://www.github.com/ricericebaby/ADOCRUD
I'm attempting to convert the displayed value of a combobox by using its binding as a key to look for the value I would like to display. I can't seem to get it to work.
The datacontext of my user control is MyObject.
MyObject has a property "MasterDrawerId", which is the Id of "MyReferencedObject".
Elsewhere in my application, accessible through a static property of my App.xaml.cs is a collection of "MyOtherObjects". "MyReferencedObject" has a foreign key relationship with the Id of "MyOtherObject".
My combobox is bound to the "MasterDrawerId", which is what's passed into the converter.
I then use that as a lookup for "MyReferencedObject" to get the foreign key Id of "MyOtherObject" in order to display the name of that object.
I know it seems confusing but it's basically just using the property of the datacontext in order to do a lookup and display the name of another object in its place within a combobox.
This is my code:
masterSiteComboBox.DisplayMemberPath = "Name";
Binding binding = new Binding("MasterDrawerId");
binding.Mode = BindingMode.TwoWay;
binding.Converter = new DrwIdToSiteConverter();
masterSiteComboBox.SelectedItem = binding;
masterSiteComboBox.ItemsSource = ListOfMyOtherObjects;
Here is my converter code:
public class DrwIdToSiteConverter : IValueConverter
{
public DrwIdToSiteConverter()
{
}
public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
XferSite site = new XferSite();
foreach(XferUserDrawerPermissions perm in App.UserDrawerPermissions)
{
if (perm.DocumentTypeId.Match(value.ToString()))
{
site.Id = int.Parse(perm.SiteId);
site.Name = perm.SiteName;
break;
}
}
return site;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
I set a breakpoint at the first line of my "Convert" method of my converter and it never gets hit.
The selected item for a combo box must be an item that is already contained within the collection of objects that you set through the ItemsSource property.
In other words, if your ItemsSource is bound to a collection of Object1, Object2, Object3, you cannot set the SelectedItem to new Object() { Name = 1 }; If you do this, you must override the Equals and GetHashCode methods. This will allow you the ability to set the SelectedItem to a new object.
Example:
public class MyObject
{
public MyObject(string name)
{
if(string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
Name = name;
}
public string Name { get; private set; }
// override object.Equals
public override bool Equals(object obj)
{
//
// See the full list of guidelines at
// http://go.microsoft.com/fwlink/?LinkID=85237
// and also the guidance for operator== at
// http://go.microsoft.com/fwlink/?LinkId=85238
//
MyObject myObj = obj as MyObject;
if (myObj == null) return false;
return Name == myObj.Name;
}
// override object.GetHashCode
public override int GetHashCode()
{
return Name.GetHashCode;
}
}
var items = new List<MyObject>()
{
new MyObject {Name = "One"},
new MyObject {Name = "Two"},
new MyObject {Name = "Three"},
};
// Converter code
return new MyObject {Name = "One"};
Instead of
masterSiteComboBox.SelectedItem = binding;
do
masterSiteComboBox.SetBinding(ComboBox.SelectedItemProperty, binding);
I want to bind a collection of objects to a DataGrid in Silverlight. The objects belong to the following type:
public class Seats
{
Dictionary<Group, long> dctValues = new Dictionary<Group, long>();
public int Id { get; set; }
public Dictionary<Group, long> Values
{
get { return dctValues; }
}
}
Whereas, Group is represented by:
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
}
I want to be able to generate columns based on the dictionary of groups, where each column would have the header set to Group.Name and the cell value for each item equal to the long value in the dictionary.
I'm going to assume that we can't guarantee that there is only a single instance of a Group class for each group name. (Else you would be generating columns based on a list of known groups no?)
Here is a class derived from DataGrid:
public class SeatsGrid : DataGrid, IValueConverter
{
public SeatsGrid()
{
AutoGenerateColumns = false;
}
#region public List<Seats> SeatsList
public List<Seats> SeatsList
{
get { return GetValue(SeatsListProperty) as List<Seats>; }
set { SetValue(SeatsListProperty, value); }
}
public static readonly DependencyProperty SeatsListProperty =
DependencyProperty.Register(
"SeatsList",
typeof(List<Seats>),
typeof(SeatsGrid),
new PropertyMetadata(null, OnSeatsListPropertyChanged));
private static void OnSeatsListPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SeatsGrid source = d as SeatsGrid;
List<Seats> value = e.NewValue as List<Seats>;
source.OnSeatsListPropertyChanged(value);
}
private void OnSeatsListPropertyChanged(List<Seats> value)
{
ItemsSource = null;
Columns.Clear();
var groups = value
.SelectMany(seats => seats.Values.Keys)
.Select(g => g.Name)
.Distinct();
foreach (var group in groups)
{
DataGridTextColumn col = new DataGridTextColumn();
col.Binding = new Binding()
{
Converter = this,
ConverterParameter = group
};
col.Header = group;
Columns.Add(col);
}
ItemsSource = value;
}
#endregion public List<Seats> SeatsList
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Seats seats = (Seats)value;
string group = (string)parameter;
Group actualGroup = seats.Values.Keys.FirstOrDefault(g => g.Name == group);
if (actualGroup != null)
{
return seats.Values[actualGroup].ToString();
}
else
{
return null;
}
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Place an instance of this class in Xaml and assign a List<Seats> to its SeatsList property and it generates the columns and renders the rows.
How does it work?
The magic starts in the OnSeatsListPropertyChanged method. It first gets a list of distinct Group names. It generates a new Text column for each group name setting the header naturally to the group name.
The weird stuff appears when setting the binding for the column. The binding is given a converter which for convience I decided to implement on the SeatsGrid class as well. The converter parameter is the group name. Since no path is specified the whole Seats object will be passed to the converter when binding actually occurs.
Now looking at the IValueConverter.Convert method. It finds an instance (is any) of Group in the seats that has the same name as the converter parameter. If found uses that Group as the key to lookup a value to return.
If the Groups were known to be unique per name then the code can be simplified but the principle is the same.