WPF dynamic binding to property - wpf

I have an ItemsControl that should display the values of some properties of an object.
The ItemsSource of the ItemsControl is an object with two properties: Instance and PropertyName.
What I am trying to do is displaying all the property values of the Instance object, but I do not find a way to set the Path of the binding to the PropertyName value:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Source=??{Binding Path=Instance}??, Path=??PropertyName??, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the question marks are the points where I don't know how to create the binding.
I initially tried with a MultiValueConverter:
<TextBlock Grid.Column="1" Text="{Binding}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource getPropertyValue}">
<Binding Path="Instance" Mode="OneWay"/>
<Binding Path="PropertyName" Mode="OneWay"/>
</MultiBinding>
</TextBlock.DataContext>
</TextBlock>
The MultiValueConverter uses Reflection to look through the Instance and returns the value of the property.
But if the property value changes, this change is not notified and the displayed value remains unchanged.
I am looking for a way to do it with XAML only, if possible, if not I will have to write a wrapper class to for the items of the ItemsSource collection, and I know how to do it, but, since it will be a recurring task in my project, it will be quite expensive.
Edit:
For those who asked, InstanceProperties is a property on the ViewModel which exposes a collection of objects like this:
public class InstanceProperty : INotifyPropertyChanged
{
//[.... INotifyPropertyChanged implementation ....]
public INotifyPropertyChanged Instance { get; set; }
public string PropertyName { get; set; }
}
Obviously the two properties notify theirs value is changing through INotifyPropertyChanged, I don't include the OnPropertyChanged event handling for simplicity.
The collection is populated with a limited set of properties which I must present to the user, and I can't use a PropertyGrid because I need to filter the properties that I have to show, and these properties must be presented in a graphically richer way.
Thanks

Ok, thanks to #GazTheDestroyer comment:
#GazTheDestroyer wrote: I cannot think of any way to dynamically iterate and bind to an arbitrary object's properties in XAML only. You need to write a VM or behaviour to do this so you can watch for change notifications, but do it in a generic way using reflection you can just reuse it throughout your project
I found a solution: editing the ViewModel class InstanceProperty like this
added a PropertyValue property
listen to PropertyChanged event on Instance and when the PropertyName value changed is fired, raise PropertyChanged on PropertyValue
When Instance or PropertyName changes, save a reference to Reflection's PropertyInfo that will be used by PropertyValue to read the value
here is the new, complete, ViewModel class:
public class InstanceProperty : INotifyPropertyChanged
{
#region Properties and events
public event PropertyChangedEventHandler PropertyChanged;
private INotifyPropertyChanged FInstance = null;
public INotifyPropertyChanged Instance
{
get { return this.FInstance; }
set
{
if (this.FInstance != null) this.FInstance.PropertyChanged -= Instance_PropertyChanged;
this.FInstance = value;
if (this.FInstance != null) this.FInstance.PropertyChanged += Instance_PropertyChanged;
this.CheckProperty();
}
}
private string FPropertyName = null;
public string PropertyName
{
get { return this.FPropertyName; }
set
{
this.FPropertyName = value;
this.CheckProperty();
}
}
private System.Reflection.PropertyInfo Property = null;
public object PropertyValue
{
get { return this.Property?.GetValue(this.Instance, null); }
}
#endregion
#region Private methods
private void CheckProperty()
{
if (this.Instance == null || string.IsNullOrEmpty(this.PropertyName))
{
this.Property = null;
}
else
{
this.Property = this.Instance.GetType().GetProperty(this.PropertyName);
}
this.RaisePropertyChanged(nameof(PropertyValue));
}
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.PropertyName)
{
this.RaisePropertyChanged(nameof(PropertyValue));
}
}
private void RaisePropertyChanged(string propertyname)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion
}
and here is the XAML:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=PropertyValue, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Related

WPF - Dependency Property of Custom Control lost Binding at 2 way mode

I have this Custom Control
XAML:
<UserControl x:Class="WpfApplication1.UC"
...
x:Name="uc">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Text="{Binding Test, ElementName=uc}" Width="50" HorizontalAlignment="Left"/>
</StackPanel>
</UserControl>
C#
public partial class UC : UserControl
{
public static readonly DependencyProperty TestProperty;
public string Test
{
get
{
return (string)GetValue(TestProperty);
}
set
{
SetValue(TestProperty, value);
}
}
static UC()
{
TestProperty = DependencyProperty.Register("Test",typeof(string),
typeof(UC), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public UC()
{
InitializeComponent();
}
}
And this is how i used that custom control:
<DockPanel>
<ItemsControl ItemsSource="{Binding Path=DataList}"
DockPanel.Dock="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" CommandParameter="{Binding}" Click="Button_Click"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
</DockPanel>
--
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<string> _dataList;
public ObservableCollection<string> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
private string _selectedString;
public string SelectedString
{
get { return _selectedString; }
set
{
_selectedString = value;
OnPropertyChanged("SelectedString");
}
}
public MainWindow()
{
InitializeComponent();
this.DataList = new ObservableCollection<string>();
this.DataList.Add("1111");
this.DataList.Add("2222");
this.DataList.Add("3333");
this.DataList.Add("4444");
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedString = (sender as Button).CommandParameter.ToString();
}
}
If I do not change text of UC, everything is ok. When I click each button in the left panel, button's content is displayed on UC.
But when I change text of UC (ex: to 9999), Test property lost binding. When I click each button in the left panel, text of UC is the same that was changed (9999). In debug I see that SelectedString is changed by each button click but UC's text is not.
I can 'fix' this problem by using this <TextBox Text="{Binding Test, ElementName=uc, Mode=OneWay}" Width="50" HorizontalAlignment="Left"/> in the UC.
But I just want to understand the problem, can someone help me to explain it please.
Setting the value of the target of a OneWay binding clears the binding. The binding <TextBox Text="{Binding Test, ElementName=uc}" is two way, and when the text changes it updates the Test property as well. But the Test property is the Target of a OneWay binding, and that binding is cleared.
Your 'fix' works because as a OneWay binding, it never updates Test and the binding is never cleared. Depending on what you want, you could also change the UC binding to <local:UC Test="{Binding SelectedString, Mode=TwoWay}"/> Two Way bindings are not cleared when the source or target is updated through another method.
The issue is with below line
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
The mode is set as oneway for SelectString binding so text will be updated when the value from code base changes. To change either the source property or the target property to automatically update the binding source as TwoWay.
<local:UC Test="{Binding SelectedString, Mode=TwoWay}"/>

update GUI for SelectedeRow values of wpf datagrid

I have a wpf (.Net 4.5) datagrid. I am using the MVVM pattern for my application with the MVVM-Light framework.
I have a datagrid that is bound to an observable collection of "Tracking" objects called TrackingCollection. The datagrid selectedItem is bound to a "SelectedTracking" property in the viewModel.
<DataGrid Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="3" MinHeight="300"
ItemsSource="{Binding TrackingCollection}"
CanUserAddRows="False" CanUserDeleteRows="False"
SelectionMode="Single" SelectedItem="{Binding SelectedTracking, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RowDetailsTemplate="{StaticResource FTC_TrackingFullDetailTemplate}">
</DataGrid>
I have a comboBox in one column that is bound to an "idAction" property of the SelectedTracking object. When the user changes the selection of this comboBox, I want to assign the values of two other combo boxes in two other columns of the datagrid. These other columns are not bound to properties of the view model, rather they are bound directly to the properties of the SelectedTracking object. These properties of the SelectedTracking object are iSource_Type and iDestination_Type.
Here is the column definition for iSourceType:
<DataGridTemplateColumn Header="SOURCE" SortMemberPath="tracking_source.chrSource" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Style="{StaticResource FTC_DetailComboBox}" Margin="0" Padding="3"
ItemsSource="{Binding DataContext.TrackingSources, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
SelectedValuePath="idSource"
DisplayMemberPath="chrSource"
SelectedValue="{Binding iSource_Type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
So when I assign these (iSource_Type, iDestination_Type) values in the ViewModel code (in a selectionChanged function of the first "Action" comboBox) the values are updated on the object itself. But the change is not reflected back to the UI's comboboxes bound to these properties.
What I tried:
First:
I have an implementation of INotifyPropertyCHanged with a function called RaisePropertyChanged. THis is provided through the MVVM_Light framework. SO i tried to use the following:
RaisePropertyChanged("iDestination_Type")
RaisePropertyChanged("iSource_Type")
RaisePropertyChanged("SelectedTracking")
RaisePropertyChanged("SelectedTracking.iDestination_Type")
RaisePropertyChanged("SelectedTracking.iSource_Type")
But these do not work.
Second:
I also tried to create properties in the viewmodel that bound to the SelectedTracking object. But this just caused all the tracking objects to get the same values.
Question:
Can INotifyPropertyChanged work on properties that are not a part of the viewmodel, but are properties of objects found in the view model. If so, what syntax do I need in the INotifyPropertyChanged event?
Additional INformation:
The MVVM-Light implementation of INotifyPropertyChanged (RaisePropertyChanged()) does not accept an empty string that would normaly update all UI elements. So is there a way I can override the Implementation of INotifyPropertyCHanged in just one CLass?
If I understand your problem correctly you would like a way to notify your ViewModel of changes to your Model.
If so you can implement INotifyPropertyChanged in your model and subscribe to the model objects PropertyChanged event in your ViewModel. Here you can raise the property changed notification on your ViewModel properties.
A simple example to demonstrate the concept:
Model:
public class Tracking : INotifyPropertyChanged
{
private string _isourcetype;
private string _idestinationtype;
public string SourceType
{
get { return _isourcetype; }
set
{
_isourcetype = value;
OnPropertyChanged("SourceType");
}
}
public string DestinationType
{
get { return _idestinationtype; }
set
{
_idestinationtype = value;
OnPropertyChanged("DestinationType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel:
public class TrackingViewModel : ViewModelBase
{
private Tracking _selectedTracking;
public string DestinationType
{
get { return _selectedTracking.DestinationType; }
}
public string SourceType
{
get { return _selectedTracking.SourceType; }
}
public Tracking SelectedTracking
{
get { return _selectedTracking; }
set
{
_selectedTracking = value;
RaisePropertyChanged("SelectedTracking");
}
}
public TrackingViewModel()
{
_selectedTracking = new Tracking();
_selectedTracking.PropertyChanged += SelectedTracking_PropertyChanged;
}
void SelectedTracking_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SourceType":
RaisePropertyChanged("SourceType");
break;
case "DestinationType":
RaisePropertyChanged("DestinationType");
break;
}
}
}

Binding to Property of a ComboBox's SelectedItem

I am having a hard time finding the right syntax for binding to a ComboBox's SelectedItem's property. This is the XAML I am trying to use for the binding. Where you see SelectedItem.Mode is the idea I am having difficulty with. Note that CurrentMode is in the ViewModel and has the same type as SelectedItem.Mode
<ComboBox SelectedItem.Mode="{Binding Path=CurrentMode, Mode=TwoWays}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImageSource}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<local:ModeItem Mode="Free" ImageSource="pencil.png"/>
<local:ModeItem Mode="Arrow" ImageSource="arrow.png"/>
</ComboBox>
A local:ModeItem looks like this
public class ModeItem : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register("Mode", typeof(AnnotationMode), typeof(ModeItem));
public AnnotationMode Mode
{
get { return (AnnotationMode)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
public string ImageSource { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
I am using MVVM and trying to bind the AnnotationMode (CurrentMode) of the ViewModel to that of the ComboBox's SelectedItem's AnnotationMode (Mode)
Just do this
SelectedItem="{Binding CurrentMode}
You don't have to do all this extra stuff you are doing. Note You do need to make the datacontext of the combobox is pointing to your viewmodel.
Edit :-
You should be able to do this
SelectedValue="{Binding CurrentMode, Mode=TwoWay}"
SelectedValuePath="Mode"

Binding to individual elements in a collection

I am fairly new to MVVM, so bear with. I have a view model class that has a public property implemented as so:
public List<float> Length
{
get;
set;
}
In my XAML for the view, I have several text boxes, with each one bound to a specific element in this Length list:
<TextBox Text="{Binding Length[0], Converter=DimensionConverter}" />
<TextBox Text="{Binding Length[2], Converter=DimensionConverter}" />
<TextBox Text="{Binding Length[4], Converter=DimensionConverter}" />
The DimensionConverter is a IValueConverter derived class that formats the values like a dimension (i.e. 480.0 inches becomes 40'0" in the text box on screen), and back again (i.e. takes 35'0" for a string and yield 420.0 inches for the source)
My issue: I need to be able to validate each value in the List as it is changed in the associated TextBox. For some, I may need to modify other values in the List depending on the entered value (i.e. change the float at Length[0] will change the value at Length[4] and update the screen).
Is there any way to re-work the property to allow for an indexer? Or, do I need to create individual properties for each item in the List (which really makes the List unnecessary)? Essentially, since I already have the collection of float, I was hoping to be able to write MVVM code to validate each item as it is modified.
Thoughts? (and, thanks in advance)
You can use an ObservableCollection<float> instead of a List<float>, and handle the CollectionChanged event to detect when the user changes a value.
Wouldn't something like this:
<ItemsControl ItemsSource="{Binding Length}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=TwoWay, Converter=DimensionConverter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Be close to what you want?
It will display the entire list, and allow the user to modify the values, which will be returned straight back to the list, as long as your IValueConverter implements ConvertBack.
Then do as Thomas said to validate, or implement an ObservableLinkedList
What you do at the moment looks dirty already and it's barely a few lines of code..
It would be great if you can have a class which implements INotifyPropertyChanged to have the properties provided the length of list is constant.
if you want validate your text input with mvvm then create a model that you can youse at your viewmodel
public class FloatClass : INotifyPropertyChanged
{
private ICollection parentList;
public FloatClass(float initValue, ICollection pList) {
parentList = pList;
this.Value = initValue;
}
private float value;
public float Value {
get { return this.value; }
set {
if (!Equals(value, this.Value)) {
this.value = value;
this.RaiseOnPropertyChanged("Value");
}
}
}
private void RaiseOnPropertyChanged(string propName) {
var eh = this.PropertyChanged;
if (eh != null) {
eh(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
at your viewmodel you can use the model like this
public class FloatClassViewmModel : INotifyPropertyChanged
{
public FloatClassViewmModel() {
this.FloatClassCollection = new ObservableCollection<FloatClass>();
foreach (var floatValue in new[]{0f,1f,2f,3f}) {
this.FloatClassCollection.Add(new FloatClass(floatValue, this.FloatClassCollection));
}
}
private ObservableCollection<FloatClass> floatClassCollection;
public ObservableCollection<FloatClass> FloatClassCollection {
get { return this.floatClassCollection; }
set {
if (!Equals(value, this.FloatClassCollection)) {
this.floatClassCollection = value;
this.RaiseOnPropertyChanged("FloatClassCollection");
}
}
}
private void RaiseOnPropertyChanged(string propName) {
var eh = this.PropertyChanged;
if (eh != null) {
eh(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
here is the xaml example
<ItemsControl ItemsSource="{Binding Path=FloatClassViewmModel.FloatClassCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay, Converter=DimensionConverter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
hope this helps

WPF checkbox IsChecked property does not change according to binding value

here is my code:
xaml side:
I use a data template to bind with item "dataType1"
<DataTemplate DataType="{x:Type dataType1}">
<WrapPanel>
<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Command="{Binding Path=CheckedCommand} />
<TextBlock Text="{Binding Path=ItemName, Mode=OneWay}" />
</WrapPanel>
</DataTemplate>
then I create a ComboBox with item with type "dataType1"
<ComboBox Name="comboBoxItems" ItemsSource="{Binding Path=DataItems, Mode=TwoWay}">
and here is dataType1 difinition:
class dataType1{public string ItemName{get; set;} public bool IsChecked {get; set;}}
the scenario is I prepare a list of dataType1 and bind it to the ComboBox, ItemName display flawlessly while CheckBox IsChecked value is always unchecked regardless the value of "IsChecked" in dataType1.
Is special handling needed in binding IsChecked property in CheckBox in wpf?
Peter Leung
The problem you're having here is that the CheckBox doesn't know when the value of dataType1.IsChecked changes. To fix that, change your dataType1 to:
class dataType1 : INotifyPropertyChanged
{
public string ItemName { get; set; }
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
if (isChecked != value)
{
isChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So now, when the property value is changed it will notify the binding that it needs to update by raising the PropertyChanged event.
Also, there are easier ways to do this that avoid you having to write as much boiler-plate code. I use BindableObject from Josh Smith.

Resources