Models
public class EmployeeDetails
{
public string Name { get; set; }
public int Age {get;set;}
}
public class AddressDetails
{
public EmployeeDetails EmployeeName { get; set; }
public string City { get; set; }
}
View
<Window x:Class="ClassCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ClassCollection"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={StaticResource loc},Path=ViewModel}"
>
<Grid>
<StackPanel Margin="0 20 0 0">
<TextBox x:Name="txt1" Width="90" Height="20" Text="{Binding Details.EmployeeName}"/>
<TextBox x:Name="txt2" Width="90" Height="20" Text="{Binding Details.City}" Margin="0 20 0 0"/>
</StackPanel>
<Button x:Name="btn" Width="90" Height="25" Content="Add" Command=" {Binding AddCommand}"/>
</Grid>
</Window>
ViewModel
public class Viewmodel
{
public ObservableCollection<AddressDetails> EmployeeList;
public Viewmodel()
{
EmployeeList = new ObservableCollection<AddressDetails>();
LoadCommand();
}
private AddressDetails _details;
public AddressDetails Details
{
get { return _details; }
set
{
_details = value;
}
}
// Commands
public ICommand AddCommand { get; set; }
private void LoadCommand()
{
AddCommand = new CustomCommand(Add, CanAdd);
}
private bool CanAdd(object obj)
{
return true;
}
private void Add(object obj)
{
EmployeeList.Add(new AddressDetails { EmployeeName = Details.EmployeeName, City = Details.City });
}
}
Locator
public class Locator
{
private static Viewmodel viewmodel = new Viewmodel();
public static Viewmodel ViewModel
{
get { return viewmodel; }
}
}
How to add TextBox value to collection list using MVVM?
The Above is my code that I have tried. It shows null reference exception if I do like above. What would be the problem?
Update
I have two fields in EmployeeDetails class. So I must give input for these two field when add to collection. But I need only one field Name to insert to the collection. How to do it?
Analysis
It seems the _details field is not «initialized».
Solution
Please consider introducing the appropriate field initialization, for example:
private readonly AddressDetails _details = new AddressDetails
{
EmployeeName = new EmployeeDetails()
};
Related
when typing in the textbox and click "Add Employee", i want it to update and display to the datagrid, i've implemented INotifyPropertyChanged and RelayCommand. what am i missing that's not populating the data. thanks in advance
here is my model
public class EmployeeModel
{
public string Name { get; set; }
public int Pedicure { get; set; }
public int Tip { get; set; }
public int Total { get; set; }
}
this is my ViewModel
List<EmployeeModel> employeeModel = new List<EmployeeModel>() { };
private ICommand _addEmployeeCommand;
public ICommand AddEmployeeCommand
{
get
{
return _addEmployeeCommand ?? (_addEmployeeCommand = new RelayCommand(x => { AddNewEmployee(); }));
}
}
public List<EmployeeModel> Employee
{
get { return employeeModel; }
set
{
if(value != employeeModel)
{
employeeModel = value;
OnPropertyChanged("Employee");
}
}
}
private string employeeName;
public string EmployeeName
{
get { return employeeName; }
set
{
if (value != employeeName)
{
employeeName = value;
OnPropertyChanged("EmployeeName");
}
}
}
public void AddNewEmployee()
{
Employee.Add(new EmployeeModel { Name = EmployeeName });
}
here is my View
<TabItem Header="Employee">
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding Employee}">
</DataGrid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: "/>
<TextBox Text="{Binding EmployeeName}"
Width="40"
Height="15"
VerticalAlignment="Top"/>
<Button Content="Add"
Command="{Binding AddEmployeeCommand}"
Height="20"
VerticalAlignment="Top"/>
</StackPanel>
</StackPanel>
(I pluralized the name Employee to Employees in this answer for future readers)
The problem is with the Source of the DataGrid
Bear in mind that OnPropertyChanged("Employees") only notifies about the changes made to the Employees and is not responsible for any changes made within Employees.
To be clear, it only works when you do employeeModels = new List<EmployeeModel>()
And won't be called when Employees.Add(employee)
Hopefully WPF has its own ObservableCollection type that will take care of that:
private ObservableCollection<Employee> _employees = new ObservableCollection<Employee>;
public ObservableCollection<Employee> Employees { get { return _employees; } }
I'm binding a List of a custom class (which implements the INotifyPropertyChanged) into a ListBox.
When I add any item to the List, the ListBox doesn't update, but if I scroll, the ListBox gets updated.
The class
class MyClass : INotifyPropertyChanged
{
private string name = string.Empty;
public string Name { /* INotifyPropertyChanged */ }
}
The property
private List<MyClass> loaded;
public List<MyClass> Loaded { /* INorutyPropertyChanged */ }
The ListBox
<ListBox ItemsSource={Binding Loaded} />
If I force override the List property, it works fine:
Loaded = new List<MyClass>() { new MyClass { Name = "test"; } }
Update to:
public ObservableCollection<MyClass> Loaded { get; private set; }
and
<ListBox ItemsSource={Binding Loaded, UpdateSourceTrigger=PropertyChanged} />
Also, you do not need to use INotiftyPropertyChanged for your Loaded property. If the binding happens once, and the data-source does not change, there's no need.
Edit:
Here's a working example.
MainWindow.xaml
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Width="200" ItemsSource="{Binding Items, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Width="100" Height="75" HorizontalAlignment="Right" Content="Add" Command="{Binding AddItem}"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new DataContext();
}
}
DataContext.cs
namespace WpfApplication3
{
public class DataContext
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public DataContext()
{
Items = new ObservableCollection<Item>
{
new Item
{
Value = "test"
}
};
AddItem = new RelayCommand(() =>
{
Items.Add(new Item
{
Value = "new item"
});
}, () => true);
}
}
public class Item
{
public string Value { get; set; }
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
Let me know if you have any questions.
I have a ListBox which is bound to a List of DataModel.
DataModel.cs
public class DataModel
{
public string Name { get; set; }
public string Desc { get; set; }
public string Code { get; set; }
}
The ListBox should display two properties, so I have defined the ItemTemplate as below
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text=" - "></TextBlock>
<TextBlock Text="{Binding Code}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
My requirement is that the user can select which two properties to display in the ListBox at run time. I am not sure how to achieve this. I have created a sample solution to explain my problem.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="5">
<Label Content="Property One"></Label>
<ComboBox Name="ComboBox1" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"></ComboBox>
<Label Content="Property Two"></Label>
<ComboBox Name="ComboBox2" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"></ComboBox>
<Button Content="Go" Click="ButtonBase_OnClick" Margin="3"></Button>
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding DataModelList}" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text=" - "></TextBlock>
<TextBlock Text="{Binding Code}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
}
}
public class ViewModel
{
public List<String> DataModelProperties { get; set; }
public List<DataModel> DataModelList { get; set; }
public ViewModel()
{
DataModelList = new List<DataModel>();
DataModelList.Add(new DataModel() { Name = "Name1", Code = "Code1", Desc = "Desc1" });
DataModelList.Add(new DataModel() { Name = "Name2", Code = "Code2", Desc = "Desc2" });
DataModelList.Add(new DataModel() { Name = "Name3", Code = "Code3", Desc = "Desc3" });
DataModelProperties = typeof(DataModel).GetProperties().Select(s => s.Name).ToList();
}
}
public class DataModel
{
public string Name { get; set; }
public string Desc { get; set; }
public string Code { get; set; }
}
}
Things I have tried
<TextBlock Text="{Binding ElementName=ComboBox1, Path=SelectedItem}"></TextBlock>
This just displays the selected property name.
I suggest you to manage all the stuff regarding the "dynamic description" showed in the ListBox in your ViewModel.
First of all, your view model and your model should implement INotifyPropertyChanged. In my sample I created a simple base class which implements it. My base class is called NotifyPropertyChangedImpl.
Moreover I added two properties to the view model: the comboboxes for selecting the properties are binded to those two properties.
public class ViewModel : NotifyPropertyChangedImpl
{
private string property1;
private string property2;
public List<String> DataModelProperties { get; set; }
public List<DataModel> DataModelList { get; set; }
public string Property1
{
get
{
return property1;
}
set
{
if (property1 != value)
{
property1 = value;
SetDynamicDescriptions();
}
}
}
public string Property2
{
get
{
return property2;
}
set
{
if (property2 != value)
{
property2 = value;
SetDynamicDescriptions();
}
}
}
public ViewModel()
{
DataModelList = new List<DataModel>();
DataModelList.Add(new DataModel() { Name = "Name1", Code = "Code1", Desc = "Desc1" });
DataModelList.Add(new DataModel() { Name = "Name2", Code = "Code2", Desc = "Desc2" });
DataModelList.Add(new DataModel() { Name = "Name3", Code = "Code3", Desc = "Desc3" });
DataModelProperties = typeof(DataModel).GetProperties().Select(s => s.Name).ToList();
}
private void SetDynamicDescriptions()
{
PropertyInfo propertyInfo1;
PropertyInfo propertyInfo2;
Type type = typeof(DataModel);
if (!String.IsNullOrEmpty(property1) && !String.IsNullOrEmpty(property2))
{
propertyInfo1 = type.GetProperty(property1);
propertyInfo2 = type.GetProperty(property2);
foreach (DataModel dataModel in DataModelList)
{
dataModel.DynamicDescription = String.Format("{0} - {1}",
propertyInfo1.GetValue(dataModel, null), propertyInfo2.GetValue(dataModel, null));
}
}
}
}
As you can see the method SetDynamicDescriptions rebuilds the DynamicsDescription each time Property1 or Property2 changes.
I also added a property to the model class:
public class DataModel : NotifyPropertyChangedImpl
{
private string dynamicDescription;
public string Name { get; set; }
public string Desc { get; set; }
public string Code { get; set; }
public string DynamicDescription
{
get
{
return dynamicDescription;
}
set
{
if (dynamicDescription != value)
{
dynamicDescription = value;
OnPropertyChanged("DynamicDescription");
}
}
}
}
So at the end your XAML will be:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="5">
<Label Content="Property One"></Label>
<ComboBox Name="ComboBox1" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"
SelectedItem="{Binding Property1}"></ComboBox>
<Label Content="Property Two"></Label>
<ComboBox Name="ComboBox2" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"
SelectedItem="{Binding Property2}"></ComboBox>
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding DataModelList}" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DynamicDescription}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I hope this can help you.
In short, what I've done is I made 2 public properties to bind to and changed your properties to fields. Also I've made custom attribute, to control what fields user is able to select. And created a DisplayOptions class to store selection and spread it across DataModel instances.
The solution is a bit messy, considering it's a PoC, but I believe this can do:
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="5">
<Label Content="Property One"></Label>
<ComboBox Name="ComboBox1" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"></ComboBox>
<Label Content="Property Two"></Label>
<ComboBox Name="ComboBox2" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"></ComboBox>
<Button Content="Go" Click="ButtonBase_OnClick" Margin="3"></Button>
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding DataModelList}" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayProperty1}"></TextBlock>
<TextBlock Text=" - "></TextBlock>
<TextBlock Text="{Binding DisplayProperty2}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
CS:
public partial class MainWindow : Window
{
public ViewModel model = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = model;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
//This part has to be on the View side, but this is PoC
model.Opts.Prop1 = typeof(DataModel).GetFields()
.Where(a => a.Name == ComboBox1.SelectedItem.ToString()).FirstOrDefault();
model.Opts.Prop2 = typeof(DataModel).GetFields()
.Where(a => a.Name == ComboBox2.SelectedItem.ToString()).FirstOrDefault();
DataContext = null;
DataContext = model;
}
}
public class ViewModel
{
public DisplayOptions Opts = new DisplayOptions();
public List<String> DataModelProperties { get; set; }
public List<DataModel> DataModelList { get; set; }
public ViewModel()
{
var properties = typeof(DataModel).GetFields()
.Where(a => a.CustomAttributes.Any(b => b.AttributeType == typeof(SwitchableAttr)));
//Initialising options before creating DataModel instances
DataModelProperties = properties.Select(s => s.Name).ToList();
Opts.Prop1 = properties.ElementAt(0);
Opts.Prop2 = properties.ElementAt(1);
DataModelList = new List<DataModel>();
DataModelList.Add(new DataModel() { Name = "Name1", Code = "Code1", Desc = "Desc1", options = Opts });
DataModelList.Add(new DataModel() { Name = "Name2", Code = "Code2", Desc = "Desc2", options = Opts });
DataModelList.Add(new DataModel() { Name = "Name3", Code = "Code3", Desc = "Desc3", options = Opts });
}
}
public class DisplayOptions
{
public FieldInfo Prop1;
public FieldInfo Prop2;
}
public class DataModel
{
public DisplayOptions options;
[SwitchableAttr]
public string Name;
[SwitchableAttr]
public string Desc;
[SwitchableAttr]
public string Code;
public string DisplayProperty1 { get { return (string)options.Prop1.GetValue(this); } set { } }
public string DisplayProperty2 { get { return (string)options.Prop2.GetValue(this); } set { } }
}
public class SwitchableAttr : Attribute { }
This is not Complete MVVM
so here is the code
public partial class MainWindow : Window
{
private ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
this.DataContext = vm;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(ComboBox1.Text) && !string.IsNullOrEmpty(ComboBox2.Text))
{
vm.AddDataToModel(ComboBox1.Text, ComboBox2.Text);
}
}
}
public class ViewModel
{
public List<String> DataModelProperties { get; set; }
private ObservableCollection<DataModel> _DataModelList;
public ViewModel()
{
_DataModelList = new ObservableCollection<DataModel>();
DataModelProperties = typeof(DataModel).GetProperties().Select(s => s.Name).ToList();
}
public void AddDataToModel(string cmbx1Val,string cmbx2Val)
{
_DataModelList.Add(new DataModel() { Name = cmbx1Val, Code = cmbx2Val, Desc = "Desc1" });
}
public ObservableCollection<DataModel> DataModelList
{
get
{
return _DataModelList;
}
set
{
_DataModelList = value;
}
}
}
I have a PrimaryItems List & foreach PrimaryItem there is a SecondaryItems list.So i used a ListBox as ItempTemplate of another ListBox.
<ListBox ItemsSource="{Binding PrimaryItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding SecondaryItems}" SelectedItem="{Binding SelectedSecondaryItem}" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My View Model Code
private List<PrimaryItem> _primaryItems;
public List<PrimaryItem> PrimaryItems
{
get { return _primaryItems; }
set { _primaryItems = value;RaisePropertyChanged(); }
}
//SecondaryItems list is inside in each PrimaryItem
//private List<SecondaryItem> _secondaryItems;
//public List<SecondaryItem> SecondaryItems
//{
// get { return _secondaryItems; }
// set { _secondaryItems = value; RaisePropertyChanged(); }
//}
private SecondaryItem _selectedSecondaryItem;
public SecondaryItem SelectedSecondaryItem
{
get { return _selectedSecondaryItem; }
set
{
_selectedSecondaryItem = value;
if (_selectedSecondaryItem != null)
{
//TO DO
}
}
}<br/>
This is the class structure
public class PrimaryItem
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondaryItem> SecondaryItems{ get; set; }
}
public class SecondaryItem
{
public int Id { get; set; }
public string Name { get; set; }
}
and I set SelectedItem Binding to the Second ListBox.
But am not getting the Selection Trigger on Second ListBox.
Can we use a ListBox inside another ListBox` Template ? If yes how do we overcome this problem?
First of all, use ObservableCollection instead of List since it implements INotifyPropertyChanged interface.
As far as I understand your requirements, PrimaryItem class should has a property SecondaryItems. So remove it from ViewModel and paste to PrimaryItem class (as well as SelectedSecondaryItem property):
private ObservableCollection<SecondaryItem> _secondaryItems;
public ObservableCollection<SecondaryItem> SecondaryItems
{
get { return _secondaryItems; }
set { _secondaryItems = value; RaisePropertyChanged(); }
}
EDIT:
I've fully reproduced your situation and get it working.
Classes:
public class PrimaryItem
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondaryItem> SecondaryItems { get; set; }
private SecondaryItem _selectedSecondaryItem;
public SecondaryItem SelectedSecondaryItem
{
get { return _selectedSecondaryItem; }
set
{
_selectedSecondaryItem = value;
if (_selectedSecondaryItem != null)
{ // My breakpoint here
//TO DO
}
}
}
}
public class SecondaryItem
{
public int Id { get; set; }
public string Name { get; set; }
}
ViewModel:
public class MyViewModel : ViewModelBase
{
private List<PrimaryItem> _primaryItems;
public List<PrimaryItem> PrimaryItems
{
get { return _primaryItems; }
set { _primaryItems = value; RaisePropertyChanged("PrimaryItems"); }
}
public ErrorMessageViewModel()
{
this.PrimaryItems = new List<PrimaryItem>
{
new PrimaryItem
{
SecondaryItems =
new List<SecondaryItem>
{
new SecondaryItem { Id = 1, Name = "First" },
new SecondaryItem { Id = 2, Name = "Second" },
new SecondaryItem { Id = 3, Name = "Third" }
},
Name = "FirstPrimary",
Id = 1
}
};
}
}
View:
<Window x:Class="TestApp.Views.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TestApp.ViewModels;assembly=TestApp.ViewModels"
xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Title" Height="240" Width="270" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding PrimaryItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding SecondaryItems}" SelectedItem="{Binding SelectedSecondaryItem}" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
You can try to use LinqToVisualTree, it can get alomost all Controls in your app, you just have to select what you want to find(in your case ListBoxItem), and then cast it to your model. I used it, when I needed to get text from TextBox which was in ListboxItem. But it also fits to your task.
I have a listbox that is bound to an observablecollection. The observable collection contains a list of objects, each with it's own observablecollection. What i want is to click an item in the first listbox and have it's list of things displayed in the second listbox. Can I do this in pure WPF?
Just bind the ItemsSource of the second listbox to the SelectedItem of the first list box.
Edit: here is some code.
public partial class MainWindow : Window
{
public MainWindow()
{
TestItems = new ObservableCollection<Test>();
InitializeComponent();
for (int i = 0; i < 5; i++)
TestItems.Add(InitTest(i));
}
public ObservableCollection<Test> TestItems { get; set; }
private Test InitTest(int index)
{
Test test = new Test();
test.Name = "Test" + index.ToString();
test.Test2Items = new ObservableCollection<Test2>();
for (int i = 0; i <= index; i++)
{
Test2 test2 = new Test2();
test2.Label = test.Name + "_label" + i.ToString();
test.Test2Items.Add(test2);
}
return test;
}
}
public class Test
{
public string Name { get; set; }
public ObservableCollection<Test2> Test2Items { get; set; }
public override string ToString()
{
return Name;
}
}
public class Test2
{
public string Label { get; set; }
public override string ToString()
{
return Label;
}
}
Xaml
<Window x:Class="WpfApplication1.MainWindow"
x:Name="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Example" Height="300" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="ListBox1" Grid.Column="0" ItemsSource="{Binding TestItems, ElementName=MyWindow}" />
<ListBox Grid.Column="1" ItemsSource="{Binding SelectedItem.Test2Items, ElementName=ListBox1}" />
</Grid>
</Window>
Your view models could look something like this: (I am using my BindableBase here)
class MainViewModel : Bindablebase {
public ObservableCollection<ItemViewModel> Items { get; private set; }
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem {
get { return _selectedItem; }
set { SetProperty(ref _selectedItem, value, "SelectedItem"); }
}
}
class ItemViewModel : BindableBase {
public ItemViewModel (string name) {
Name = name;
Items = new ObservableCollection<string>();
}
public string Name { get; private set; }
public ObservableCollection<string> Values { get; private set; }
private string _selectedValue;
public string SelectedValue {
get { return _selectedValue; }
set { SetProperty(ref _selectedValue, value, "SelectedValue"); }
}
}
And then your view would have:
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<!--
Note that the DataContext here could be ommitted
and the bindings would be like {Binding SelectedItem.Values}
-->
<ComboBox DataContext="{Binding SelectedItem}"
ItemsSource="{Binding Values}"
SelectedItem="{Binding SelectedValue}"/>