Here are the relevant parts of the XAML file:
xmlns:local="clr-namespace:BindingTest"
<ListBox x:Name="myList"
ItemsSource="{Binding Source={x:Static local:MyClass.Dic},
Path=Keys,
Mode=OneWay,
UpdateSourceTrigger=Explicit}">
</ListBox>
MyClass is a public static class and Dic is a static public property, a Dictionary.
At a certain point I add items to the Dictionary and would like the ListBox to reflect the changes.
This is the code I thought about using but it doesn't work:
BindingExpression binding;
binding = myList.GetBindingExpression(ListBox.ItemsSourceProperty);
binding.UpdateTarget();
This code instead works:
myList.ItemsSource = null;
myList.ItemsSource = MyClass.dic.Keys;
I would prefer to use UpdateTarget, but I can't get it to work.
What am I doing wrong?
Binding of items is handled rather differently than standard binding of DependencyPropertys in WPF (specifically, by ItemsControls).
I think you want something like the following:
var itemsView = CollectionViewSource.GetDefaultView(myListBox.ItemsSource);
itemsView.Refresh()
It is in fact the ICollectionView object that you want to refresh. This effectively is the object that manages the collection binding for you. See the MSDN page for more info.
Expanding on the previous answer by Noldorin, you can hook to the PropertyChanged event to make this happen automatically anytime your model is updated:
public partial class YourView {
public YourView () {
InitializeComponent();
ViewModel=YourDataObject;
ViewModel.PropertyChanged+=OnUpdateYourDataObject;
}
public YourDataObject? ViewModel {
get => DataContext as YourDataObject;
set => DataContext=value;
}
public void OnUpdateYourDataObject(object? sender, PropertyChangedEventArgs? e) =>
CollectionViewSource.GetDefaultView(YourListBox.ItemsSource).Refresh();
}
Replace YourDataObject and YourListBox with the correct values. This will automatically update your view whenever a property on your object changes. I'm not sure why this isn't handled automatically but I couldn't figure out the setting to make that work.
Related
SO I have a WPF MVVM application. When I press a button, I want a new entry to be added to the dropdown in a combobox, and for the combobox's selected item to be set to that item. I'm able to get the item added to the dropdown through an ObservableCollection, but I can't seem to bind the SelectedItem properly. I have tried:
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
And putting the SelectedItem directly in the ViewModel implementing INotifyPropertyChanged in the ViewModel. But this does not work. Any ideas?
EDIT: I should also add that the OnNotifyPropertyChange event does fire correctly when I expect it to, so I'm not sure what's going on. I also tried using UpdateSourceTrigger=PropertyChanged to no avail.
Just a small advice to avoid such a situation where you write the name of the property incorect.
If you're using .net 4.5 you can use the CallerMemberName-Attribute in your OnPropertyChanged-Method. This looks like:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
Then you're property looks something like
public bool MyProperty
{
get{ return myProperty; }
set
{
myProperty=value;
OnPropertyChanged();
}
}
You also can write a method which extracts you the propertyname out of a lambda-expression. The method in a base-class looks like:
public static class Helper
{
public static string GetPropertyName<T>(Expression<Func<T>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
The usage in a property than looks like:
public bool MyProperty
{
get{ return myProperty; }
set
{
myProperty = value;
OnPropertyChanged(Helper.GetPropertyName(() => MyProperty));
}
}
With this approach you have a compile-time-check of your property-name.
Thanks guys, I spelled the property wrong in the OnNotifyPropertyChanged...Good grief.
You can also use
OnPropertyChanged(nameof(MyProperty));
to avoid missspellings.
I started to use Fody with PropertyChanged which injects the OnPropertyChanged Code automatically saving you some typing work and makes the code look nice and clean.
Can someone tell me if this is possible. I have a WPF datagrid and I'd like to bind the column headers of the datagrid to properties/fields in the code behind.
So heres what I've tried. This is my column code.
<DataGridTextColumn Header="{Binding ElementName=frmAssetPPM, Path=HeaderProperty}"
And this is what I've added to the window xaml.
<Window .... Name="frmAssetPPM">
And this is my property definition in the code behind:
private const string HeaderPropertyConstant = "Property";
private string _headerProperty = HeaderPropertyConstant;
public string HeaderProperty
{
get { return _headerProperty; }
set { _headerProperty = value; }
}
However, when I run the application, I'm getting this error message displayed in the Output window in VS.
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=HeaderProperty; DataItem=null; target element is 'DataGridTextColumn' (HashCode=47624635); target property is 'Header' (type 'Object')
Can someone tell me what I'm doing wrong? Or if I can even do this? I read somewhere that columns are a separate object and this sometimes leads to complications.
Some things are tough to bind because they are not part of the Visual tree, such as popups or in your case - datagrid headers. A well known workaround is to use Josh Smith's DataContextSpy. Basically you instantiate it as a resource and give it the binding. Then use that instance elsewhere, where you can tap into it's data context. There are plenty examples on the web, but to get you started, something like this should work..
<DataGrid.Resources>
<DataContextSpy x:Key="dcSpy"
DataContext="{Binding ElementName=frmAssetPPM, Path=HeaderProperty}"/>
....
then your binding will work:
<DataGridTextColumn Header="{Binding Source={StaticResource dcSpy}, Path=DataContext}"
here's Josh Smith's code if you don't find it on the web:
public class DataContextSpy : Freezable
{
public DataContextSpy ()
{
// This binding allows the spy to inherit a DataContext.
BindingOperations.SetBinding (this, DataContextProperty, new Binding ());
}
public object DataContext
{
get { return GetValue (DataContextProperty); }
set { SetValue (DataContextProperty, value); }
}
// Borrow the DataContext dependency property from FrameworkElement.
public static readonly DependencyProperty DataContextProperty = FrameworkElement
.DataContextProperty.AddOwner (typeof (DataContextSpy));
protected override Freezable CreateInstanceCore ()
{
// We are required to override this abstract method.
throw new NotImplementedException ();
}
}
I need to create a DependancyProperty Collection
and somehow bind or be able to know when each item changes
It is not easy to explain this problem..
To declare a simple DependancyProperty we do that:
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register("SelectedColor", typeof(Color),
typeof(MyClass), new PropertyMetadata(Colors.Red));
public Color SelectedColor
{
get { return (Color)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
And later on in XAML we can do that:
<MyClass SelectedColor="{Binding blah blah}"/>
If I add a simple callback on value change in the static declaration so instead of
just new PropertyMetadata(Colors.Red)
i'll have: new PropertyMetaData(Colors.Red,MyCallback)
Each time the value is changed I will be able to react to that change from code behind.
Ok, now what I want is something that will be written in XAML like that:
<MyClass>
<MyClass.SelectedColors>
<Color>{Binding blah1}</Color>
<Color>{Binding blah2}</Color>
<Color>{Binding blahN}</Color>
<MyClass.SelectedColors>
</MyClass>
However, I can't use multibinding as I want to know which color has
changed and multibinding gives me an aggregator tactic, meaning when
I done writing the multibinding converter I will get all the values
from all "blahs" at the same time and will not be able to know which
one was the one that changed..
I began writing something simple that look as follows:
public static readonly DependencyProperty SelectedColorsProperty =
DependencyProperty.Register("SelectedColors", typeof(List<Color>),
typeof(MyClass),
new PropertyMetadata(new List<Color>()));
public List<Color> SelectedColors
{
get { return (List<Color>)GetValue(SelectedColorsProperty); }
set { SetValue(SelectedColorsProperty, value); }
}
public MyClass()
{
foreach(var item in SelectedColors)
{
//dunno what goes here.. or how to bind the color change?
}
}
So now I can't create a callback that will handle each change of each item, right?
Yet that is exactly what I need, to be able to react to a change in each item..
However it does allow me to express the XAML syntax I need..
Any ideas? Did anyone bumped into that kind of problem before?
I noticed very short explanation in MSDN but i'm not sure if it helps me and how
to use it if can help, it is the last paragraph in the page that mentions
something about using a
Freezable<T>
http://msdn.microsoft.com/en-us/library/aa970563.aspx
I appreciate your help,
Thanks.
Update: A MultiBinding Solution can also be ok if it will allow to distinguish which of the values had the change (in our example which color changed).
The Markup in that case might look like that:
<MyClass>
<MyClass.SelectedColors>
<MultiBinding..
<Binding Path="FillColor" ElementName="MyRectWhichHasFillColorDProperty"/>
<Binding blah2 which is Color2 />
<Binding blah3 which is ColorN />
Wrap your Colors in a class that implements INotifyPropertyChanged and then setup a listener to the PropertyChanged event for each color in your constructor.
Wrapper class
public class ColorViewModel : INotifyPropertyChanged
{
private Color color;
public Color Color
{
get { return color; }
set
{
if (value != color)
{
color = value;
NotifyPropertyChanged("Color");
}
}
}
// INotifyPropertyChanged implementation goes here
}
Then in your event listener you will get the individual ColorViewModel that fired the property changed event.
I have had to solve a very similar problem before and I created an ObservableCollection of my ViewModel class so that I could subscribe to an collection changes and attach/detach my event listeners to the changed items.
I typed this from memory on my Surface so there are probably lots of typos and you should take the code sample as sudo code.
Hope this helps.
EDIT
Here is what your Xaml could look like:
<namespace:MyClass>
<namespace:MyClass.SelectedColors>
<namespace:ColorViewModel Color="{Binding SomeValue}" />
<namespace:ColorViewModel Color="{Binding OtherValue}" />
</namespace:myClass.SelectedColors>
</namespace:MyClass>
where Colors is a collection of ColorViewModel.
You will not be able to wire up your PropertyChanged listeners in your constructor since at the time of construction there are no ColorViewModels in Colors, so I would just make Colors an ObservableCollection and use the CollectionChanged event to wire up PropertyChanged listeners to any new additions to the collection.
You may just want to Use a SolidColorBrush instead of a ColorViewModel class as it has a Color property and a Changed event.
I'm looking for some general WPF/C# info on binding to a custom class using ObervableCollection. I currently have an error relating to "BindingExpression path error"..."property not found on". Any pointers would be good.
It sounds like you haven't assigned your DataContext. Below is a brief example.
Assuming your custom class looks something like this:
CODE:
public class Foo
{
private ObservableCollection<string> _names;
public ObservableCollection<string> Names
{
get{ return _names;}
set
{
_names = value;
}
}
}
and your XAML looks like
XAML:
<ListBox Name="lstNames" ItemsSource="{Binding Names}"/>
Set your DataContext in code behind.
lstNames.DataContext = new Foo();
This is a very simplistic version to achieve what you need. You should really have a look at Binding to Collections.
There are two reasons, that may appears. First - you have typed property name on xaml with error. Second - you forgot to set DataContext to your View.
Ive got two DataGrids. EmployeeGrid and WorkSessionsGrid. Each Employee has a list of WorkSessions that I want the user to access by selecting an Item in the EmployeeGrid which should make the WorkSessionsGrid generate the WorkSessions for the selected Employee. Why is the following not correct?
<DataGrid Name="dg_2" ItemsSource="{Binding ElementName=dg_1, Path=SelectedItem.WorkSessions}"/>
Update
I've come to the conclusion that the problem has to be in one of the other layers.
Here's the remainder of my code, hopefully someone is capable of helping me out.
Is there something fundamentally that I am missing?
Code-Behind xaml
public partial class MainWindow : Window
{
public EmployeeViewModel EmployeeViewModel = new EmployeeViewModel();
public MainWindow()
{
InitializeComponent();
menu_employee.DataContext = EmployeeViewModel;
sp_employee.DataContext = EmployeeViewModel;
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
sp_worksessions.DataContext = EmployeeViewModel.SelectedEmployee.WorkSessions;
menu_worksession.DataContext = EmployeeViewModel.SelectedEmployee.WorkSessions;
datagrid_worksessions.ItemsSource = EmployeeViewModel.SelectedEmployee.WorkSessions;
}
}
WorkSessionViewModel
class WorkSessionViewModel : ViewModelBase
{
private WorkSessions _workSessionsModel = new WorkSessions();
public WorkSessions WorkSessionsView = new WorkSessions();
private WorkSessionModel _selectedWorkSession = new WorkSessionModel();
public WorkSessionModel SelectedWorkSession
...
WorkSessionModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
Try Binding to the Element in stead.
<DataGrid Content="{Binding ElementName=ListOfEmp, Path=SelectedItem.Name}" DataContext="{Binding}" />
This bit of XAML looks quite correct, try to debug the binding, there might be some other problems like visual tree breaks or the WorkSessions collection perchance is a field and not a property etc.
If there are binding errors please share them.
As #H.B. has pointed out correctly, please use your Visual Studio's Output Window to see any binding errors. They will tell you if the bindings are failing. If you find binding erros then yours binding should be solved for two possible issues...
Source of data is incorrect. Is the data context and items source correctly set for that UI element such as DataGrid?
Path of the property in the binding could be incorrect. Is your SelectedItem having an object has any property named WorkSessions in it? etc.
Apart from this we still dont know what dg_1 and dg_2 from your XAML is. Your code behind shows different names datagrid_employees and datagrid_worksessions I guess.
You should add one more item for the EmployeeViewModel, called: SelectedEmployee and bind that with the employee grid selected item, mode=TwoWay.
Then you databind for the second grid should be:
<DataGrid Name="dg_2" ItemsSource="{Binding Path=SelectedEmployee.WorkSessions}"/>
Since both grids are in the same window, thus, you should only set datacontext for the windows only. In side the viewmodel, you have 2 dependency properties: EmployeeList, SelectedEmployee. Whereas, EmployeeList is binded to ItemsSource of the employee grid. SelectedEmployee is binded to SelectedItem on the employee grid.