I have a ComboBox in WPF binding its ItemsSource Property to a Property returning an IEnumerable of String. The binding is just one-way. The class that contains the data for the ComboBox implements INotifyPropertyChanged Interface and calls the OnPropertyChanged(..) as soon as the Property gets updated. When I leave the ComboBox untouched the changes are correctly propagated. But as soon as the ComboBox is expanded once or a value is selected the changes in the ItemsSource Collection are no longer updated. What may be the reason for this behaviour?
Heres the XAML
<ComboBox Name="cmbSourceNames"
Grid.Row="0"
SelectedItem="{Binding Path=CurrentSource, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=SourceAddresses, NotifyOnSourceUpdated=True}"/>
The DataContext is set in the code behind:
this.cmbSourceNames.DataContext = this._dpa;
And this one is the Method that triggers the change of the Property. The Method for adding the Packet is delegated to the Current Dispatcher with BeginInvoke.
private void DispatcherAddDataPacket(DataPacket dp)
{
ObservableCollection<DataPacket> dpList;
this._dpkts.TryGetValue(dp.SourceAddress, out dpList);
if (dpList == null)
{
dpList = new ObservableCollection<DataPacket>();
dpList.Add(dp);
this._dpkts.Add(dp.SourceAddress, dpList);
OnPropertyChanged("SourceAddresses");
}
else
{
dpList.Add(dp);
}
}
The Property is giving back the Keys of the Dictionary as IEnumerable.
Finally I implemented the Binding Property using an ObservableCollection tracking the keys when a new Packet gets added (so every key to the Dictionary has an equivalent in this ObservableCollection). I think it's not really a satisfying solution (because you have to keep track of both Collections independently) but it works like this.
Related
The WPF piece of code is quite simple:
<telerik:RadGridView Name="AnalisiKey"
AutoGenerateColumns="True"
Margin="10,273,694,59"
d:DataContext="{d:DesignInstance Type=viewModels:FrequentKeywordFinderViewModel, IsDesignTimeCreatable=True}"
ItemsSource="{Binding ItemCollectionViewSourceSingole}"
ClipboardCopyMode="All" SelectionMode="Extended" SelectionUnit="Mixed">
<!--<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn x:Name="Keyword" Header="Keyword" Language="it-it" DataMemberBinding="{Binding (viewModels:KeyFreq.Keyword)}" />
<telerik:GridViewDataColumn x:Name="FreqNelDocum" Header="FreqNelDocum" Language="it-it" UniqueName="FreqNelDocum"/>
</telerik:RadGridView.Columns>-->
</telerik:RadGridView>
As well as the ViewModel
class FrequentKeywordFinderViewModel : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider) => this;
public List<KeyFreq> ItemCollectionViewSourceSingole { get; set; } = new List<KeyFreq>();
}
And the piece of code where the ItemSource is populated:
private void MostroRisultatiSuGriglia(List<KeyFreq> singole,
List<KeyFreq> doppie, bool excludeUnfrequentKeys)
{
var dataContext = ((FrequentKeywordFinderViewModel)this.DataContext);
var itemCollectionViewSourceSingole = dataContext.ItemCollectionViewSourceSingole;
singole = CalcolaTfIdf(StopWordsUtil.FrequenzaKeywords, singole);
dataContext.ItemCollectionViewSourceSingole.AddRange(singole.Where(s => s.FreqNelDocum > 1).ToList());
itemCollectionViewSourceDoppie.Source = doppie.Where(s => s.FreqNelDocum > 1).ToList();
}
With Snoop I can delve into the datagrid.ItemSource and see the items. But they don't appear in the datagrid. Any suggestion?
A key point to be aware of when using binding is that the control doesn't get updated from bound properties unless and until it's notified that the values have changed. There are two basic ways to implement this notification:
Inherit your ViewModel from INotifyPropertyChanged and invoke the PropertyChanged event whenever your property value changes. This approach is suitable for most situations, including numerical and string properties bound to controls such as TextBlock and TextBox.
Use ObservableCollection for collections bound to the ItemsSource property (for controls which have an ItemsSource property).
Controls are aware of the INotifyPropertyChanged interface and the INotifyCollectionChanged interface underlying ObservableCollection, and listen for the appropriate PropertyChanged and CollectionChanged events.
Guidelines for selecting the appropriate technique are as follows:
If the property value in the ViewModel is set before the control's DataContext has been set to the ViewModel and never subsequently changes, you actually don't need to use the PropertyChanged notification at all, because the control will see the intended property value when the ViewModel is bound.
If you are binding to a property for which the value will be intially assigned or will change after the DataContext has been set to the ViewModel, the ViewModel must inherit from INotifyPropertyChanged and the property setter must invoke the PropertyChanged event, otherwise the control will never be aware that the property value has changed.
If you are binding a collection to control's ItemsSource property, you need to consider the above, but you also need to consider how and when you are populating or updating the collection's contents.
If you are creating and populating a collection such as a list, then setting a ViewModel's property (which is bound to a control's ItemsSource property) and never modifying the collection's contents (although you may later assign the ViewModel property to different collection), the ViewModel must inherit from INotifyPropertyChanged and the collection property setter must invoke the PropertyChanged event. In this scenario, you actually don't need to consider ObservableCollection; you can use any desired collection type in your ViewModel.
If you are modifying your collection's contents while it is bound to a control's ItemsSource property, CollectionChanged events are required for the control to update correctly; the easiest way to accomplish this is to use an ObservableCollection in your ViewModel; it will automatically raise CollectionChanged events as items are added or removed.
These are basic guidelines that should help to identify and resolve the most common/most likely problems when using binding.
Typical property binding:
public class MyViewModel : INotifyPropertyChanged
{
private string _myString;
public string MyString
{
get => _myString;
set
{
_myString = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MyString));
}
}
}
In your case, you might only need to change ItemCollectionViewSourceSingole from List<KeyFreq> to ObservableCollection<KeyFreq> because you initialize the empty collection in the ViewModel constructor and only add items later.
I'm very new to WPF. I'm trying to bind to a property a row in a DataGrid so that when the row's clicked the property is set. The ItemsSource that's bound to the DataGrid is an ObservableCollection of objects of type Field.
I've tried to bind to the SelectedItem attribute on the DataGrid, but the property is not being called. I'm using almost identical code to bind to the SelectedItem of a ComboBox and this is working fine. Is there a difference that I don't know about?
<ComboBox ItemsSource="{Binding RecordTypes}" SelectedItem="{Binding SelectedRecordType}" ...
<DataGrid ItemsSource="{Binding Fields}" SelectedItem="{Binding SelectedField}" ...
In my ViewModel:
private Field SelectedField
{
get
{
return _selectedField;
}
set
{
_selectedField = value;
}
}
(I will use auto properties later, it's just currently set up like this so that I could break when the property was set).
I'm not sure if it makes a difference, but the DataGrid is composed of 2 DataGridTextColumns and a DataGridTemplateColumn, which contains a checkbox.
Does anyone have any ideas? I'd really appreciate any suggestions.
To confirm, the reason that I want to listen to the click of a row is so that I can have the checkbox be checked whenever a row is clicked. If there's a better solution for this then please let me know.
You need to make it a two-way binding:
SelectedItem="{Binding SelectedField,Mode=TwoWay}"
That propagates changes in the view (user selects an item, SelectedItem changes) back to the viewmodel ("SelectedField" property).
Also, as #KevinDiTraglia pointed out, you need to make sure that the viewmodel property SelectedField is public, not private, otherwise the binding will not be able to access the getter/setter.
WPF Combobox is not updating when collection changed in Model.
I am using ICollectionView for both DataGrid and ComboBox. DataGrid is updating when collection is changed in model, but ComboBox is not updating. Please let me know if there are any alternate ways to do this.
Here is the code
Model->
In Model I have
public ObservableCollection<Product> MyModelProducts
ViewModel->
DataGrid Collection
public ICollectionView MyViewModelProducts
{
get
{
return CollectionViewSource.GetDefaultView(MyModel.Instance.MyModelProducts);
}
}
ViewModel-ComboBox Collection
public ICollectionView MyViewModelListOfProducts
{
get
{
return CollectionViewSource.GetDefaultView(MyModel.Instance.MyModelProducts.Select(p => p.Category).Distinct().ToList<string>());
}
}
Code in View-->
<ComboBox ItemsSource="{Binding MyViewModelListOfProducts, Mode=OneWay}" />
Binding MyViewModelProducts to DataGrid
Binding MyViewModelListOfProducts to a ComboBox.
You have to refer to the selected product in offer to filter
Set IsSynchronizedWithCurrentItem to true in the DataGrid.
Raise a property change of MyViewModelListOfProducts when you want it to update. You subscribe the the MyViewModelProducts.CurrentChanged event and update it from there.
You can have a property DistinctCategories or so in the Product class that does that linq query (and is notified upon change, of course), and then bind the ComboBox to the MyViewModelProducts and point to this property - this I think is the preferred way, unless you think the distinct categories is has really no use in the model layer.
I have a View that is linked to my ViewModel using a DataTemplate, like this
<DataTemplate DataType="{x:Type ViewModels:ViewModel}">
<Views:View />
</DataTemplate>
The ViewModel holds a property ProcessOption that is of type MyEnum?, where MyEnum is a custom enumeration that has let's say 3 values: Single, Multiple and All. I am trying to bind a combobox to this property, so the approach I am following is:
ViewModel has a property of List<string> that is
public List<string> Options
{
get
{
return Enum.GetNames(typeof(MyEnum)).ToList();
}
}
to which I bind the ItemsSource property of the Combobox. Then, in addition to the ProcessOption property, the ViewModel also has an OptionName property (of string), which is intended to hold the selected option's name. The ViewModel implements INotifyPropertyChanged and both properties raise the PropertyChanged event in their setters. The binding I am using then is:
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding OptionName}"
SelectedValue="{Binding ProcessOption}"/>
This works fine up to this point. Initially the combobox is empty and both properties are null, and when the user selects an option this is propagated to the ViewModel as it should.
The problem appears when I load the data from a Database, and I want to load the controls with initial values. In this case, in the ViewModel's constructor I have this:
this.ProcessOption = objectFromDB.ProcessOption // this is the value restored from DB, let's say it is MyEnum.Multiple
this.OptionName = Options.First(x => x.Equals(Enum.GetName(typeof(MyEnum), objectFromDB.ProcessOption)));
The problem is, although the above sets the two properties to their correct values, they are later set to null from the Combobox binding, so the initial values are not kept. I have also tried to do something like if (value == null) { return; } in the properties' setters, in which case they have the correct values after the View loads, however the Combobox still does not display the correct option, it is empty.
I should also note that I've also tried setting IsSynchronisedWithCurrentItem and it doesn't make any difference, apart from the fact that the first element is displayed instead of the combobox being empty.
Can anyone help with this binding? Any help will be very much appreciated, this is driving me crazy!
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding OptionName}"
SelectedValue="{Binding ProcessOption}"/>
Your binding doesn't look like it should work at all -- you don't have TwoWay binding set up, and I think SelectedItem and SelectedValue is an either/or proposition.
I suggest that you get rid of OptionName and just bind SelectedItem to ProcessOption (TwoWay) with an IValueConverter that will convert to/from string.
I have a TreeView that permits the user to select different items. The display for each item is determined using Data Templates with the DataType set to the appropriate ViewModel type. The DataContext is automatically set based on the selected item in the tree view to the appropriate ViewModel.
Here's the problem:
One of the DataTemplates has a ComboBox bound to an ObservableCollection to get the list of items and a property to get/set the SelectedValue in the ViewModel.
When I select one item of this type, and then select another item of the same type, the ComboBox displays blank instead of the correct selected item. It appears that the Combo Box is setting the SelectedValue property to NULL immediately after the transition to the new item, and then never updating.
<ComboBox Margin="1,0"
ItemsSource="{Binding ItemsToSelect}"
SelectedValue="{Binding SelectedValue}"
SelectedValuePath="ValuePath" DisplayMemberPath="DisplayPath"
IsEnabled="{Binding CanSelectItem}">
</ComboBox>
The really strange part is if I select an item of a different type between selecting items of the same type, it always displays correctly.
I've tried ignoring the NULL value in the SelectedValue setter, and that didn't work regardless of whether I also raised the PropertyChanged event or not.
private MyObject selectedValue;
public MyObject SelectedValue
{
get
{
return selectedValue;
}
set
{
if (value != null)
{
this.selectedValue = value;
}
this.OnPropertyChanged("SelectedValue");
}
}
Looking at the similar questions while writing this lead me to an interesting attribute that I hadn't found yet - IsSynchronizedWithCurrentItem from this question. At first, I thought this solved the problem, but alas it just changes the behavior somewhat.
With this attribute set to True, the combo doesn't entirely clear its selection, but instead just marks the first item as selected. So, instead of being set to NULL, now the SelectedValue property is being set to the first item in the list.
Anyone have any ideas for a solution?
If I understand your problem correctly, a couple of things are going on here.
First you do not have Mode=TwoWay, UpdateSourceTrigger=PropertyChanged} set in your bindings.
Second, the property binding will only update if there is a change in the value itself.
Check this out Data Binding
I figured out a work around, but it isn't quite what I wanted.
I changed the binding to use SelectedItem instead of SelectedValue, and the issue doesn't occur.
I got the idea from this question.
So I changed
SelectedValue="{Binding SelectedValue}"
To (and renamed my ViewModel Property):
SelectedItem="{Binding SelectedItem}"
After updating the uses of this property in the ViewModel, everything worked.