Is it possible to set the value behind a two-way binding directly, without knowing the bound property?
I have an attached property that is bound to a property like this:
<Element my:Utils.MyProperty="{Binding Something}" />
Now I want to change the value that is effectively stored in Something from the perspective of the attached property. So I cannot access the bound property directly, but only have references to the DependencyObject (i.e. the Element instance) and the DependencyProperty object itself.
The problem when simply setting it via DependencyObject.SetValue is that this effectively removes the binding, but I want to change the underlying bound property.
Using BindingOperations I can get both the Binding and the BindingExpression. Now is there a way to access the property behind it and change its value?
Okay, I have solved this now myself using a few reflection tricks on the binding expression.
I basically look at the binding path and the responding data item and try to resolve the binding myself. This will probably fail for more complex binding paths but for a simple property name as in my example above, this should work fine.
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(dependencyObj, dependencyProperty);
if (bindingExpression != null)
{
PropertyInfo property = bindingExpression.DataItem.GetType().GetProperty(bindingExpression.ParentBinding.Path.Path);
if (property != null)
property.SetValue(bindingExpression.DataItem, newValue, null);
}
Try setting a default value in the PropertyMetadata
You can find more information on MSDN -
http://msdn.microsoft.com/en-us/library/system.windows.propertymetadata.aspx
Here is an example :
public Boolean State
{
get { return (Boolean)this.GetValue(StateProperty); }
set { this.SetValue(StateProperty, value); }
}
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(
"State", typeof(Boolean), typeof(MyStateControl),new PropertyMetadata(myDefaultValue));
The problem when simply setting it via DependencyObject.SetValue is
that this effectively removes the binding, but I want to change the
underlying bound property.
This is true if the Binding.Mode is set to OneWay. If it is set to TwoWay, using DependencyObject.SetValue won't remove its binding.
This is a quote from Pro WPF 4.5 in C# (page 232):
Removing a binding: If you want to remove a binding so that you can
set a property in the usual way, you need the help of the
ClearBinding() or ClearAllBindings() method. It isn’t enough to simply
apply a new value to the property. If you’re using a two-way binding,
the value you set is propagated to the linked object, and both
properties remain synchronized.
So, to be able to change (and propagate) the my:Utils.MyProperty with SetValue without removing its binding:
<Element my:Utils.MyProperty="{Binding Something, Mode=TwoWay}" />
You can pass value via a binding on a dummy object.
public static void SetValue(BindingExpression exp, object value)
{
if (exp == null)
throw new ValueCannotBeNullException(() => exp);
Binding dummyBinding = new Binding(exp.ParentBinding.Path.Path);
dummyBinding.Mode = BindingMode.OneWayToSource;
dummyBinding.Source = exp.DataItem;
SetValue(dummyBinding, value);
}
public static void SetValue(Binding binding, object value)
{
BindingDummyObject o = new BindingDummyObject();
BindingOperations.SetBinding(o, BindingDummyObject.ValueProperty, binding);
o.Value = value;
BindingOperations.ClearBinding(o, BindingDummyObject.ValueProperty);
}
This is my dummy object
internal class BindingDummyObject : DependencyObject
{
public object Value
{
get
{
return (object)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(BindingDummyObject));
}
I had a similar issue when trying to implement a menu that was backed by an enumeration. I wanted to be able to set the underlying property (which was an enum) to the value associated with the menu item.
In my example, I attached two properties to an MenuItem:
public static readonly DependencyProperty EnumTargetProperty = DependencyProperty.RegisterAttached(
"EnumTarget",
typeof(object),
typeof(MenuItem),
new PropertyMetadata(null, EnumTargetChangedCallback)
);
public static readonly DependencyProperty EnumValueProperty = DependencyProperty.RegisterAttached(
"EnumValue",
typeof(object),
typeof(MenuItem),
new PropertyMetadata(null, EnumValueChangedCallback)
);
And the markup looks like this:
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="local:EnumMenuItem.EnumValue" Value="{Binding EnumMember}"/>
<Setter Property="local:EnumMenuItem.EnumTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=local:MainWindow}, Path=DataContext.Settings.AutoUpdateModel.Ring}"/>
<Setter Property="Header" Value="{Binding DisplayName}"/>
<Setter Property="ToolTip" Value="{Binding ToolTip}"/>
</Style>
</MenuItem.ItemContainerStyle>
The item source for the parent menu item was bound to an MarkupExtension implementation that provided values for each member in the enum.
Now, when the menu item was checked, I used this code to set the value of the property without removing the binding.
menuItem.Checked += (sender, args) =>
{
var checkedMenuItem = (MenuItem)sender;
var targetEnum = checkedMenuItem.GetValue(EnumTargetProperty);
var menuItemValue = checkedMenuItem.GetValue(EnumValueProperty);
if (targetEnum != null && menuItemValue != null)
{
var bindingExpression = BindingOperations.GetBindingExpression(d, EnumTargetProperty);
if (bindingExpression != null)
{
var enumTargetObject = bindingExpression.ResolvedSource;
if (enumTargetObject != null)
{
var propertyName = bindingExpression.ResolvedSourcePropertyName;
if (!string.IsNullOrEmpty(propertyName))
{
var propInfo = enumTargetObject.GetType().GetProperty(propertyName);
if (propInfo != null)
{
propInfo.SetValue(enumTargetObject, menuItemValue);
}
}
}
}
}
};
This seems to work fine for my scenario with a complex path.
I hope this helps out!
Here is how I've done for a TextBlock:
BindingExpression bindingExpression = textBlock.GetBindingExpression(TextBlock.TextProperty);
string propertyName = bindingExpression.ResolvedSourcePropertyName;
PropertyInfo propertyInfo;
Type type = bindingExpression.DataItem.GetType();
object[] indices = null;
if (propertyName.StartsWith("[") && propertyName.EndsWith("]"))
{
// Indexed property
propertyInfo = type.GetProperty("Item");
indices = new object[] { propertyName.Trim('[', ']') };
}
else
propertyInfo = type.GetProperty(propertyName);
if (propertyInfo.PropertyType == typeof(string))
{
propertyInfo.SetValue(bindingExpression.DataItem, text, indices);
// To update the UI.
bindingExpression.UpdateTarget();
}
Related
I'm trying to bind 2 ways
In my ViewModel, I have
private Temporary _selectedCompany;
public Temporary SelectedCompany
{
get
{
return this._selectedCompany;
}
set
{
if (this._selectedCompany == value || value == null)
return;
this._selectedCompany = value;
this.SelectedCompany.CompanyName = "TestName";
base.OnPropertyChanged("SelectedCompany");
}
}
Temporary is actually a class similar to a class you'd do for a CompanyAddress (name, country, phone etc) and was created by EntityFramework.
In the corresponding View, the XAML is
<local:CompanyDetail CompanyName="{Binding SelectedCompany.CompanyName}"/>
In code behind of the custom control
// Dependency Property
public static readonly DependencyProperty CompanyNameProperty =
DependencyProperty.Register("CompanyName", typeof(string),
typeof(CompanyDetail), null);
// .NET Property wrapper
public string CompanyName
{
get { return (string)GetValue(CompanyNameProperty); }
set { SetValue(CompanyNameProperty, value); }
}
There is nothing in the ViewModel. There is the following XAML
<TextBox Text="{Binding CompanyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=UserControl, AncestorLevel=1, Mode=FindAncestor}}" />
So, when the controls are loaded and shown on screen, I see the value "TestName" in the TextBox but, if I change the value by typing and then click an OK button I can see the value has not been updated.
I'm assuming it has nothing to do with INotifyPropertyChanged because I think it's all a reference type any way?
What am I doing wrong?
The CompanyName binding must be made two-way, either by setting it explicitly
<local:CompanyDetail
CompanyName="{Binding SelectedCompany.CompanyName, Mode=TwoWay}"/>
or by declaring the dependency property to bind two-way by default
public static readonly DependencyProperty CompanyNameProperty =
DependencyProperty.Register(
"CompanyName", typeof(string), typeof(CompanyDetail),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
I have a UserControl that contains a ListBox and I want to track the SelectedItems of that listbox.
The UserControl has a DP "SelectedItemsList" that is defined like this
public static DependencyProperty SelectedItemsListProperty = DependencyProperty.Register(
"SelectedItemsList",
typeof (IList),
typeof (MyListControl),
new FrameworkPropertyMetadata(null,
OnSelectedItemsChanged));
In the listbox' Item "SelectionChanged" event, I want to save the selected items to the DP. This is triggered whenever I change the selection in the listbox.
private void OnItemSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItemsList = this.myListBox.SelectedItems;
}
In my view that contains the "MyListControl" I create a binding to my viewmodel that want to use the selected items.
<controls:MyListControl
Source="{Binding SomeItemsList, UpdateSourceTrigger=PropertyChanged}"
SelectedItemsList="{Binding SelectedItems, UpdateSourceTrigger=PropertyChanged}"/>
My problem is, that the DP SelectedItemsList never gets updated. The PropertyChangeCallback "OnSelectedItemsChanged" of the DP is only triggered when I initially load the lists content. The value of the SelectedItemsList is always null.
I am aware that this question is similar to Dependency property callback does not work, but the answers posted there do not solve my problem.
What am I missing here?
Thanks,
Edit (2015-09-10):
Thank you all for your comments. I found a solution that fits my needs:
First of all I created a custom listbox control that provided the list of selected items in a dependency property (very similar to Select multiple items from a DataGrid in an MVVM WPF project).
public class CustomListBox : ListBox
{
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof (IList),
typeof (CustomListBox),
new PropertyMetadata(null));
public CustomListBox()
{
SelectionChanged += OnSelectionChanged;
}
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList= new ArrayList(this.SelectedItems);
}
}
I am not happy yet with the "new ArrayList"-part, but if in my viewmodel's property setter I want to check for equality, SelectedItemsList can not be a reference of SelectedItems. The previous and the new value would always be the same.
Then I reduced the item selection parts of my UserControl "MyListControl" simply to the dependency property itself:
public static DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof (IList),
typeof (MyListControl),
new FrameworkPropertyMetadata(null));
public IList SelectedItems
{
get
{
return (IList)GetValue(SelectedItemsProperty);
}
set
{
SetValue(SelectedItemsProperty, value);
}
}
and modified the xaml of the MyListControl:
<controls:CustomListBox
SelectionMode="Extended"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyListControl}},
Path=Source, UpdateSourceTrigger=PropertyChanged}"
SelectedItemsList="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyListControl}},
Path=SelectedItems, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
>
The property in my ViewModel looks like
public IList SelectedObjects
{
get { return _selectedObjects; }
set { if (this._selectedObjects != value)
{
this._selectedObjects = value;
OnPropertyChanged(SelectedObjectsProperty);
}
}
}
It was important that the type of this property is IList, otherwise the value in the setter would always be null.
And in the view's xaml
<controls:MyListControl
Source="{Binding CurrentImageList, UpdateSourceTrigger=PropertyChanged}"
SelectedItems="{Binding SelectedObjects, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
I just had the same problem today, unfortunately, when you are assigning to SelectedItemsList a value, WPF seems to unbind it. To fix it, I update the value in the binded item. I know that it is not the best solution in the world but for me it works.
In this case the code would looked like this:
private void OnItemSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SetPropertyValue(
this.GetBindingExpression(SelectedItemsListProperty),
this.myListBox.SelectedItems);
}
private void SetPropertyValue(BindingExpression bindingExpression, object value)
{
string path = bindingExpression.ParentBinding.Path.Path;
var properties = new Queue<string>(
path.Split(
new[]
{
'.'
}).ToList());
this.SetPropertyValue(bindingExpression.DataItem, bindingExpression.DataItem.GetType(), properties, value);
}
private void SetPropertyValue(object destination, Type type, Queue<string> properties, object value)
{
PropertyInfo property = type.GetProperty(properties.Dequeue());
if (property != null && destination != null)
{
if (properties.Count > 0)
{
this.SetPropertyValue(property.GetValue(destination), property.PropertyType, properties, value);
}
else
{
property.SetValue(destination, value);
}
}
}
You need to bind your Listbox' SelectedItems to the DP SelectedItemsList to propagate the user selection to the DP. The binding you already have will then pass the changes on to the viewmodel, but I think you will need a binding mode 'twoway' instead of UpdateSourceTrigger.
And don't use the PropertyChangeCallback in your DP: Changing the SelectedItemsList if the SelectedItemsListProperty has changed makes no sense. (Usually the former is a wrapper property of the latter.)
I have a custom UserControl that contains a grid ...I wish to set the ItemsSource property of that grid by xaml code of of a data template in a resource dictionary...
then I have used dependency property... this is my implementation...
public partial class MyControlGrid : UserControl
{
// Dependency Property
public static readonly DependencyProperty MyItemSourceProperty =
DependencyProperty.Register("MyItemSource", typeof(ICollectionView),
typeof(MyControlGrid), new FrameworkPropertyMetadata(null, OnMyItemSourcePropertyChanged));
IDictionary<string, string> _columns = new Dictionary<string, string>();
private static void OnMyItemSourcePropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
// When the color changes, set the icon color PlayButton
MyControlGrid muc = (MyControlGrid)obj;
ICollectionView value = (ICollectionView)args.NewValue;
if (value != null)
{
muc.MyGridControl.ItemsSource = value;
}
}
public ICollectionView MyItemSource
{
get
{
return (ICollectionView)GetValue(MyItemSourceProperty);
}
set
{
SetValue(MyItemSourceProperty, value);
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value));
// Old value irrelevant.
}
}
public MyControlGrid()
{
InitializeComponent();
}
}
this is the user control xaml code
<UserControl x:Class="GUI.Design.Templates.MyControlGrid"
Name="MyListControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfTkit="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:Templates="clr-namespace:Emule.GUI.Design.Templates">
<StackPanel>
<WpfTkit:DataGrid ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Templates:MyControlGrid}}, Path=MyItemSource}"
x:Name="MyGridControl"
<StackPanel>
this is the binding path expression I use
<basic:MyControlGrid MyItemSource="{Binding MyDataContextVisibleCollection}"/>
this dont work and wpf output window dont show me any errors
note that, naturally, if I bind this directly in the user controls work fine
<WpfTkit:DataGrid ItemsSource="{Binding MyDataContextVisibleCollection}"
Waths I wrong?
thanks
p.s. sorry for my english
this
answer show me the way
use of PropertyChangedCallback work fine with my code:
public static readonly DependencyProperty MyItemSourceProperty =
DependencyProperty.Register("MyItemSource", typeof(IEnumerable),
typeof(MyControlGrid), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MyControlGrid.OnItemsSourceChanged)));
alternatively I have to remove comment on OnTargetPowerChanged and fire the property changed event
set
{
SetValue(MyItemSourceProperty, value);
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value));
// Old value irrelevant.
}
correct with
public ICollectionView MyItemSource
{
get
{
return (ICollectionView)GetValue(MyItemSourceProperty);
}
set
{
SetValue(MyItemSourceProperty, value);
OnItemsSourceChanged(this, new DependencyPropertyChangedEventArgs(MyItemSourceProperty, value, value));
}
}
I am creating a ToggleSwitchItem user control, which contains a ToggleSwitch and a TextBlock. I have defined a dependency property called IsChecked which I just want to use to expose the IsChecked property of the private ToggleSwitch child.
But the data binding doesn't work... It just stays at the default value when loaded.
What am I missing?
Code:
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register(
"IsChecked",
typeof(bool),
typeof(ToggleSwitchItem),
new PropertyMetadata(new PropertyChangedCallback
(OnIsCheckedChanged)));
public bool IsChecked
{
get
{
return (bool)GetValue(IsCheckedProperty);
}
set
{
SetValue(IsCheckedProperty, value);
}
}
private static void OnIsCheckedChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ToggleSwitchItem item = (ToggleSwitchItem)d;
bool newValue = (bool)e.NewValue;
item.m_switch.IsChecked = newValue;
}
for the data binding, I'm using to following:
<phone:PhoneApplicationPage.Resources>
<myApp:SharedPreferences x:Key="appSettings"/>
</phone:PhoneApplicationPage.Resources>
IsChecked="{Binding Source={StaticResource appSettings},
Path=SomeProperty, Mode=TwoWay}"
The SharedPreferences class is working fine, as it works without issue when bound to a plain vanilla ToggleSwitch's IsChecked property exactly as per above.
Thanks!
SOLUTION (with help from Anthony):
I bind my child toggle switch to my user control in the user control's constructor like so:
Binding binding = new Binding();
binding.Source = this;
binding.Path = new PropertyPath("IsChecked");
binding.Mode = BindingMode.TwoWay;
m_switch.SetBinding(ToggleSwitch.IsCheckedProperty, binding);
And I remove the callback as I no longer need it:
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register(
"IsChecked",
typeof(bool),
typeof(ToggleSwitchItem),
null);
public bool IsChecked
{
get
{
return (bool)GetValue(IsCheckedProperty);
}
set
{
SetValue(IsCheckedProperty, value);
}
}
I can't quite see what is actually wrong with the code you've show so far, except that you haven't show how the user toggling the switch would actually cause the IsChecked property to change.
Have you try using binding inside the UserControl:
<ToggleButton IsChecked="{Binding Parent.IsChecked, ElementName=LayoutRoot, Mode=TwoWay}" />
You do not need the OnPropertyChanged callback with this approach.
Check the DataContext of your control.Which means 2 things : All instances of your control must have right DataContext to work -ok-, and also you should not 'break' this DataContext when you define the control (at the Class level). If, when you define your control, you set the DataContext to 'this' / Me in code or to 'Self' in xaml, it nows refer only to itself and forget about the DataContext in which it is when you instanciate it in your application -- Binding fails.
If you have to refer to your control's properties within your control Xaml, use a binding with findAncestor / AncestorType = ToggleSwitchItem Or name your control in Xaml and bind with its ElementName.
Maybe this could help
public bool IsChecked
{
get { return GetValue(IsCheckedProperty) is bool ? (bool) GetValue(IsCheckedProperty) : false; }
set
{
SetValue(IsCheckedProperty, value);
}
}
I want to change the foreground color of cells that hold negative numbers, but I don't know how to specify the DataTrigger that would let me. I'm using something like this:
<Style x:Key="NumberCellStyle" BasedOn="{StaticResource CellStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResourceExtension SignConverter}}" Value="-1">
<Setter Property="TextBlock.Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
But in the SignConverter converter I get the whole ViewModel instead of the numeric value I want to convert. I want this to work across the app, without me needing to specify the correct Path for each binding.
Thank you very much!
Better way, write a custom column.
The code follows for anyone that's in the same situation:
public class DataGridDecimalColumn : DataGridTextColumn
{
Binding foregroundBinding;
DecimalBrushConverter brushConverter = new DecimalBrushConverter {
NegativeBrush = Brushes.Red,
PositiveBrush = Brushes.Black,
ZeroBrush = Brushes.Black,
};
protected override FrameworkElement
GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem) as TextBlock;
element.SetBinding(TextBlock.ForegroundProperty, GetForegroundBinding());
return element;
}
Binding
GetForegroundBinding()
{
if(foregroundBinding == null) {
var binding = (Binding)Binding;
foregroundBinding = new Binding {
Path = binding.Path,
Converter = BrushConverter,
};
}
return foregroundBinding;
}
public DecimalBrushConverter
BrushConverter
{
get { return brushConverter; }
set { brushConverter = value; }
}
}
DecimalBrushConverter simple takes a decimal? and converts it to one of the specified brushes depending on its value.
What control are you applying the style too? It sounds like whatever you are applying it to doesn't have any specific bindings set for itself, so it is just inheriting its parents' value, which ends up being your ViewModel instance.
Update: Based on the comment, I think that you need to specify a Path in the Binding expression of the style. Since no path is specified, it just uses the current DataContext, which ends up being the entire ViewModel instance.
OK, I didn't find a way to solve my original problem, but I'll work around it by using a DataGridTemplateColumn with templates that correctly set the Foreground color depending on the value that's bind to them.
How would you get this code to read the IsSelected property of the DataGrid itself? I've tried the following code but can't work out how to get the bool value into the ConverterParameter, where the DecimalBrushConverter reads the parameter and provides the SelectedBrush if isSelected==true.
public class DataGridDecimalColumn : DataGridTextColumn
{
private readonly DecimalBrushConverter _brushConverter = new DecimalBrushConverter
{
NegativeBrush = Brushes.Red,
PositiveBrush = Brushes.Black,
ZeroBrush = Brushes.Black,
SelectedBrush = Brushes.White
};
private Binding _foregroundBinding;
private DecimalBrushConverter BrushConverter
{
get { return _brushConverter; }
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem) as TextBlock;
if (element != null)
element.SetBinding(TextBlock.ForegroundProperty, GetForegroundBinding());
return element;
}
private Binding GetForegroundBinding()
{
if (_foregroundBinding == null)
{
var binding = (Binding) Binding;
var bindingToRow = new Binding
{
Path = new PropertyPath("IsSelected"),
RelativeSource=new RelativeSource(RelativeSourceMode.FindAncestor,typeof(DataGridRow),1)
};
_foregroundBinding = new Binding
{
Path = binding.Path,
Converter = BrushConverter,
ConverterParameter = bindingToRow
};
}
return _foregroundBinding;
}
}