I have a datagrid populated with "notes" and when a note is clicked I want the richtextbox to show the note.comments. But the Bindings isn't working.
public NoteDTO SelectedNote {get; set;}
public string stringNotes {get; set;}
public void OpenNote()
{
stringNotes = SelectedNote.Comments;
}
<DataGrid x:Name="NoteGrid" cal:Message.Attach="[Event MouseDoubleClick] = [Action OpenNote()]" ItemsSource="{Binding Notes}" SelectedItem="{Binding SelectedNote}"
<toolkit:RichTextBox Text="{Binding stringNotes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
If I may get help please.
The main problem is that you're binding to a property that has no concept of change notifications; you're not implementing INotifyPropertyChanged. That being said, why not just bind the RichTextBox directly to the property off of NoteDTO:
<toolkit:RichTextBox Text="{Binding SelectedNote.Comments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The other option is to manually copy the comments between SelectedNote and stringNotes, then implement INotifyPropertyChanged, but this isn't ideal unless you want to have an intermediate property before propagating them to the NoteDTO object.
EDIT:
I noticed that your SelectedNote property will never notify the UI that it has changed, which will prevent bindings from working. Try something like the following:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string selectedNote;
public string SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
this.OnPropertyChanged("SelectedNote");
}
}
}
Related
I have two classes A and B which both implement an interface IThingWithList.
public interface IThingWithList
{
ObservableCollection<int> TheList;
}
TheList in A contains 1, 2, 3
TheList in B contains 4, 5, 6
I have a controller class which has a list of IThingWithList which contains A and B
public class MyControllerClass
{
public ObservableCollection<IThingWithList> Things { get; } = new ObservableCollection<IThingWithList>() { A, B };
public IThingWithList SelectedThing { get; set; }
}
Now, in xaml I have two ComboBoxes as follows
<ComboBox
ItemsSource="{Binding MyController.Things}"
SelectedValue="{Binding MyController.SelectedThing, Mode=TwoWay}" />
<ComboBox
DataContext="{Binding MyController.SelectedThing}"
ItemsSource="{Binding TheList}" />
The first ComboBox controls which (A or B) is the data context of the second combo box.
Problem:
When I select A or B from the first ComboBox The list items of the second ComboBox are not updated.
What I have tried:
Making both A and B ObservableObjects
Making IThingWithList implement INotifyPropertyChanged
Adding UpdateSourceTrigger to the ItemsSource Bindings
Scouring Google.
Here is how I typically do the ViewModel (in your case "Controller") Base Class in order to get the functionality you are looking for:
This is the base class that all VMs derive from.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetAndNotify<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(property, value))return;
property = value;
this.OnPropertyChanged(propertyName);
}
}
Here is how I would adjust your ControllerClass:
public class MyControllerClass : ViewModelBase
{
private ObservableCollection<IThingWithList> _things;
public ObservableCollection<IThingWithList> Things
{
get => _things;
set { SetAndNotify(ref _things, value); }
}
private IThingWithList _selectedthing;
public IThingWithList SelectedThing
{
get => _selectedThing;
set{SetAndNotify(ref _selectedThing, value);}
}
}
Now adjust your XAML to have the DataContext of the container set instead of each Control (makes life easier)
<UserControl xmlns:local="clr-namespace:YourMainNamespace">
<UserControl.DataContext>
<local:MyControllerClass/>
</UserControl.DataContext>
<StackPanel>
<ComboBox
ItemsSource="{Binding Things}"
SelectedValue="{Binding SelectedThing, Mode=TwoWay}" />
<!-- ComboBox ItemsSource="See next lines" /-->
</StackPanel>
</Window>
You can change SelectedThing to be an ObservableCollection or you can have a second object that is the list and updates accordingly:
//Add into MyControllerClass
public MyInnerThingList => SelectedThing.TheList;
//Edit the SelectedThing to look like:
private IThingWithList _selectedthing;
public IThingWithList SelectedThing
{
get => _selectedThing;
set
{
SetAndNotify(ref _selectedThing, value);
RaisePropertyChanged(nameof(MyInnerThingList));
}
}
Then change the binding to:
<ComboBox ItemsSource="{Binding MyInnerThingList, Mode=OneWay}" />
You may also want to add a SelectedMyInnerThing property, but not sure if that is needed.
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>
I have an ObservableCollection that gets it's data from a DataTable that is populate from a Postgres Database. I need to bind this ObservableCollection to a ComboBoxColumn in a DataGrid. I have seen quite a lot of examples on how to do this, yet I'm constantly missing something.
Edit: This is the new updated code and it is working except for the INotifyPropertyChanged that I have set only to "name" (yet)
namespace Country_namespace
{
public class CountryList : ObservableCollection<CountryName>
{
public CountryList():base()
{
// Make the DataTables and fill them
foreach(DataRow row in country.Rows)
{
Add(new CountryName((string)row.ItemArray[1], (int)row.ItemArray[0]));
}
}
}
public class CountryName: INotifyPropertyChanged
{
private string name;
private int id_country;
public event PropertyChangedEventHandler PropertyChanged;
public CountryName(string country_name, int id)
{
this.name = country_name;
this.id_country = id;
}
public string Name
{
get { return name; }
set {
name = value;
OnPropertyChanged("CountryName");
}
}
public int idcountry
{
get { return id_country; }
set { id_country = value; }
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
XAML:
xmlns:c="clr-namespace:Country_namespace"
<Windows.Resources>
<c:CountryList x:Key="CountryListData"/>
</Windows.Resources>
DataGrid Column:
<dg:DataGridTemplateColumn Header="country">
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource CountryListData}}" DisplayMemberPath="Name"></ComboBox>
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
first of all. you can just bind to public properties.
country_ seems no public property.
second if binding not work you always have to check datacontext first and binding path second. you can use Snoop to do this at runtime
EDIT:
you did not post your itemssource for your grid. so some assumptions here.
<DataGrid ItemsSource="{Binding MySource}">
...
<ComboBox ItemsSource="{Binding MySourcePropertyForCountries}"/>
--> this would work when your MySource object item has a public property MySourcePropertyForCountries.
but if your want to bind your combobox to a list wich is outside the MySource object. then you have to use some kind relativeSourcebinding or elementbinding.
<DataGrid x:Name="grd" ItemsSource="{Binding MySource}">
...
<ComboBox ItemsSource="{Binding ElementName=grd, Path=DataContext.MyCountries}"/>
--> this would work when the datacontext of the datagrid has a property MyCountries
Ok so I have spent hours now trying to figure this out and I cant.
I have the below combo box which is binding correctly to my collection of data.
<ComboBox Name="cbx" Width="250" Height="25"
Visibility="{Binding Path=IsComboBox,Converter={StaticResource BoolConverter}}"
ItemsSource="{Binding Path=Answers}"
SelectedValuePath="AnswerId"
SelectedItem="{Binding Path=SelectedAnswer, Mode=TwoWay}"
DisplayMemberPath="Answer"/>
The Selected Item however is not populating top my Selected Answer property. I put a textbox on the form and bound it to SelectedAnswer.Answer and that is binding to the answer correctly.
For some reason though my combo box will not bind the selected answer
I have read something about the layout of the combo box property and tried changing that, also stepped through the getter and setter of the property to ensure it is not clearing down (which is not as it will bind to the text box)
Please help with this.
SurveyAnswer:
public class SurveyAnswer : INotifyPropertyChanged
{
private Guid answerId;
public Guid AnswerId
{
get { return answerId; }
set {
answerId = value;
NotifyPropertyChanged("AnswerId");
}
}
private string answer;
public string Answer
{
get { return answer; }
set {
answer = value;
NotifyPropertyChanged("Answer");
}
}
public Guid SurveyLineID { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set {
isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
#region NotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
I think you need to change SelectedItem to SelectedValue. Sometimes that order of parameters matters as well.
<ComboBox Name="cbx" Width="250" Height="25"
Visibility="{Binding Path=IsComboBox,Converter={StaticResource BoolConverter}}"
ItemsSource="{Binding Path=Answers}"
SelectedValue="{Binding Path=SelectedAnswer, Mode=TwoWay}"
DisplayMemberPath="Answer" SelectedValuePath="AnswerId"/>
This is helpful:
http://johnpapa.net/binding-to-silverlight-combobox-and-using-selectedvalue-selectedvaluepath-and-displaymemberpath
I am trying to update a textblock on the view by databinding to a property in the viewmodel (the datacontext for the view).
In the code below; when SelectedItem changes, I want the textblock text to update with the value of the Name property on SelectedItem.
In an attempt to achieve this I have set the binding source to the property that is changing and the binding path to the data I want to update the textblock with.
I.e. I am expecting that the binding engine will see a change on the binding Source (SelectedItem) and pull the data from the binding Path (SelectedItem.Name).
http://msdn.microsoft.com/en-us/library/ms746695.aspx
Setting the SelectedItem raises INPC but the text does not update.
public class ViewModel
{
public IConfiguration Configuration { get; set;}
}
public class Configuration : IConfiguration, INotifyPropertyChanged
{
public Item SelectedItem
{
get { return _item;}
set
{
_item = value;
ItemName = _item.Name;
RaisePropertyChangedEvent("SelectedItem");
}
}
public string ItemName
{
get { return _itemName;}
set
{
_itemName= value;
RaisePropertyChangedEvent("ItemName");
}
}
}
public class Item
{
public string Name { get; set;}
}
I know that changes on Configuration are seen because this works:
<TextBlock Text="{Binding Configuration.ItemName}"/>
But this does not:
<TextBlock Text="{Binding Path=Name, Source=Configuration.SelectedItem}"/>
And nor does this:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name, Source=Configuration.SelectedItem}"/>
I'm assuming that this should be straightforward - what have I missed?
I've never actually seen anyone use Binding.Source before, so I don't know much about it. But my guess is that it's not dynamic. When you create your binding, it's grabbing a reference to the object specified in your Source, and then that's it: it uses that same reference for the lifetime of the binding.
Why make this complicated? Just use Path. That's the normal way of doing binding, and it's dynamic all the way -- what you're doing is exactly what Path is intended for.
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}"/>
This is probably working, you just can not see it. The Binding engine has not been notified that the Name property of the Item object has changed.
Try implementing the INotifyPropertyChanged interface on the Item class as well (raising the PropertyChanged event as necessary)
This will work for your third binding situation, and also for a similar definition as below
<TextBlock DataContext="{Binding Path=Configuration.SelectedItem}" Text="{Binding Path=Name}"/>
But for a simpler fix, this should work:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}" />
Edit:
public class Configuration : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private Item _SelectedItem = null;
public Item SelectedItem
{
get
{
return _SelectedItem;
}
set
{
_SelectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
public class Item
{
public string Name { get; set; }
}
Then in a Command Execute somewhere I have this:
Configuration.SelectedItem = new Item() { Name = "test" };
Which updates the TextBlock in the View fine:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}" />