DisplayMemberPath is not working on ComboBox - c#

I have a combobox which is bound to a property called "BlockDetails" in the viewmodel. When I expand the combobox I can see the items inside it. But the problem is it doesn't select/display the item. On top when I set SelectedValue="{Binding BlockName,Mode=TwoWay}", in the output window it gives a binding path error saying 'Error: BindingExpression path error: 'BlockName' property not found on 'Presentation.InstrumentUI.ViewsLoggedIn.ServiceUtilityMethodsView'. BindingExpression: Path='BlockName' DataItem='Presentation.InstrumentUI.ViewsLoggedIn.ServiceUtilityMethodsView'; target element is 'Windows.UI.Xaml.Controls.ComboBox' (Name='null'); target property is 'SelectedValue' (type 'Object')'. I don't understand why is it going and searcing in the View instead of the model. Please help.
Here is my combobox
<ComboBox uwpControls:DockPanel.Dock="Right"
Margin="16,0,0,0"
Style="{StaticResource ComboBoxStyleForm}"
ItemsSource="{x:Bind ViewModel.BlockDetails,Mode=TwoWay}"
DisplayMemberPath="BlockName"
SelectedValuePath="BlockName"
SelectedValue="{Binding BlockName,Mode=TwoWay}"></ComboBox>
In the code behind I have the ViewModel as follows, the item source for the Combobox is bound correctly
public IServiceUtilityMethodsViewModel ViewModel { get; }
public ServiceUtilityMethodsView()
{
InitializeComponent();
ViewModel = LifetimeScope.Resolve<IServiceUtilityMethodsViewModel>();
DataContext = this;
}
Here is the viewmodel property.
public List<VmServiceMethodBlockDefinition> BlockDetails
{
get => _blockDetails;
set => Set(ref _blockDetails, value);
}
In my model the class is declared as follows,
public class VmServiceMethodBlockDefinition : BindableBaseThreadSafe
{
private string _blockName;
public string BlockName
{
get => _blockName;
set => Set(ref _blockName, value);
}
private List<VmServiceMethodBlockParameters> _blockParameters;
public List<VmServiceMethodBlockParameters> BlockParameters
{
get => _blockParameters;
set => Set(ref _blockParameters, value);
}
}

I think you either confused the SelectedValue property or missed to post some details.
You have an object or data type that is an item of the ComboBox.
You have ComboBox.DisplayMemberPath to select the property of this data type, which should be displayed by the ComboBox in the drop down.
Finally you have ComboBox.SelectedValuePath to select a property of this data type to be the ComboBox.SelectedValue. You use ComboBox.SelectedValue instead of ComboBox.SelectedItem, if you are not interested of the actual data item, but a specific value of this item. Both properties serve the same purpose of exposing the currently selected item.
You typically bind this ComboBox.SelectedValue to the same view model (data context) that exposes the source collection, in your case the BlockDetails collection. Your view model should have a property SelectedBlockName. Then use x:Bind to bind the ComboBox.SelectedValue to this property:
IServiceUtilityMethodsViewModel
public List<VmServiceMethodBlockDefinition> BlockDetails
{
get => _blockDetails;
set => Set(ref _blockDetails, value);
}
private string _selectedBlockName
public string SelectedBlockName
{
get => _selectedBlockName;
set => Set(ref _selectedBlockName, value);
}
XAML
<ComboBox ItemsSource="{x:Bind ViewModel.BlockDetails, Mode=OneTime}"
DisplayMemberPath="BlockName"
SelectedValuePath="BlockName"
SelectedValue="{x:Bind ViewModel.SelectedBlockName, Mode=TwoWay}" />

Related

ListBox emtpy when bound to populated ICollectionView

I'm trying to implement filtering on a UserControl (which is essentially just a ListBox with a data template) using ICollectionView.
When I bind to the ICollectionView my LOAListBox is empty.
My xaml looks like this:
<TextBox Text="{Binding SearchString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- LOA_List is a DependencyProperty which binds to ListBox.ItemsSource -->
<controls:LOAListBox LOA_List="{Binding FilteredView, Mode=OneWay}" />
And in my view model, I do this:
public class LOAViewModel : ViewModelBase
{
public ICollectionView FilteredView { get; private set; }
private string _searchString;
public string SearchString
{
get => _searchString;
set
{
_searchString = value;
RaisePropertyChanged("SearchString");
FilteredView.Refresh();
}
}
private List<LOA> _available_LOAs;
public List<LOA> Available_LOAs
{
get => _available_LOAs;
set
{
_available_LOAs = value;
RaisePropertyChanged("Available_LOAs");
}
}
public LOAViewModel()
{
Available_LOAs = data.GetLOAData();
FilteredView = CollectionViewSource.GetDefaultView(Available_LOAs);
FilteredView.Filter = new Predicate<object>(o => Filter(o as LOA));
}
private bool Filter(LOA loa)
{
return SearchString == null || loa.Display_Name.Contains(SearchString);
}
}
During debugging I can see that Available_LOAs is not empty and after GetDefaultView FilteredView also has that same collection. There aren't any binding errors. I also made by filter method always return true just to remove that possibility.
I feel like I must be missing a step but I've checked various other online examples and I can't find anything... My hunch is that it's related to the fact I'm binding to a ListBox nested in a UserControl, but I don't understand why that would matter when it works if change the binding from FilteredView to Available_LOAs directly.
Update; this is the simplified code for LOAListBox:
XAML:
<UserControl>
<ListBox ItemsSource="{Binding LOA_List, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
</UserControl>
Code-behind:
public partial class LOAListBox : UserControl
{
public static readonly DependencyProperty DataSource = DependencyProperty.Register(nameof(LOA_List), typeof(List<LOA>), typeof(LOAListBox), new PropertyMetadata());
public List<LOA> LOA_List
{
get => (List<LOA>)GetValue(DataSource);
set => SetValue(DataSource, value);
}
}
You cannot bind an ICollectionView to a List<T> property.
Change the type of your dependency property to IEnumerable:
public static readonly DependencyProperty DataSource = DependencyProperty.Register(nameof(LOA_List),
typeof(IEnumerable), typeof(LOAListBox), new PropertyMetadata());
public IEnumerable LOA_List
{
get => (IEnumerable)GetValue(DataSource);
set => SetValue(DataSource, value);
}
As a side note, you should also change the name of the dependency property from "DataSource" to "LOA_ListProperty" (and remove the underscore from both names) to follow the naming convention.
After going step-by-step to reproduce the issue, I eventually realised that I wasn't notifying of changes to FilteredView and, not helping matters, I was changing the ICollectionView source without reassigning the ICollectionView,
So I made my FilteredView a standard property that calls RaisePropertyChanged():
private ICollectionView _filteredView;
public ICollectionView FilteredView
{
get => _filteredView;
set
{
_filteredView = value;
RaisePropertyChanged("FilteredView");
}
}
And when I change the ICollectionView source variable I reassign based on the new source collection:
FilteredView = CollectionViewSource.GetDefaultView(Available_Destination_LOAs);
FilteredView.Filter = new Predicate<object>(o => Filter(o as LOA));

WPF combobox is empty when binding enum

I am trying to bind values of an enum to a combo box but the combo box remain empty with no options to choose.
This is the combo box xaml defintion:
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Path=SkillItemSource}" SelectedItem="{Binding Path=neededSkill, Mode=TwoWay}" SelectedIndex="0" Margin="5" MinWidth="100"></ComboBox>
And this is the items source and selected item which are defined in the window's cs:
public Skill neededSkill = Skill.FirstSkill;
public string[] SkillItemSource
{
get
{
return Enum.GetNames(typeof(Skill));
}
}
What is missing for the values to appear in the combobox?
What is missing for the values to appear in the combobox?
You need to set the DataContext of the ComboBox, or a parent element, to an instance of the class where the SkillItemSource property is defined. If the property is defined in the code-behind, you could just set the DataContext to the view itself: this.DataContext = this;
Also, you can't mix types. If the ItemsSource is bound to an IEnumerable<string>, the SelectedItem property should be bound to a string property.
Also note that neededSkill must be defined as a public property for you to be able to bind to it.
Try this:
public Skill neededSkill { get; set; } = Skill.FirstSkill;
public IEnumerable<Skill> SkillItemSource { get; } = Enum.GetValues(typeof(Skill)).Cast<Skill>();

ComboBox binding to referenced (relative) source

When binding ComboBox to referenced source:
<ComboBox SelectedValue="{Binding Source.SelectedItem}"
ItemsSource="{Binding Source.Items}"
DisplayMemberPath="Name" />
where Source is
SourceType _source;
public SourceType Source
{
get { return _source; }
set { _source = value; OnPropertyChanged(); }
}
and SourceType is
public class SourceType: INotifyPropertyChanged
{
Item _selectedItem;
public Item SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value; OnPropertyChanged(); }
}
public IReadOnlyList<Item> Items { get; }
public SourceType(...)
{
Items = new List<Items>(...) // **new** list generated from passed arguments
SelectedItem = Items.First();
}
}
and Item is
public class Item: INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
}
following happens:
for only one source (if Source never changes) it works: ComboBox display list of Items and correct item is selected (I can see its Name when switching view);
for more than one items ComboBox bugs: has no selection (but drop-down list is there and working fine), selection doesn't persist when switching view or Source is changed (e.g. between 2 sources).
It seems like ComboBox has some problems to identify SelectedValue or find it in the ItemsSource. And I can't figure out what is wrong.
Debugging does not uncover anything: Items is set correctly, SelectedItem is first item from Items collection, yet ComboBox is displayed without selection. Why?
I will use a ObservableCollection instead of a List for Items and use SelectedItem for the ComboBox, not SelectedValue.
Read this great answer for differences between SelectedItem and SelectedValue
Difference between SelectedItem, SelectedValue and SelectedValuePath
#Giangregorio answer is good and working in general, but now I remember why I used SelectedValue in first place.
SelectedValue is useful whenever ItemsSource does not contains SelectedValue. If SelectedItem is used (as per answer), then binding will call setter with null as if the user could select null from the list. I am exploiting this case (to avoid having data-templates and more complicated ViewModel), so I have to stick with SelectedValue and I think I found the reason of the problem in otherwise should-work case.
I have to declare ItemsSource binding first and SelectedValue second:
<ComboBox ItemsSource="{Binding Source.Items}"
SelectedValue="{Binding Source.SelectedItem}"
DisplayMemberPath="Name" />
It works!
Sounds to me like another xaml-specific issue, similar to declare CommandParameter before Command problem.

Combobox with composite collection not updating with changes to observablecollection

As the title suggests; I have a combobox using a composite collection to bind to an observable collection using the MVVM pattern.
If I load my model with existing data then the combobox shows the values so I know the binding works. I can add items to the observable collection and they are shown in a data grid so I know the notify property changed events on the observable collection are working. I suspect it is not working because the composite collection is using a "Static Resource" as its source but if I change it to Dynamic Resource then I get the error:
A 'DynamicResourceExtension' cannot be set on the 'Source' property of
type 'Binding'. A 'DynamicResourceExtension' can only be set on a
DependencyProperty of a DependencyObject.
I have searched for days to find a solution and while others have faced similar problems the solutions have yet to solve my problem.
Here is my code for the model:
public class Model : ObservableObject
{
#region Properties
private string name;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
private string balance;
public string Balance
{
get { return this.balance; }
set { this.balance = value; }
}
#endregion
My ViewModel:
public class ViewModel : ObservableObject
{
private ObservableCollection<Model> modelcollection;
public ObservableCollection<Model> ModelCollection
{
get { return modelcollection; }
set
{
modelcollection= value;
RaisePropertyChangedEvent("ModelCollection");
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChangedEvent("Name");
}
}
private string _balance;
public string Balance
{
get { return _balance; }
set
{
_balance = value;
RaisePropertyChangedEvent("Balance");
}
}
And finally the XAML of my view for the combobox:
<ComboBox MinWidth="100" SelectedValue="{Binding combovalue, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Name">
<ComboBox.Resources>
<vm:ViewModel x:Key="CollectionKey"/>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding ModelCollection, Source={StaticResource CollectionKey}, UpdateSourceTrigger=PropertyChanged}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Thank you all so much in advance.
EDIT:
So I have moved in a direction; right or wrong is yet to be decided.
I think I have traced the problem to be outside the combobox itself so more context is needed here.
The comobobox is on the second tab of a tab control. Each tab item has its own data context pointing to its own view model. Data is entered on the first tab and I want that data to show up in the combobox on the second tab.
If I put a combobox on the first tab then that combobox updates with changes in the observable collection as entered on the first tab. So the issue, (I think), is that the combobox on the second tab is trying to bind to two different view models at the same time. One for the items source and a different one for the selected value.
There was a suggestion in another thread to use x:Reference for the data context but I can't seem to figure out the correct syntax for that.
If your still reading this then any help is really appreciated.
In the end I never got the combobox to populate from the other view model. I am still unsure if this is possible or not.
The solution that finally worked for me was to just combine everything I needed for binding into a single view model. It made the view model a bit heavy but cut down a lot on the XAML so I guess that's a win.

MVVM Pattern in Master-Detail when editing details

i have a ObservableCollection with some data. They are displayed as Master (ListBox) and Detail (some Labels). I use binding and IsSynchronizedWithCurrentItem to show the correct details to the selected master item. This works all fine. Now i want to edit some details (load different image). I implemeted this a a Button Command in the ViewModel.
But how do i know which item is selected (UI) in the ViewModel-layer ?
Thanks for help
I don't really find the IsSynchronizedWithCurrentItem property that useful in MVVM scenarios.
Just expose another SelectedItem property in the ViewModel.
public ItemType SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
// your logic here
}
}
You need to bind a value or Enumerable of values of your ViewModel to the ListBox's SelectedItems property.
SelectedItems="{Binding VMProperty}"
http://msdn.microsoft.com/en-us/library/system.windows.controls.listbox.selecteditems(v=vs.110).aspx
If you only want one item selected you need to set:
SelectionMode="Single"
SelectedItem="{Binding VMProperty}"
If I understood you right, what you are looking for is the following
<ListBox SelectedItem="{Binding ObjectName, UpdateSourceTrigger=PropertyChanged}"/>
Plus under your ViewModel you have to declare the following
public YourObject ObjectName { get; set; }
Simple as that!
The normal way to detect which item in the collection is currently selected in the UI is to data bind a property of the same type as the items in the collection to the ListBox.SelectedItem property:
<ListBox ItemsSource="{Binding SomeCollection}"
SelectedItem="{Binding SomeProperty}" />
Now, whenever the user selects a new item, the SomeProperty setter will be called:
public YourDataType SomeProperty
{
get { return someProperty; }
set
{
someProperty = value;
// The value has just changed
}
}

Categories