I have a WPF app. I'm using MVVM pattern. I have a dictionary:
public abstract class ViewModelBase
{
public static Dictionary<string, Action> Permissions { get; set; }
...
}
and i want to bind it's values to visibility of menu items like so:
<MenuItem x:Name="systemMenuItem" Header="System" Visibility="{Binding Permissions[systemMenuItem].CanBeShow, Converter={StaticResource BoolToVis}}">...</MenuItem>
To fill this dictionary i need window build visual tree because elements of dictionary contains information from window MenuItem's. If i will create dictionary before InitializeComponent i will get an exception that there is no key with value systemMenuItem because VisualTreeHelper.GetChildrenCount returns zero elements. If i will do it on Loaded event i will get normal filled dictionary but binding not working in that case. How can i fill my dictionary before window will be shown to user and get information from MenuItems? And how can i make my binding work in that case? The window is the main and startup.
as a general rule using dictionaries for binding is a bad idea, this is probably why MS never created Observable Dictionary as part of the .Net framework, you're best bet would be to create a Permission class and then have an Observable Collection of them, this will give you both collection binding and change binding on the Permission
Note: this uses C#6 so if you are on a earlier version you may need to tweak it
Example
Xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Permissions}">
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Visibility" Value="{Binding CanBeShow, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Setter Property="Command" Value="{Binding Action}"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>
<Grid/>
</DockPanel>
</Window>
View Model
public class ViewModel
{
public ViewModel()
{
//some dummy data
Permissions.Add(new Permission()
{
Name = "Open",
CanBeShow = true,
Action = ApplicationCommands.Open
});
Permissions.Add(new Permission()
{
Name = "Save",
CanBeShow = false,
Action = ApplicationCommands.Save
});
Permissions.Add(new Permission()
{
Name = "Delete",
CanBeShow = true,
Action = ApplicationCommands.Delete
});
}
public ObservableCollection<Permission> Permissions { get; } = new ObservableCollection<Permission>();
//notice no set you want to change the content of the collection not the collection
}
public class Permission:INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
private bool canBeShow;
public bool CanBeShow
{
get { return canBeShow; }
set
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CanBeShow)));
canBeShow = value;
}
}
private ICommand action;
public ICommand Action
{
get { return action; }
set {
action = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Action)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
however if you are trying to implement some kind of security on Menu actions,
then preferred way of doing this would be to override the ICommand to create a Custom Command that will look up it's own permissions and expose them as properties with out going near the View
public class PermissionCommand:INotifyPropertyChanged,ICommand
{
public event EventHandler CanExecuteChanged;
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get { return name; }
set
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public bool CanBeShow
{
get
{
//Check permissions for CanBeShow here
}
}
public bool CanExecute(object parameter)
{
//Check permissions for Execution here
}
public void Execute(object parameter)
{
// perform action here
}
//you will need to trigger the events when the permissions change
}
You can define a multi-value converter like this:
public class ElementPermissionToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
Dictionary<string, Action> permissions = values.OfType<Dictionary<string, Action>>().FirstOrDefault();
FrameworkElement element = values.OfType<FrameworkElement>().FirstOrDefault();
if (permissions != null && element != null && !string.IsNullOrWhiteSpace(element.Name))
{
Action action;
if (permissions.TryGetValue(element.Name, out action))
{
return action.CanBeShown ? Visibility.Visible : Visibility.Collapsed;
}
else
{
return Visibility.Collapsed;
}
}
else
{
return DependencyProperty.UnsetValue;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
And use it in XAML like this:
<MenuItem x:Name="systemMenuItem" Header="System">
<MenuItem.Visibility>
<MultiBinding Converter="{StaticResource ElementPermissionToVisibilityConverter}">
<Binding Path="Permissions"/>
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</MenuItem.Visibility>
</MenuItem>
This will provide several advantages not least of which would be to re-evaluate the binding if the Permissions property changes.
Obviously this is quite verbose but you could wrap the binding into a MenuItem Style so you only need define it once.
Firstly, i want to tell thank you to everyone who helped me with their suggestions to find solution.
I have created SilentDictionary class and using it instead of Dictionary:
public class SilentDictionary<TKey, TValue> : IDictionary<TKey, TValue>
where TValue : class, new()
{
protected IDictionary<TKey, TValue> Dictionary;
public SilentDictionary()
{
Dictionary = new Dictionary<TKey, TValue>();
}
public TValue this[TKey key]
{
get
{
TValue item;
if (Dictionary.TryGetValue(key, out item))
return item;
else
{
item = new TValue();
Add(key, item);
return item;
}
}
set
{
Add(key, value);
}
}
public int Count
{
get
{
return Dictionary.Count;
}
}
public bool IsReadOnly
{
get
{
return Dictionary.IsReadOnly;
}
}
public ICollection<TKey> Keys
{
get
{
return Dictionary.Keys;
}
}
public ICollection<TValue> Values
{
get
{
return Dictionary.Values;
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Add(TKey key, TValue value)
{
TValue item;
if (Dictionary.TryGetValue(key, out item))
{
Type t = typeof(TValue);
var props = t.GetProperties();
foreach (var p in props)
p.SetValue(item, p.GetValue(value));
}
else
Dictionary.Add(key, value);
}
public void Clear()
{
Dictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Dictionary.Contains(item);
}
public bool ContainsKey(TKey key)
{
return Dictionary.ContainsKey(key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Dictionary.CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return Dictionary.GetEnumerator();
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Dictionary.Remove(item);
}
public bool Remove(TKey key)
{
return Dictionary.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
return Dictionary.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)Dictionary).GetEnumerator();
}
}
The main idea of this class is not throw exception when InitializeComponent is in progress. It gives me an opportunity to create an instance of dictionary before binding will try to get value from it. This is what is going on in the main window constructor:
public MainWindow()
{
ViewModelBase.Permissions = new SilentDictionary<string, Action>();
InitializeComponent();
}
Now binding have no DependencyProperty.UnsetValue. Binding have connection with my static SilentDictionary instance. Next step is to make static notify property change for dictionary:
public abstract class ViewModelBase
{
protected static SilentDictionary<string, Action> _permissions;
public static SilentDictionary<string, Action> Permissions
{
get { return _permissions; }
set
{
_permissions = value;
NotifyPropertyChanged();
}
}
public static event EventHandler PermissionsChanged;
protected static void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
EventHandler temp = Volatile.Read(ref PermissionsChanged);
PermissionsChanged?.Invoke(null, new EventArgs());
}
// other code
}
And, finally, i can bind to this dictionary as i planned at the beginning:
<MenuItem x:Name="systemMenuItem" Header="Система" Visibility="{Binding Permissions[systemMenuItem].CanBeShown, Converter={StaticResource BoolToVis}}">
And everything works well as expected.
Related
I have a ViewModel which works in UWP, all bindings are working except my custom dictionary and I don't know why. Nothing shows up.
I'm using FodyWeavers hence the shorthand notation. The custom dictionary returns the key with a * behind it if the key isn't found.
In the ViewModel
public static TranslationDictionary Translations { get; set; }
In the view
<TextBlock Text="{Binding Translations[Test_Translation]}" />
Custom Dictionary
public class TranslationDictionary : Dictionary<string, string>
{
public new void Add(string key, string value)
{
if (value == null)
{
return;
}
base.Add(key, value);
}
public new void Remove(string key)
{
if (!ContainsKey(key))
{
return;
}
base.Remove(key);
}
public new string this[string key]
{
get
{
string value;
return TryGetValue(key, out value) ? value : key + "*";
}
set
{
if (value == null)
{
Remove(key);
}
else
{
base[key] = value;
}
}
}
}
You could achieve this result by using x:Bind instead of Binding
An overview of the difference between x:Bind & Binding here
And so declaring a static class looking like that :
public static class DictionariesOperations
{
public static string GetValue(Dictionary<string, string> dict, string key)
{
return dict[key];
}
}
And then in your xaml :
<TextBlock Text="{x:Bind local:DictionariesOperations.GetValue(Translations, Test_Translation)}" />
Hope this help =)
I'm trying to implement some kind of Object Picker in WPF. So far I've created the Window with a DataGrid which ItemsSource is bound to an ObservableCollection. I also set AutoGenerateColumns to 'true' due to the fact that the Item to be picked can be any kind ob object.
The Objects inside the Collection are wrapped in a SelectionWrapper< T> which contains an IsSelected Property in order to select them.
class SelectionWrapper<T> : INotifyPropertyChanged
{
// Following Properties including PropertyChanged
public bool IsSelected { [...] }
public T Model { [...] }
}
I also added a CustomColumn to the DataGrid.Columns in order to bind the IsSelected Property like so
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding SourceView}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Selected" Binding="{Binding IsSelected}" />
</DataGrid.Columns>
</DataGrid>
The result I get with this solution is not very satisfying because there is just my defined Column 'Selected' and two GeneratedColumns 'IsSelected' and 'Model'.
Is there a way to change the target for the AutoGeneration to display all Properties of Model instead?
Also it is necessary to make the AutoGeneratedColumns ReadOnly because no one should edit the displayed Entries.
It is no option to turn off AutoGenerateColumns and add some more manual Columns like
<DataGridTextColumn Binding="{Binding Model.[SomeProperty]}"/>
because the Model can be of any kind of Object. Maybe there is a way to route the target for AutoGeneration to the Model Property?
Thanks in Advance
Edit
After accepting the Answer of #grek40
I came up with the following
First I created a general class of SelectionProperty which is inherited in SelectionProperty<T>. Here I implement the Interface ICustomTypeDescriptor which finally looked like:
public abstract class SelectionProperty : NotificationalViewModel, ICustomTypeDescriptor
{
bool isSelected = false;
public bool IsSelected
{
get { return this.isSelected; }
set
{
if (this.isSelected != value)
{
this.isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
object model = null;
public object Model
{
get { return this.model; }
set
{
if (this.model != value)
{
this.model = value;
this.OnPropertyChanged("Model");
}
}
}
public SelectionProperty(object model)
{
this.Model = model;
}
#region ICustomTypeDescriptor
[...]
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return TypeDescriptor.GetProperties(this.Model.GetType());
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
if (pd.DisplayName == "IsSelected")
return this;
return this.Model;
}
#endregion
Then I created a specialized ObservableCollection
class SelectionPropertyCollection<T> : ObservableCollection<T>, ITypedList
where T : SelectionProperty
{
public SelectionPropertyCollection(IEnumerable<T> collection) : base(collection)
{
}
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
return TypeDescriptor.GetProperties(typeof(T).GenericTypeArguments[0]);
}
public string GetListName(PropertyDescriptor[] listAccessors)
{
return null;
}
}
Well and the last thing is the ViewModel. The most significant lines are
class ObjectPickerViewModel<ObjectType> : BaseViewModel
{
public ICollectionView SourceView { get; set; }
SelectionPropertyCollection<SelectionProperty<ObjectType>> source = null;
public SelectionPropertyCollection<SelectionProperty<ObjectType>> Source
{
get { return this.source; }
set
{
if (this.source != value)
{
this.source = value;
this.OnPropertyChanged("Source");
}
}
}
// [...]
this.Source = new SelectionPropertyCollection<SelectionProperty<ObjectType>>(source.Select(x => new SelectionProperty<ObjectType>(x)));
this.SourceView = CollectionViewSource.GetDefaultView(this.Source);
}
The good thing here is, that I can still add more Columns in the XAML but also have all public Properties of the Wrapped Object!
Following the course of Binding DynamicObject to a DataGrid with automatic column generation?, the following should work to some extent but I'm not quite sure if I would ever use something like it in production:
Create a collection that implements ITypedList and IList. GetItemProperties from ITypedList will be used. Expect the list type to implement ICustomTypeDescriptor:
public class TypedList<T> : List<T>, ITypedList, IList
where T : ICustomTypeDescriptor
{
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
if (this.Any())
{
return this[0].GetProperties();
}
return new PropertyDescriptorCollection(new PropertyDescriptor[0]);
}
public string GetListName(PropertyDescriptor[] listAccessors)
{
return null;
}
}
Implement SelectionWrapper<T> as DynamicObject and implement ICustomTypeDescriptor (at least the PropertyDescriptorCollection GetProperties() method)
public class SelectionWrapper<T> : DynamicObject, INotifyPropertyChanged, ICustomTypeDescriptor
{
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set { SetProperty(ref _IsSelected, value); }
}
private T _Model;
public T Model
{
get { return _Model; }
set { SetProperty(ref _Model, value); }
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (Model != null)
{
var prop = typeof(T).GetProperty(binder.Name);
// indexer member will need parameters... not bothering with it
if (prop != null && prop.CanRead && prop.GetMethod != null && prop.GetMethod.GetParameters().Length == 0)
{
result = prop.GetValue(Model);
return true;
}
}
return base.TryGetMember(binder, out result);
}
public override IEnumerable<string> GetDynamicMemberNames()
{
// not returning the Model property here
return typeof(T).GetProperties().Select(x => x.Name).Concat(new[] { "IsSelected" });
}
public PropertyDescriptorCollection GetProperties()
{
var props = GetDynamicMemberNames();
return new PropertyDescriptorCollection(props.Select(x => new DynamicPropertyDescriptor(x, GetType(), typeof(T))).ToArray());
}
// some INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent([CallerMemberName]string prop = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(prop));
}
protected bool SetProperty<T2>(ref T2 store, T2 value, [CallerMemberName]string prop = null)
{
if (!object.Equals(store, value))
{
store = value;
RaisePropertyChangedEvent(prop);
return true;
}
return false;
}
// ... A long list of interface method implementations that just throw NotImplementedException for the example
}
The DynamicPropertyDescriptor hacks a way to access the properties of the wrapper and the wrapped object.
public class DynamicPropertyDescriptor : PropertyDescriptor
{
private Type ObjectType;
private PropertyInfo Property;
public DynamicPropertyDescriptor(string name, params Type[] objectType) : base(name, null)
{
ObjectType = objectType[0];
foreach (var t in objectType)
{
Property = t.GetProperty(name);
if (Property != null)
{
break;
}
}
}
public override object GetValue(object component)
{
var prop = component.GetType().GetProperty(Name);
if (prop != null)
{
return prop.GetValue(component);
}
DynamicObject obj = component as DynamicObject;
if (obj != null)
{
var binder = new MyGetMemberBinder(Name);
object value;
obj.TryGetMember(binder, out value);
return value;
}
return null;
}
public override void SetValue(object component, object value)
{
var prop = component.GetType().GetProperty(Name);
if (prop != null)
{
prop.SetValue(component, value);
}
DynamicObject obj = component as DynamicObject;
if (obj != null)
{
var binder = new MySetMemberBinder(Name);
obj.TrySetMember(binder, value);
}
}
public override Type PropertyType
{
get { return Property.PropertyType; }
}
public override bool IsReadOnly
{
get { return !Property.CanWrite; }
}
public override bool CanResetValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return typeof(object); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
public class MyGetMemberBinder : GetMemberBinder
{
public MyGetMemberBinder(string name)
: base(name, false)
{
}
public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
{
throw new NotImplementedException();
}
}
public class MySetMemberBinder : SetMemberBinder
{
public MySetMemberBinder(string name)
: base(name, false)
{
}
public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, DynamicMetaObject value, DynamicMetaObject errorSuggestion)
{
throw new NotImplementedException();
}
}
Now if you bind some TypedList<SelectionWrapper<ItemViewModel>> to your datagrid itemssource, it should populate the columns for IsSelected and for the properties of ItemViewModel.
Let me say it again - the whole approach is a bit hacky and my implementation here is far from being stable.
As I think about it some more, there is probably no real need for the whole DynamicObject stuff as long as the TypedList is used to define the columns and some DynamicPropertyDescriptor to access the properties from wrapper and model.
Is there a way to change the target for the AutoGeneration to display all Properties of Model instead?
Short answer: No.
Only a column per public property of the type T of the IEnumerable<T> that you set as the ItemsSource will be created.
You should consider setting the AutoGenerateColumns property to false and creating the columns programmatically instead of hard-coding them in your XAML markup.
In XAML (specifically on Universal Windows Platform) I can data bind to an indexed property using the property path notation for indexers.
e.g. Given a data source of type Dictionary<string,string> I can bind to an indexed property as follows:
<TextBlock Text="{Binding Dictionary[Property]}"/>
In the above, the TextBlock's DataContext is set to an object with a dictionary property, say the following:
public class DataObject
{
public Dictionary<string,string> Dictionary { get; } = new Dictionary<string,string> {["Property"]="Value"};
}
My question is, is it possible to bind to the indexed property without using the indexer notation, but instead using the syntax for standard property binding?
i.e.
<TextBlock Text="{Binding Dictionary.Property}"/>
From my initial tests this doesn't seem to work. Is there an easy way to make it work? I want to use a data source object with an indexed property (like the Dictionary in this case but could just be a simple object). I don't want to use dynamic objects.
There is no syntax that does what you want, I'm afraid. The syntax for standard property binding works for standard properties, e.g.,
<TextBlock Text="{Binding Dictionary.Count}" />
...will bind to the Count property of the dictionary (or whatever object). You need them brackets...
EDIT
If you really hate the brackets, the closest thing I can find would be to use a converter with a parameter. For example:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
IDictionary<string, string> dictionary = value as IDictionary<string, string>;
string dictionaryValue;
if (dictionary != null && dictionary.TryGetValue(parameter as string, out dictionaryValue))
{
return dictionaryValue;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
XAML:
<Page
x:Class="UWP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uwp="using:UWP">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<uwp:MyConverter x:Key="MyConverter" />
</Grid.Resources>
<Grid.DataContext>
<uwp:DataObject />
</Grid.DataContext>
<TextBlock
Text="{Binding Dictionary, ConverterParameter=Property1, Converter={StaticResource MyConverter}}" />
</Grid>
</Page>
...which is a roundabout way of ending up with something harder to read than the brackets.
A long time ago there was this ObservableDictionary class in Windows 8 app templates that does what you want. It's not a Dictionary<string, string>, but you can copy values from your Dictionary into the ObservableDictionary
Usage
Declare an ObservableDictionary property in your ViewModel, then bind to its indexer like usual properties:
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation.Collections;
namespace MyApp
{
public class ObservableDictionary : IObservableMap<string, object>
{
private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<string>
{
public ObservableDictionaryChangedEventArgs(CollectionChange change, string key)
{
this.CollectionChange = change;
this.Key = key;
}
public CollectionChange CollectionChange { get; private set; }
public string Key { get; private set; }
}
private Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public event MapChangedEventHandler<string, object> MapChanged;
private void InvokeMapChanged(CollectionChange change, string key)
{
var eventHandler = MapChanged;
if (eventHandler != null)
{
eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
}
}
public void Add(string key, object value)
{
this._dictionary.Add(key, value);
this.InvokeMapChanged(CollectionChange.ItemInserted, key);
}
public void Add(KeyValuePair<string, object> item)
{
this.Add(item.Key, item.Value);
}
public bool Remove(string key)
{
if (this._dictionary.Remove(key))
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
return true;
}
return false;
}
public bool Remove(KeyValuePair<string, object> item)
{
object currentValue;
if (this._dictionary.TryGetValue(item.Key, out currentValue) &&
Object.Equals(item.Value, currentValue) && this._dictionary.Remove(item.Key))
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
return true;
}
return false;
}
public virtual object this[string key]
{
get
{
return this._dictionary[key];
}
set
{
this._dictionary[key] = value;
this.InvokeMapChanged(CollectionChange.ItemChanged, key);
}
}
public void Clear()
{
var priorKeys = this._dictionary.Keys.ToArray();
this._dictionary.Clear();
foreach (var key in priorKeys)
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
}
}
public ICollection<string> Keys
{
get { return this._dictionary.Keys; }
}
public bool ContainsKey(string key)
{
return this._dictionary.ContainsKey(key);
}
public bool TryGetValue(string key, out object value)
{
return this._dictionary.TryGetValue(key, out value);
}
public ICollection<object> Values
{
get { return this._dictionary.Values; }
}
public bool Contains(KeyValuePair<string, object> item)
{
return this._dictionary.Contains(item);
}
public int Count
{
get { return this._dictionary.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return this._dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this._dictionary.GetEnumerator();
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
int arraySize = array.Length;
foreach (var pair in this._dictionary)
{
if (arrayIndex >= arraySize) break;
array[arrayIndex++] = pair;
}
}
}
}
class DictionaryXamlWrapper : DynamicObject, INotifyPropertyChanged
{
#region PropertyChangedFunctional
public void OnPropertyChanged([CallerMemberName] string aProp = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(aProp));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
Dictionary<string, object> members = new Dictionary<string, object>();
// установка свойства
public override bool TrySetMember(SetMemberBinder binder, object value)
{
members[binder.Name] = value;
OnPropertyChanged(binder.Name);
return true;
}
// получение свойства
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
if (members.ContainsKey(binder.Name))
{
result = members[binder.Name];
return true;
}
return false;
}
}
Of course, you can delete INotifyPropertyChanged functional if you do not need it.
And usage as you wanted
<TextBlock Text="{Binding DictionaryXamlWrapper.TestField}"/>
Edit: This is a simplified update of the original version of this post.
In WPF I implemented a UserControl (called 'NumericTextBox') which uses a *DependencyProperty 'Value' that is kept in sync with the Text property of a TextBox (xaml):
<TextBox.Text>
<Binding Path="Value"
Mode="TwoWay"
ValidatesOnDataErrors="True"
NotifyOnValidationError="True"
UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
For validation purposes I use the IDataErrorInfo interface (xaml.cs):
public partial class NumericTextbox : Textbox, IDataErrorInfo {
public double Value {
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double),
typeof(NumericTextBox),
new PropertyMetadata(default(double)));
public string this[string columnName]
{
// Never gets called!
get { /* Some validation rules here */ }
}
}
As stated in the source code, the get property actually never gets called, hence no validation happens. Do you see the reason for the problem?
Edit2: Based on ethicallogics' answer I restructered my code. The NumericTextBox now uses an underlying viewmodel class which provides a Dependency Property Value that is bound to the Text property of the TextBox which is declared by NumericTextBox. Additionally NumericTextBox uses the viewmodel as its datacontext. The viewmodel is now responsible for checking changes of the Value property. As the value restrictions of NumericTextBox are customizable (e.g. the Minimum can be adapted) it forwards these settings to the viewmodel object.
Do it like this rather than creating any Dependency Property . Validations are applied at ViewModel not in Control or View . Try it like this I hope this will help.
public class MyViewModel : INotifyPropertyChanged, IDataErrorInfo
{
public MyViewModel()
{
Value = 30;
}
private double _value;
[Range(1, 80, ErrorMessage = "out of range")]
public double Value
{
get
{
return _value;
}
set
{
_value = value;
ValidationMessageSetter("Value", value);
}
}
private void ValidationMessageSetter(string propertyName, object value)
{
Notify(propertyName);
string validationresult = ValidateProperty(propertyName, value);
if (!string.IsNullOrEmpty(validationresult) && !_dataErrors.ContainsKey(propertyName))
_dataErrors.Add(propertyName, validationresult);
else if (_dataErrors.ContainsKey(propertyName))
_dataErrors.Remove(propertyName);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void Notify(string str)
{
if(PropertyChanged!=null)
PropertyChanged(this,new PropertyChangedEventArgs(str));
}
private string ValidateProperty(string propertyName,object value)
{
var results = new List<ValidationResult>(2);
string error = string.Empty;
bool result = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null)
{
MemberName = propertyName
},
results);
if (!result && (value == null || ((value is int || value is long) && (int)value == 0) || (value is decimal && (decimal)value == 0)))
return null;
if (!result)
{
ValidationResult validationResult = results.First();
error = validationResult.ErrorMessage;
}
return error;
}
#region IDataErrorInfo Members
private Dictionary<string, string> _dataErrors = new Dictionary<string, string>();
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
if (_dataErrors.ContainsKey(columnName))
return _dataErrors[columnName];
else
return null;
}
}
#endregion
}
<TextBox Text="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
I hope this will help.
The IDataErrorInfo interface should be implemented on the object that is being bound to, not on the object that has the DependencyProperty.
In your example, if you want to get validation using this mechanism then your view model needs to do something like the below for the Value property:
public class ViewModel : IDataErrorInfo
{
public string this[string columnName]
{
// Never gets called!
get
{
if (columnName == "Value")
return GetValidationMessageForValueField();
return null;
}
}
}
I'm guessing that what you actually want to do is to validate when someone enters a non-numeric value in the TextBox..? If this is the case the you probably want to take a different approach than using IDataErrorInfo
I'm trying to use following implementation of the ObservableDictionary: ObservableDictionary (C#).
When I'm using following code while binding the dictionary to a DataGrid:
ObserveableDictionary<string,string> dd=new ObserveableDictionary<string,string>();
....
dd["aa"]="bb";
....
dd["aa"]="cc";
at dd["aa"]="cc"; I'm getting following exception
Index was out of range. Must be non-negative and less than the size of the
collection. Parameter name: index
This exception is thrown in CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem) in the following method:
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}
The index param seems to correspond to KeyValuePair<TKey, TValue> oldItem.
How can KeyValuePair<TKey, TValue> be out of range, and what should I do to make this work?
here's what I did in the end:
[Serializable]
public class ObservableKeyValuePair<TKey,TValue>:INotifyPropertyChanged
{
#region properties
private TKey key;
private TValue value;
public TKey Key
{
get { return key; }
set
{
key = value;
OnPropertyChanged("Key");
}
}
public TValue Value
{
get { return value; }
set
{
this.value = value;
OnPropertyChanged("Value");
}
}
#endregion
#region INotifyPropertyChanged Members
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(name));
}
#endregion
}
[Serializable]
public class ObservableDictionary<TKey,TValue>:ObservableCollection<ObservableKeyValuePair<TKey,TValue>>, IDictionary<TKey,TValue>
{
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value)
{
if (ContainsKey(key))
{
throw new ArgumentException("The dictionary already contains the key");
}
base.Add(new ObservableKeyValuePair<TKey, TValue>() {Key = key, Value = value});
}
public bool ContainsKey(TKey key)
{
//var m=base.FirstOrDefault((i) => i.Key == key);
var r = ThisAsCollection().FirstOrDefault((i) => Equals(key, i.Key));
return !Equals(default(ObservableKeyValuePair<TKey, TValue>), r);
}
bool Equals<TKey>(TKey a, TKey b)
{
return EqualityComparer<TKey>.Default.Equals(a, b);
}
private ObservableCollection<ObservableKeyValuePair<TKey, TValue>> ThisAsCollection()
{
return this;
}
public ICollection<TKey> Keys
{
get { return (from i in ThisAsCollection() select i.Key).ToList(); }
}
public bool Remove(TKey key)
{
var remove = ThisAsCollection().Where(pair => Equals(key, pair.Key)).ToList();
foreach (var pair in remove)
{
ThisAsCollection().Remove(pair);
}
return remove.Count > 0;
}
public bool TryGetValue(TKey key, out TValue value)
{
value = default(TValue);
var r = GetKvpByTheKey(key);
if (!Equals(r, default(ObservableKeyValuePair<TKey, TValue>)))
{
return false;
}
value = r.Value;
return true;
}
private ObservableKeyValuePair<TKey, TValue> GetKvpByTheKey(TKey key)
{
return ThisAsCollection().FirstOrDefault((i) => i.Key.Equals(key));
}
public ICollection<TValue> Values
{
get { return (from i in ThisAsCollection() select i.Value).ToList(); }
}
public TValue this[TKey key]
{
get
{
TValue result;
if (!TryGetValue(key,out result))
{
throw new ArgumentException("Key not found");
}
return result;
}
set
{
if (ContainsKey(key))
{
GetKvpByTheKey(key).Value = value;
}
else
{
Add(key, value);
}
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
var r = GetKvpByTheKey(item.Key);
if (Equals(r, default(ObservableKeyValuePair<TKey, TValue>)))
{
return false;
}
return Equals(r.Value, item.Value);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
var r = GetKvpByTheKey(item.Key);
if (Equals(r, default(ObservableKeyValuePair<TKey, TValue>)))
{
return false;
}
if (!Equals(r.Value,item.Value))
{
return false ;
}
return ThisAsCollection().Remove(r);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public new IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return (from i in ThisAsCollection() select new KeyValuePair<TKey, TValue>(i.Key, i.Value)).ToList().GetEnumerator();
}
#endregion
}
This implementation looks and feels like dictionary to the user and like ObservableCollection to WPF
Similar data structure, to bind to Dictionary type collection
http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/
It provides a new Data structure ObservableDictionary and fires PropertyChanged in case of any change to underlying Dictionary.
I ended up writing a class to hold the Key-Value pair and using a collection of that class. I'm using Caliburn Micro which is where the BindableCollection comes from, but an ObservableCollection should work the same way. I use the MVVM pattern.
the viewmodel
using Caliburn.Micro;
private BindableCollection<KeyValuePair> _items;
public BindableCollection<KeyValuePair> Items
{
get { return _items; }
set
{
if (_items != value)
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
}
the custom keyValuePair
public class KeyValuePair
{
public string Key { get; set; }
public string Value { get; set; }
}
and in the view
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding Key, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Column="1"
Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It bothers me that I can't just bind to a dictionary, but I find this much easier and cleaner than writing an ObservableDictionary from scratch and worrying about the change notifications.
ObservableDictionary was added to the .Net Framework at version 4.5:-
https://zamjad.wordpress.com/2012/10/12/observabledictionary-in-net-4-5/
Here is a link to the latest source code:-
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Annotations/ObservableDictionary.cs
I first created a class called "ConcurrentObservableCollection" in which i extended the ObservableCollection functions.
public class ConcurrentObservableCollection<T> : ObservableCollection<T>
{
private readonly object _lock = new object();
public new void Add(T value)
{
lock (_lock)
{
base.Add(value);
}
}
public List<T> ToList()
{
lock (_lock)
{
var copyList = new List<T>();
copyList.AddRange(base.Items);
return copyList;
}
}
public new IEnumerator<T> GetEnumerator()
{
lock (_lock)
{
return base.GetEnumerator();
}
}
public new bool Remove(T item)
{
lock (_lock)
{
return base.Remove(item);
}
}
public new void Move(int oldIndex, int newIndex)
{
lock (_lock)
{
base.Move(oldIndex, newIndex);
}
}
public new bool Contains(T item)
{
lock (_lock)
{
return base.Contains(item);
}
}
public new void Insert(int index, T item)
{
lock (_lock)
{
base.Insert(index, item);
}
}
public new int Count()
{
lock (_lock)
{
return base.Count;
}
}
public new void Clear()
{
lock (_lock)
{
base.Clear();
}
}
public new T this[int index]
{
get
{
lock (_lock)
{
return base[index];
}
}
}
}
Then i replaced the exisitng "ObservabeCollection" with my new "ConcurrentObservableCollection"
Even I am using the ObservableDictionary of github, I also faced this exception. I had declared the dictionary variable at class level later I tried to create a new instance in the method where it was getting accessed.
OldCode which gave exception:
public class CName
{
ObservableDictionary<string, string> _classVariableDictionary = new ObservableDictionary<string, string>();
}
NewCode which worked:
public void MethodName()
{
ObservableDictionary<string, string> _localVariableDictionary = new ObservableDictionary<string, string>();
}