TreeViewItem not updating with bound ItemsSource, ObservableCollection, and INotifyPropertyChanged - c#

I know this question has been asked a lot but even after trying all of the different answers I still can't get this to work for me. The object I'm trying to bind to is updating correctly in the code behind so the only thing that isn't working is the children of the TreeViewItem updating when the ItemsSource is changed.
It seems that I have everything set up correctly but maybe there is something about how I am tying things together that is making this not work. I am using C# .NET 4.5 WPF project in VS 2015 in Windows 7. I am binding to a static classes' static property that has only a get method to a TreeViewItem's ItemsSource and setting DisplayMemberPath.
XAML:
<!-- Menu tree -->
<TreeView Grid.Column="0"
x:Name="menutree"
Background="Transparent"
BorderThickness="0">
<!-- Profiles TVI -->
<TreeViewItem Header="{x:Static loc:Resources.profiles}"
IsExpanded="True">
<!-- Color profile TVI -->
<TreeViewItem x:Name="colorTvi"
Header="{x:Static loc:Resources.colorProfiles}"
MouseRightButtonDown="colorTvi_MouseRightButtonDown"
DisplayMemberPath="Name"
ItemsSource="{Binding Source={x:Static local:Shared.ColorProfiles}, Mode=OneWay}" />
<TreeViewItem ...
Class / Properties being bound to:
public static class Shared
{
#region Getter / Setter
// Notify property changed
public static NotifyChanged Notify { get; set; } = new NotifyChanged();
// All profiles that have been created
public static List<Profile> Profiles
{
get { return _Profiles; }
set
{
// Set profile
_Profiles = value;
Notify.OnPropertyChanged(nameof(Profiles));
Notify.OnPropertyChanged(nameof(ColorProfiles));
}
}
private static List<Profile> _Profiles = new List<Profile>();
// Color profiles
public static ObservableCollection<ColorProfile> ColorProfiles
{
get
{
return new ObservableCollection<ColorProfile>(
Profiles?.Where(m => m.GetType() == typeof(ColorProfile))?.Cast<ColorProfile>()?.ToList() ??
new List<ColorProfile>());
}
}
#endregion
}
The NotifyChanged class:
// Property changed class
public class NotifyChanged : INotifyPropertyChanged
{
// Property changed event
public event PropertyChangedEventHandler PropertyChanged;
// Notify property changed
public void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
I would like to get this to work without having to call Refresh() in code behind. I have tried binding to Shared.Profiles directly but that doesn't help any. ColorProfile is a base class that inherits from Profile.
Hopefully there's some stupid, simple thing I'm missing. Thanks in advance for the help.
UPDATE :
On further inspection it actually looks like the ItemsSource isn't even updating. During debugging I can see in the control's property explorer that the ItemsSource is bound to an ObservableCollection but the ItemsSource is not reflecting the changes made to the list. If I manually bind in the code behind then it works.

Notify is the one shouting PropertyChanged, on itself.
No one is binded to Notify so no one is updating.
The one who needs to implement INotifyPropertyChanged, or inherit from NotifyChanged is Shared.

Related

Curious behavior with ComboBox in UWP - SelectedItem & ItemsSource

hope you're all fine.
I'm encountering 2issues with a ComboBox in a UWP application.
If the property ItemsSource is bound to a collection that implements INotifyPropertyCollectionChanged, the list is never loaded completly. I only have the 2, 3 or 4 first items... depending on the time. No problem when the same collection is bound to a DataGrid so I think my collection is built correctly. As a workaround (code-behind), I first load my collection (in a Task) and set the ItemsSource property when the task is completed. This solution works but I'd like to do less things code-behind.
The binding on the property SelectedItem seems to work with ReferenceEquals only, the type of item in my collection implements Equals based on IDs and it has been tested separately and successfylly in a console app. As a workaround (code-behind), once my list is loaded, I change the property bound to SelectedItem like this:
Users.TaskFill.ContinueWith(t => BaseItemCollection.UserInterfaceAction.Invoke(() =>
{
if (Item?.Manager != null) Item.Manager = t.Result.FirstOrDefault(i => i.Equals(Manager));
ComboBoxManager.SetBinding(ComboBox.ItemsSourceProperty, this, "Users", BindingMode.TwoWay);
ComboBoxManager.SetBinding(Selector.SelectedItemProperty, "Manager", BindingMode.TwoWay);
}));
Users is my collection (filled asynchronously) used as source for the ComboBox
SetBinding is a custom extension method I've created myself to set bindings code-behind from a single-line (as follow):
public static class ExtensionMethods
{
#region DependencyObject
public static void SetBinding(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, object source, string propertyName, BindingMode mode)
{
var binding = new Binding()
{
Source = source,
Path = new PropertyPath(propertyName),
Mode = mode
};
BindingOperations.SetBinding(dependencyObject, dependencyProperty, binding);
}
public static void SetBinding(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, string propertyName, BindingMode mode)
{
var binding = new Binding()
{
Path = new PropertyPath(propertyName),
Mode = mode
};
BindingOperations.SetBinding(dependencyObject, dependencyProperty, binding);
}
#endregion
}
How can I get this working from XAML withtout needing these workarounds? I has been able to get a similar configuration working with WPF for years but am really struggling with UWP...
Thank you in advance for your help.
If the property ItemsSource is bound to a collection that implements INotifyPropertyCollectionChanged, the list is never loaded completly.
If your collection is not string, you need specify DisplayMemberPath, please check the following code. And please check the collection has value. For my testing collection that implements INotifyPropertyCollectionChanged works for ComboBox.
<ComboBox
x:Name="cmbCountry"
Grid.Row="4"
Width="292"
Height="32"
Margin="28,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DisplayMemberPath="FirstName"
ItemsSource="{Binding MyItems}"
PlaceholderText="Select Country ..."
/>
Curious behavior with ComboBox
The default ItemsPanelTemplate of ComboBox is CuriousPanel that could implement scroll loop within touch device. If you don't want to use it, you could replace it with StackPanel
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
The binding on the property SelectedItem seems to work with ReferenceEquals only,
The SelectedItem is not ComboBox display field, it is an complete User object. You could get the select user in the SelectedItem binding property set method. the following is complete code that you could refer.
public sealed partial class TestPage : Page, INotifyPropertyChanged
{
private User _selecteduser;
public TestPage()
{
this.InitializeComponent();
_myItems = new ObservableCollection<User>
{
new User{UserId=1,FirstName="Fay",LastName="Wang",City="Delhi",State="DEL",Country="INDIA"},
new User{UserId=2,FirstName="Mark",LastName="Liu",City="New York", State="NY", Country="USA"},
new User{UserId=3,FirstName="Rich",LastName="Cai",City="Philadelphia", State="PHL", Country="USA"},
new User{UserId=4,FirstName="Eveia",LastName="Dong",City="Noida", State="UP", Country="CANADA"}}
};
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)//string propertyName
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, args);
}
}
public ObservableCollection<User> Users
{
get
{ return _myItems; }
set
{
_myItems = value;
OnPropertyChanged("Users");
}
}
private ObservableCollection<User> _myItems;
public User SelectedUser
{
get
{
return _selecteduser;
}
set
{
_selecteduser = value;
OnPropertyChanged("SelectedUser");
}
}
}
Xaml
<ComboBox
x:Name="cmbCountry"
Grid.Row="4"
Width="292"
Height="32"
Margin="28,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DisplayMemberPath="FirstName"
ItemsSource="{Binding Users}"
PlaceholderText="Select User..."
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
/>

Binding a combo box to an ObservableCollection

I have a wpf c# application.
I am using a combo box and I have set its itemsource property to an observable collection.
The problem I have is that when I modify this collection the changes are not reflected in my drop down.
so i am wondering what I have done wrong?
This is my class object:
public class JobTicker
{
public string CustomerRef { get; set; }
public string JobRef { get; set; }
public int JobId { get; set; }
public string CustomerJobDetails { get; set; }
public string CustomerName { get; set; }
}
I bind to my collection:
ActiveState.JobsActive = new ObservableCollection<JobTicker>('data from a list');
my declaration of the collection variable:
public static ObservableCollection<JobTicker> JobsActive = new ObservableCollection<JobTicker>();
My combo Box (which is on a userControl of mine that is loaded when my app starts)
<xctk:WatermarkComboBox x:Name="cboActiveJobs" Grid.Row="1" Grid.Column="2"
Width="250" Watermark="Select Customer"
DisplayMemberPath="CustomerJobDetails"
HorizontalContentAlignment="Center"
SelectionChanged="cbo_SelectionChanged"
DropDownOpened="cbo_DropDownOpened"
DropDownClosed="cbo_DropDownClosed"
Style="{StaticResource ComboBoxFlatStyle}"
/>
and my code behind:
cboActiveJobs.ItemsSource = ActiveState.JobsActive;
Now if I modify 'ActiveState.JobsActive' I would expect changes to be reflected in my dropdown but they are not.
The code you have isn't actually binding it. It's just assigning a collection to a property.
The combo box's ItemsSource property can't listen for notifications from the ObservableCollection. Instead, you need an instance of the Binding class to listen for those notifications and make the UI updates happen. Binding is where all the magic is. You could create one programmatically in code behind and attach it (see links below), but the easiest and by far most common way is to bind in XAML:
<xctk:WatermarkComboBox
ItemsSource="{Binding JobsActive}"
SelectedItem="{Binding SelectedCustomer}"
x:Name="cboActiveJobs"
Grid.Row="1"
Grid.Column="2"
Width="250"
Watermark="Select Customer"
DisplayMemberPath="CustomerJobDetails"
HorizontalContentAlignment="Center"
SelectionChanged="cbo_SelectionChanged"
DropDownOpened="cbo_DropDownOpened"
DropDownClosed="cbo_DropDownClosed"
Style="{StaticResource ComboBoxFlatStyle}"
/>
Now, JobsActive should be a public property of the view model that the DataContext for that control. If it isn't, that won't work.
Since you've got a SelectionChanged event, I also added a SelectedCustomer binding, which would be a property on your view model as well. The Binding will update this both ways: Change it in your viewmodel, and the combobox selection will change. When the user picks a combobox item, the viewmodel's property value will change.
private JobTicker _selectedCustomer;
public JobTicker SelectedCustomer {
get { return _selectedCustomer; }
set {
_selectedCustomer = value;
// If you're not in C#6, use this instead:
//OnPropertyChanged("SelectedCustomer");
OnPropertyChanged(nameof(SelectedCustomer));
}
}
// Implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
If you do want to get this binding working right away without writing a viewmodel, I don't recommend that approach, but it's absolutely doable. There are several answers on StackOverflow that should help with getting that working: WPF Binding Programatically, How to programmatically set data binding using C# xaml.

UserControl depends on TreeView's (WPF) SelectedItem

I have two user controls, one contains a TreeView, one contains a ListView.
The TreeView has an itemsource and hierarchical data templates that fill the nodes and leafes (node=TvShow, leaf=Season).
The ListView should show the children of the selected TreeView item (thus, the selected season): the episodes of that season.
This worked fine when I had both the TreeView and the Listview defined in the same window, I could use something like this:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
How can I achieve this, when both controls are defined in separate user controls? (because in the context of one user control, I miss the context of the other user control)
This seems something pretty basic and I am getting frustrated that I can't figure it out by myself. I refuse to solve this with code-behind, I have a very clean MVVM project so far and I would like to keep it that way.
Hope that somebody can give me some advise!
First of all you have to created the SelectedValue proeprty in your ViewModel and bind the TreeView.SelectedItem property to it. Since the SelectedItem property is read-only I suggest you to create a helper to create OneWayToSource-like binding. The code should be like the following:
public class BindingWrapper {
public static object GetSource(DependencyObject obj) { return (object)obj.GetValue(SourceProperty); }
public static void SetSource(DependencyObject obj, object value) { obj.SetValue(SourceProperty, value); }
public static object GetTarget(DependencyObject obj) { return (object)obj.GetValue(TargetProperty); }
public static void SetTarget(DependencyObject obj, object value) { obj.SetValue(TargetProperty, value); }
public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null));
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null, OnSourceChanged));
static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SetTarget(d, e.NewValue);
}
}
The idea is simple: you have two attached properties, the Source and the Target. When the first one changes the PropertyChangedCallback is called and you simply setting the NewValue as the Target property value. In my opinion this scenario is helpful in a lot of cases when you need to bind the read-only property in XAML (especially in control templates).
I've created a simple model to demonstrate how to use this helper:
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.values = new ObservableCollection<string>()
{
"first",
"second",
"third"
};
}
ObservableCollection<string> values;
string selectedValue;
public ObservableCollection<string> Values { get { return values; } }
public string SelectedValue {
get { return selectedValue; }
set {
if (Equals(selectedValue, values))
return;
selectedValue = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, we have data source, selected value and we'll bind it like this:
<StackPanel>
<TreeView ItemsSource="{Binding Values}"
local:BindingWrapper.Source="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Mode=OneWay}"
local:BindingWrapper.Target="{Binding SelectedValue, Mode=OneWayToSource}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Header" Value="{Binding}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<TextBlock Text="{Binding SelectedValue}"/>
</StackPanel>
In the TreeView bound to the ItemsSource from the ViewModel I've created two bindings so they are changing the SelectedValue property in your ViewModel. TextBlock in the end of the sample is used just to show that this approach works.
About the very clean MVVM - I think that it is not the same as the "no code-behind". In my sample the ViewModel still doesn't know anything about your view and if you'll use another control to show your data e.g. ListBox you will be able to use the simple two-way binding and the "BindingWrapper" helper will not make your code unreadable or unportable or anything else.
Create a SelectedSeason property in your ViewModel and bind the ListView's ItemsSource to SelectedSeason.Episodes.
In a perfect world, you could now use a Two-Way binding in the TreeView to automatically update this property when the SelectedItem changes. However, the TreeView's SelectedItem property is readonly and cannot be bound. You can use just a little bit of code-behind and create an event handler for the SelectionChanged event of the TreeView to update your ViewModel's SelectedSeason there. IMHO this doesn't violate the the MVVM principles.
If you want a pure XAML solution, that a look at this answer.

Binding to member of Listbox item

I don't know if I would be informative enough, but I'm having a problem.
I bound an ObservableCollection to a normal Listbox, everything is working fine, but ImageInfo has a member (Source) which contains the place where the image is, and I need the Source member of the current selected item in the Listbox. However, I don't seem to have a clue where to start.
Maybe you need in your xaml something like <Image Source="{Binding ElementName=myListbox, Path=SelectedItem.Source}"> . Other examples and explanations related to binding here https://stackoverflow.com/a/1069389/1606534
Are you binding in normal mode to a property like: EG: < combobox itemssource={Binding Listing}/>? If so you really just need to have a public property exposed for the 'selecteditem' if memory serves. The real power in Observable Collection from my understanding of WPF is how things can change in real time and you can notice those changes when implementing INotifyPropertyChanged or INotifyCollectionChanged.
<combobox x:Name="mycombo" itemssource="{Binding itemsource}"
selecteditem="{Binding SelectedItem}" />
ViewModel property:
public string SelectedItem { get; set; }
However if you want your property to be noticed when it changes you need to implement INotifyPropertyChanged. Typically then in studios I have worked in they set a private variable at the top of the class and then use it in the get set and then use the public property in bindings.
public class example : INotifyPropertyChanged
{
private string _SelectedItem;
public string SelectedItem
{
get { return _SelectedItem; }
set
{
_SelectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
public void DoSomething()
{
Messagebox.Show("I selected: " + SelectedItem);
}
}

Bind a control to a single value in a collection/array in WPF

In WPF I have a collection of bool? values and I want to bind each of these to a separate checkbox programmatically. I want the bindings to be TwoWay so that changing the value of the individual item in the collection in code updates the check box and vice versa.
I have spent ages trying to figure out how to do this and I am completely stuck. With the following code the checkbox only gets the right value when the window is loaded and that's it. Changing the check box doesn't even update the value in the collection. (UPDATE: this appears to be a bug in .NET4 as the collection does get updated in an identical .NET3.5 project. UPDATE: Microsoft have confirmed the bug and that it will be fixed in the .NET4 release.)
Many thanks in advance for your help!
C#:
namespace MyNamespace
{
public partial class MyWindow : Window, INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public List<bool?> myCollection = new List<bool?>
{ true, false, true, false, true, false };
public List<bool?> MyCollection
{
get { return myCollection; }
set { myCollection = value; }
}
}
}
XAML:
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}">
There are a few things that need changing here to get this to work. Firstly you'll need to wrap your boolean value in an object that implements the INotifyPropertyChanged interface in order to get the change notification that you are looking for. Currently you are binding to boolean values in your collection which do not implement the interface. To do this you could create a wrapper class like so :
public class Wrapper: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool val = false;
public bool Val
{
get { return val; }
set
{
val = value;
this.OnPropertyChanged("Val");
}
}
public Wrapper(bool val)
{
this.val = val;
}
}
You'll then want to create these objects in your form instead of a list of booleans. You may also want to use an observable collection instead of a list so that notification of items being added and removed are sent. This is shown below:
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private ObservableCollection<Wrapper> myCollection = new ObservableCollection<Wrapper>()
{new Wrapper(true), new Wrapper(false), new Wrapper(true)};
public ObservableCollection<Wrapper> MyCollection
{
get { return myCollection; }
}
The next thing to do is to display a list of check boxes in your ui. To do this WPF provides itemscontrols. ListBox is an itemscontrol so we can use this as a starting point. Set the itemssource of a listbox to be MyCollection. We then need to define how each Wrapper object is going to be displayed in the list box and this can be done with a datatemplate which is created in the windows resources. This is shown below :
<Window.Resources>
<DataTemplate x:Key="myCollectionItems">
<CheckBox IsChecked="{Binding Path=Val, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=MyCollection}" ItemTemplate="{StaticResource myCollectionItems}"></ListBox>
</Grid>
This should get you up and running with a simple demo of checkboxes that have values bound to a list of booleans.
What makes you think it's not working? It's working for me :)
Here's my test XAML:
<UniformGrid>
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}"/>
<ListBox ItemsSource="{Binding MyCollection}"/>
<Button Content="Test" Click="Button_Click"/>
</UniformGrid>
Here's my code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
(the rest is the same as yours)
I placed a breakpoint on Button_Click and checked MyCollection[0] it was updated according to the IsChecked value of the CheckBox.
Try changing your collection type from List<bool?> to ObservableCollection<bool?> perhaps that is the reason you think it's not working for you (the fact that changes to the collection are not reflected anywhere else in your view).
Change your List<bool?> to an ObservableCollection<bool?>. A List does not raise the change notifications that WPF needs to update the UI. An ObservableCollection does. This handles the case where the list entry is changed and the CheckBox needs to update accordingly.
In the other direction, it works for me even with a List<bool?> -- i.e. toggling the checkbox modifies the value in the collection. Your binding syntax is certainly correct.

Categories

Resources