Binding Custom Object to WPF Combobox - wpf

I have a combobox that has an items source of type ObservableCollection<Clinic>
<ComboBox ItemsSource="{Binding Source={StaticResource ClinicList}}" DisplayMemberPath="Name" SelectedValue="{Binding Path=Name}" SelectedValuePath="Name"></ComboBox>
This combobox is within a ListView that is bound from EmployeeClinics.
public class Employee{
public ObservableCollection<Clinic> EmployeeClinics { get; set; }
}
When I launch the app I see the appropriate clinics. And the drop down seems to show the correct options, but when I update them, only the Name updates and not the ClinicId (it keeps previous ClinicId).
Edit: Similarly when I add a new clinic to the list and select it from the options, it's Id is 0 when I look at the collection.
Here is my clinic model.
public class Clinic {
public int ClinicId { get; set; }
public string _name { get; set; }
public string Name {
get {
return _name;}
set {
if (_name != value) {
_name = value;
}
}
}
}
UPDATE: Thanks #AyyappanSubramanian. I am making headway. I have updated my Objects
public class Employee{
public ObservableCollection<ClinicView> EmployeeClinics { get; set; }
}
public class ClinicView {
private Clinic selectedClinic;
public Clinic SelectedClinic {
get { return selectedClinic; }
set {
selectedClinic = value;
selectedClinicId = selectedClinic.ClinicId;
}
}
private int selectedClinicId;
public int SelectedClinicId {
get { return selectedClinicId; }
}
}
XAML:
<ComboBox ItemsSource="{Binding Source={StaticResource ClinicList}}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedClinic}"></ComboBox>
Changing the drop downs now properly changes the underlying object and updates the list as desired. Now my only issue is that the comboboxes don't display the current object, just show as blank on start. I've messed around with SelectedValue and Path with no luck. Any suggestions?

Refer the below code. You can use SelectedItem to get both the ID and Name in one SelectedObject. Get only ID using SelectedValue.
<ComboBox ItemsSource="{Binding Clinics}" DisplayMemberPath="ClinicName"
SelectedValuePath="ClinicId" SelectedValue="{Binding SelectedClinicId}"
SelectedItem="{Binding SelectedClinic}"/>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
class Clinic
{
public int ClinicId { get; set; }
public string ClinicName { get; set; }
}
class ViewModel
{
public ObservableCollection<Clinic> Clinics { get; set; }
public ViewModel()
{
Clinics = new ObservableCollection<Clinic>();
for (int i = 0; i < 10; i++)
{
Clinics.Add(new Clinic() { ClinicId=i+1,ClinicName="MyClinic"+(i+1) });
}
}
private int selectedClinicId;
public int SelectedClinicId
{
get { return selectedClinicId; }
set
{
selectedClinicId = value;
}
}
private Clinic selectedClinic;
public Clinic SelectedClinic
{
get { return selectedClinic; }
set
{
selectedClinic = value;
MessageBox.Show("ID:"+selectedClinic.ClinicId.ToString()+" "+"Name:"+selectedClinic.ClinicName);
}
}
}

Related

WPF filtered dynamic menu

I'm working on getting a UserControl in WPF working that has a MenuItem populated with an ItemsSource, which creates a menu that goes n levels deep (although I'm just looking at TopMenuItem\Branches\Leaves right now).
The wrinkle I'm having trouble with is that I want to filter the leaves through a textbox embedded into the menu. If a branch has no leaves, it also gets filtered out. It looks like this at the moment :
I'm working with an ObservableCollection of IMenuTreeItem, which can contain branches (which in turn also has an ObservableCollection of IMenuTreeItem) or leaves.
public interface IMenuTreeItem
{
string Name { get; set; }
}
public class MenuTreeLeaf : IMenuTreeItem
{
public string Name { get; set; }
public Guid UID { get; set; }
public ObjectType Type { get; set; }
public Requirement Requirement { get; set; }
public MenuTreeLeaf(string name, ObjectType type, Guid uID)
{
Type = type;
Name = name;
UID = uID;
}
public MenuTreeLeaf(string name)
{
Name = name;
}
}
public class MenuTreeBranch : IMenuTreeItem, INotifyPropertyChanged
{
public string Name { get; set; }
private ObservableCollection<IMenuTreeItem> _items;
public ObservableCollection<IMenuTreeItem> Items
{
get
{
return _items;
}
set
{
_items = value; OnPropertyChanged();
}
}
public MenuTreeBranch(string name)
{
Name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
This is how I'm filtering. It very much feels like there's a better way.
ObservableCollection<IMenuTreeItem> result = new ObservableCollection<IMenuTreeItem>(ItemsSource);
for (int i = 0; i < result.Count; i++)
{
if (result[i] is MenuTreeBranch currentBranch)
{
if (currentBranch.Items != null)
currentBranch.Items = new ObservableCollection<IMenuTreeItem>(currentBranch.Items.Where(x => x.Name.ToLower().Contains(SearchField.ToLower())));
}
}
result = new ObservableCollection<IMenuTreeItem>(result.Where(x => (x as MenuTreeBranch).Items.Count > 0));
result.Insert(0, new MenuTreeLeaf("[Search]"));
return result;
So my main problems are:
When I've filtered, I can no longer unfilter. ItemsSource gets changed too. Could it be because I'm filtering in the ItemsSourceFiltered getter? I tried to clone, but eh, didn't change anything
When I call OnPropertyChanged on ItemsSourceFiltered any time text changes in the textbox, the menu closes. The menu definitely shouldn't close while you're inputting text.
Any advice?
You may have a menu item class that exposes a recursive Filter string and a collection property that returns the filtered child items:
public class FilteredMenuItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
public ICommand Command { get; set; }
private string filter;
public string Filter
{
get { return filter; }
set
{
filter = value;
foreach (var childItem in ChildItems)
{
childItem.Filter = filter;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Filter)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FilteredChildItems)));
}
}
public List<FilteredMenuItem> ChildItems { get; set; } = new List<FilteredMenuItem>();
public IEnumerable<FilteredMenuItem> FilteredChildItems
{
get { return string.IsNullOrEmpty(Filter)
? ChildItems
: ChildItems.Where(childItem => (bool)childItem.Name?.Contains(Filter)); }
}
}
With a RootItem property in the view model like
public FilteredMenuItem RootItem { get; }
= new FilteredMenuItem { Name = "Items" };
you may bind to it in XAML like this:
<StackPanel DataContext="{Binding RootItem}">
<TextBox Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}"/>
<Menu>
<Menu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource"
Value="{Binding FilteredChildItems}"/>
</Style>
</Menu.Resources>
<MenuItem/>
</Menu>
</StackPanel>
While you populate the ChildItems property of each FilteredMenuItem, the view only shows the FilteredChildItems collection.
You may also notice that the above doesn't use ObservableCollection at all, since no items are added to or removed from any collection at runtime. You just have to make sure the item tree is populated before the view is loaded.

MahApps.Metro SplitButton SelectedItem Databinding

The View is:
<Controls:SplitButton Margin="217,409.75,56,185" Name="SplitButton1"
Width="384"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Orientation="Vertical"
DisplayMemberPath ="UserName"
SelectedItem="{Binding SelectedUser,UpdateSourceTrigger=PropertyChanged,Mode=OneWay}"
ItemsSource="{Binding Users, Mode=TwoWay}" />
The ViewModel is:
public string SelectedUser
{
get { return selectedUser; }
set
{
selectedUser = value;
RaisePropertyChanged("SelectedUser");
}
}
public ObservableCollection<UserModel> Users
{
get
{
return users;
}
set
{
users = value;
}
}
the Model is:
public class UserModel
{
private int id;
private string userName;
private int groupId;
private string deviceMacAddress;
public int Id { get; set; }
public string UserName { get; set; }
public int GroupId { get; set; }
public string DeviceMacAddress { get; set; }
}
i use the above code in xaml to bind the selectedItem in the splitbutton to ViewModel->property--SelectedUser.
but it does not work. anyone knows why?
SelectedUser is returned as Model name (PresentationLayer.Model.UserModel) instead of UserName prooperty.
Because your binding is OneWay by your definition.
Set your binding to TwoWay.
<Controls:SplitButton SelectedItem="{Binding SelectedUser,Mode=TwoWay}"/>
And, there is no need to set the UpdateSourceTrigger=PropertyChanged in this case, because the UpdateSourceTrigger is PropertyChanged by default for the SelectedItem property.

WPF MVVM Binding to SelectedItem of ListBox within DataTemplate of Parent ListBox

I have a ListBox within a DataTemplate of another ListBox simplified to the following XAML
<ListBox ItemsSource="{Binding MovieList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBox ItemsSource="{Binding Cast}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCastMember, Mode=TwoWay}"/>
<TextBlock Text="{Binding MovieName}"/>
<TextBlock Text=....../>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The Parent List is bound to an ObservableCollection of Movie with each Movie object in turn having an ObservableCollection of cast members bound to a list box. The ViewModel property and Class below
public const string MovieListPropertyName = "MovieList";
private ObservableCollection<Movie> _movieList;
public ObservableCollection<Movie> MovieList
{
get
{
return _movieList;
}
set
{
if (_movieList == value)
{
return;
}
RaisePropertyChanging(MovieListPropertyName);
_movieList = value;
RaisePropertyChanged(MovieListPropertyName);
}
}
public const string SelectedCastMemberPropertyName = "SelectedCastMember";
private MovieCastMember _selectedCastMember;
public MovieCastMember SelectedCastMember
{
get
{
return _selectedCastMember;
}
set
{
if (_selectedCastMember == value)
{
return;
}
RaisePropertyChanging(SelectedCastMemberPropertyName);
_selectedCastMember = value;
RaisePropertyChanged(SelectedCastMemberPropertyName);
}
}
With the Movie and MovieCastMember classes as follows
public class Movie
{
public int Id { get; set; }
public string Name { get; set; }
public int Year { get; set; }
public string Overview { get; set; }
public double VoteAverage { get; set; }
public ObservableCollection<MovieCastMember> Cast { get; set; }
public BitmapImage PosterImage { get; set; }
}
public class MovieCastMember
{
public int Id { get; set; }
public string Name { get; set; }
}
I want to select a cast member in any of the Movie Lists and bind the MovieCastMember object to a property in my ViewModel. My List boxes populates fine, I have tried various scenarios in XAML but the SelectedItem are not updating the property in the ViewModel. Any help would be appreciated.
That's because you're binding against Movie class in second listBox. There is no "SelectedCastMember". Move it to Movie class and it will work.

ObservableCollection and DisplayMemberBinding

I'm trying to fill ListBox by ObservableCollection. But when I add new item nothing displayed, only empty item adding.
There are fragments of my code:
XAML
<ListView ItemsSource="{Binding Points}" SelectedItem="{Binding Point}">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header ="X" Width="100" DisplayMemberBinding = "{Binding Path=ValueX, Mode=TwoWay}" />
<GridViewColumn Header ="Y" Width="100" DisplayMemberBinding = "{Binding Path=ValueY, Mode=TwoWay}"/>
</GridView>
</ListView.View>
</ListView>
Window class
var value = new Value();
var viewModel = new ViewModel(value);
DataContext = viewModel;
InitializeComponent();
Value class
private const Point POINT = null;
private readonly ObservableCollection<Point> _points = new ObservableCollection<Point>();
public Value() {
Point = POINT;
Points = _points;
}
public Point Point { get; set; }
public ObservableCollection<Point> Points { get; private set; }
public double ValueX { get; set; }
public int ValueY { get; set; }
ViewModel class
private readonly Value _value;
public ViewModel(Value value) {
_value = value;
}
public Point Point {
get { return _value.Point; }
set {
_value.Point = value;
OnPropertyChanged("Point");
}
}
public ObservableCollection<Point> Points {
get { return _value.Points; }
}
private RelayCommand _addCommand;
public ICommand AddCommand {
get {
if (_addCommand == null) {
_addCommand = new RelayCommand(Add);
}
return _addCommand;
}
}
private void Add(object obj) {
Points.Add(new Point(ValueX, ValueY));
ValueX = 0;
ValueY = 0;
}
public double ValueX {
get {
return _value.ValueX;
}
set {
if(Math.Abs(_value.ValueX - value) < Mathematics.EPSILON) return;
_value.ValueX = value;
OnPropertyChanged("ValueX");
}
}
public int ValueY {
get { return _value.ValueY; }
set {
if(_value.ValueX == value) return;
_value.ValueY = value;
OnPropertyChanged("ValueY");
}
}
and Point class
public class Point {
public readonly double ValueX;
public readonly double ValueY;
public Point(double valueX, double valueY) {
ValueX = valueX;
ValueY = valueY;
}
public override string ToString() {
return (ValueX + " " + ValueY);
}
}
When i try to add new item, new item is added but nothing is displayed. What reason can be here?
Since you bind ItemsSource to ObservableCollection<Point> it means that each item is of a Point type which has ValueX and ValueY declared as fields which are not valid binding source. Change them to properties:
public double ValueX { get; private set; }
public double ValueY { get; private set; }
Besides you use Mode=TwoWay for something that is read only. This should be changed to OneWay. If you want to leave TwoWay binding then remove private from the setter but then also you'll need to change GridViewColumn.CellTemplate to be some TextBox instead of using DisplayMemberBinding which is for display only.

Binding to ItemsSource not working until visual element is manually inspected (MVVM)

I have the Xaml which should basically bind a set of ContextualButtons for a selected tab's viewmodel to the ItemsSource property of the ToolBar. For some reason, this binding is not actually occuring unless I use Snoop to inspect the element manually...It seems that the act of snooping the element is somehow requerying the binding somehow.
Does anyone know what I might be doing wrong here? This behavior is the same if I use a Listbox as well, so I know it is something that I am doing incorrectly...but I am not sure what.
SelectedView is a bound property to the selected view from a Xam Tab control.
XAML
<ToolBar DataContext="{Binding SelectedView.ViewModel}"
ItemsSource="{Binding ContextualButtons}" >
<ToolBar.ItemTemplate>
<DataTemplate>
<!-- <Button ToolTip="{Binding Name}"-->
<!-- Command="{Binding Command}">-->
<!-- <Button.Content>-->
<!-- <Image Width="32" Height="32" Source="{Binding ImageSource}"/>-->
<!-- </Button.Content>-->
<!-- </Button>-->
<Button Content="{Binding Name}"/>
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
Code
public class TestViewModel : BaseViewModel, IBulkToolViewModel
{
public TestViewModel()
{
ContextualButtons = new ObservableCollection<IContextualButton>()
{
new ContextualButton("Test Button",
new DelegateCommand<object>(
o_ => Trace.WriteLine("Called Test Button")), String.Empty)
};
}
public string Key { get; set; }
private ObservableCollection<IContextualButton> _contextualButtons;
public ObservableCollection<IContextualButton> ContextualButtons
{
get { return _contextualButtons; }
set
{
if (_contextualButtons == value) return;
_contextualButtons = value;
//OnPropertyChanged("ContextualButtons");
}
}
}
public partial class TestView : UserControl, IBulkToolView
{
public TestView()
{
InitializeComponent();
}
public IBulkToolViewModel ViewModel { get; set; }
}
public class ContextualButton : IContextualButton
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public ICommand Command { get; set; }
public string ImageSource { get; set; }
public ContextualButton(string name_, ICommand command_, string imageSource_)
{
Name = name_;
Command = command_;
ImageSource = imageSource_;
}
}
public class BulkToolShellViewModel : BaseViewModel, IBaseToolShellViewModel, IViewModel
{
private IBulkToolView _selectedView;
public IBulkToolView SelectedView
{
get
{
return _selectedView;
}
set
{
if (_selectedView == value) return;
_selectedView = value;
OnPropertyChanged("SelectedView");
}
}
public ObservableCollection<IBulkToolView> Views { get; set; }
public DelegateCommand<object> AddViewCommand { get; private set; }
public DelegateCommand<object> OpenPortfolioCommand { get; private set; }
public DelegateCommand<object> SavePortfolioCommand { get; private set; }
public DelegateCommand<object> GetHelpCommand { get; private set; }
public BulkToolShellViewModel(ObservableCollection<IBulkToolView> views_)
: this()
{
Views = views_;
}
public BulkToolShellViewModel()
{
Views = new ObservableCollection<IBulkToolView>();
AddViewCommand = new DelegateCommand<object>(o_ => Views.Add(new TestView
{
ViewModel = new TestViewModel()
}));
OpenPortfolioCommand = new DelegateCommand<object>(OpenPortfolio);
SavePortfolioCommand = new DelegateCommand<object>(SavePortfolio);
GetHelpCommand = new DelegateCommand<object>(GetHelp);
}
private void GetHelp(object obj_)
{
}
private void SavePortfolio(object obj_)
{
}
private void OpenPortfolio(object obj_)
{
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged()
{
throw new NotImplementedException();
}
public void RaisePropertyChanged(string propertyName)
{
throw new NotImplementedException();
}
public string this[string columnName]
{
get { throw new NotImplementedException(); }
}
public string Error { get; private set; }
public AsyncContext Async { get; private set; }
public XmlLanguage Language { get; private set; }
public string Key { get; set; }
}
Thanks!
Why does BulkToolShellViewModel have its own PropertyChanged event along with RaisePropertyChanged methods that do nothing? Shouldn't it inherit this functionality from BaseViewModel? Perhaps the UI is attaching to BulkToolShellViewModel.PropertyChanged rather than BaseViewModel.PropertyChanged and is never being notified of changes.

Resources