<Stackpanel>
<TextBox x:Name="txtid" Width="90" Text={Binding Name} Height="25"/>
<TextBox x:Name="txtname" Width="90" Text={Binding Age} Height="25" Margin="0 10 0 10"/>
<Button Command={Binding AddCommand} Content="Add"/>
<ListView ItemsSource={Binding StudentList}/>
</Stackpanel>
ViewModel
public class StudentViewModel : INotifyPropertyChanged
{
public StudentViewModel()
{
_studentList = new ObservableCollection<StudentDetails>();
LoadCommand();
}
private ObservableCollection<StudentDetails> _studentList;
public ObservableCollection<StudentDetails> StudentList
{
get { return _studentList; }
set
{
_studentList = value;
OnPropertyChanged("StudentList");
}
}
public StudentDetails SelectedItems { get; set; }
private string _name;
private int _age;
public string Name
{
get { return _name;}
set { _name = value; OnPropertyChanged("Name")}
}
public string Age
{
get { return _age;}
set { _age = value; OnPropertyChanged("Age")}
}
public ICommand AddCommand { get; set; }
public void LoadCommand()
{
AddCommand = new CustomCommand(Add, CanAdd);
}
private bool CanAdd(object obj)
{
return true;
}
private void Add(object obj)
{
StudentList.Add(new StudentDetails { Name = Name, Age = Age });
}}
Model
public class StudentDetails : INotifyPropertyChanged
{
private string _name;
private int _age;
public string Name
{
get { return _name;}
set { _name = value; OnPropertyChanged("Name")}
}
public string Age
{
get { return _age;}
set { _age = value; OnPropertyChanged("Age")}
}}
I have two textbox and a listview like above. how to do two way binding using MVVM?? which means the entered textbox value should add to listview and if i select the values in the listview then the selected value should bind to the same textbox so that i can update the values. how to do it??
I have tried to run your code. It has lots of errors. Anyways from your question I think you want to have a listview and when user selects a particular list item, the corresponding age and name is displayed in text boxes and if user want to update the data, you want to add it to the list. First create a list view with data template that binds to the class file properties.
Now also create a StudentDetails object and bind the SelectedItem of the list view to it.
When user selects a list item, you get SelectionChanged event. During this time update the property of 2 text boxes to display the selected list items data in them.
Now in the add button event handler, update the list data for corresponding selected item. Make sure you bind the listitemssource to an ObservableCollection
I would have different view models for an individual student and for the student list. Name and Age properties don't actually belong to the list.
I used MVVM Light syntax for the example:
StudentViewModel
public class StudentViewModel : ViewModelBase
{
private string _name;
private int _age;
public string Name
{
get { return _name; }
set { Set<string>(ref _name, value); }
}
public int Age
{
get { return _age; }
set { Set<int>(ref _age, value); }
}
}
StudentView.xaml
<UserControl x:Class="MasterDetailExample.Views.StudentView"
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:local="clr-namespace:MasterDetailExample.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MasterDetailExample.ViewModel"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<WrapPanel HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBlock Text="Name: "/>
<TextBox Text="{Binding Name}" Width="150"/>
<TextBlock Text="Age: "/>
<TextBox Text="{Binding Age}" Width="20"/>
</WrapPanel>
</UserControl>
Now StudentsViewModel represents the student list:
public class StudentsViewModel : ViewModelBase
{
private ObservableCollection<StudentViewModel> _studentList;
private StudentViewModel _selectedStudent;
public StudentsViewModel()
{
StudentList = new ObservableCollection<StudentViewModel>();
StudentList.Add(new StudentViewModel { Name = "Joe", Age = 21 });
StudentList.Add(new StudentViewModel { Name = "Jane", Age = 19 });
}
public ObservableCollection<StudentViewModel> StudentList
{
get { return _studentList; }
private set { _studentList = value; }
}
public StudentViewModel SelectedStudent
{
get { return _selectedStudent; }
set { Set<StudentViewModel>(ref _selectedStudent, value); }
}
}
** List view, StudentsView**
<UserControl x:Class="MasterDetailExample.Views.StudentsView"
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:views="clr-namespace:MasterDetailExample.Views"
xmlns:vm="clr-namespace:MasterDetailExample.ViewModel"
d:DesignHeight="300"
d:DesignWidth="500"
mc:Ignorable="d">
<UserControl.Resources>
<vm:StudentsViewModel x:Key="StudentsVm" />
</UserControl.Resources>
<DockPanel DataContext="{StaticResource StudentsVm}">
<ListView DockPanel.Dock="Left" Width="100" ItemsSource="{Binding StudentList}" SelectedItem="{Binding SelectedStudent}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Separator />
<views:StudentView DockPanel.Dock="Right" DataContext="{Binding SelectedStudent}"/>
</DockPanel>
</UserControl>
Directly setting the DataContext kind of smells,
<views:StudentView DockPanel.Dock="Right" DataContext="{Binding SelectedStudent}"/>
In more complicated example, you would either create a DependencyProperty for the SelectedStudent, or implement some messaging logic to communicate between different view models.
Related
I'm developing a uwp app written in C#. My app uses prism.windows.
The app has two combo boxes. The first one is the selection of countries. The next one shows cities of the country which is selected in the first combo box.
I select Japan in the first combo box, Tokyo is shown in the next combo box. Next I select United States, Tokyo is still shown in the next one.
Chicago and Dayton should be shown in the next one.
namespace TestComboBox2.ViewModels
{
public class MainPageViewModel:BindableBase
{
public Views.MainPage View { get; private set; } = null;
public void Initialize(Views.MainPage mainPage)
{
View = mainPage;
}
public MainPageViewModel()
{
ChangeGroupId();
}
private List<CbGroup> CbGroupList = new List<CbGroup>();
public List<CbGroup> CbGroupLists
{
get { return CbGroupList; }
set { this.SetProperty(ref this.CbGroupList, value); }
}
private List<CbItem> CbItemList = new List<CbItem>();
public List<CbItem> CbItemLists
{
get { return CbItemList; }
set { this.SetProperty(ref this.CbItemList, value); }
}
private string txGroupId;
public string TxGroupId
{
get { return txGroupId; }
set
{
this.SetProperty(ref this.txGroupId, value);
ChangeItemId(txGroupId);
}
}
private string txItemId;
public string TxItemId
{
get { return txItemId; }
set { this.SetProperty(ref this.txItemId, value); }
}
private void ChangeGroupId()
{
CbGroupList.Add(new CbGroup("A", "Japan"));
CbGroupList.Add(new CbGroup("B", "United States"));
CbGroupList.Add(new CbGroup("C", "CANADA"));
}
private void ChangeItemId(string ValueId)
{
try
{
if (CbItemLists != null)
{
CbItemList.Clear();
}
TxItemId = null;
//
switch (ValueId)
{
case "A":
CbItemList.Add(new CbItem("A1", "Tokyo"));
break;
case "B":
CbItemList.Add(new CbItem("B1", "Chicago"));
CbItemList.Add(new CbItem("B2", "Dayton"));
break;
case "C":
CbItemList.Add(new CbItem("C1", "Toronto"));
CbItemList.Add(new CbItem("C2", "Halifax"));
CbItemList.Add(new CbItem("C3", "Edmonton"));
break;
}
}catch(Exception ex)
{
string stErrMessage = ex.Message;
}
}
}
public class CbGroup
{
public string GroupId { get; set; }
public string GroupName { get; set; }
public CbGroup(string ValueId, string ValueName)
{
GroupId = ValueId;
GroupName = ValueName;
}
}
public class CbItem
{
public string ItemId { get; set; }
public string ItemName { get; set; }
public CbItem(string ValueId, string ValueName)
{
ItemId = ValueId;
ItemName = ValueName;
}
}
}
<Page
x:Class="TestComboBox2.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestComboBox2"
xmlns:views="using:TestComboBox2.Views"
xmlns:viewmodels="using:TestComboBox2.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Style.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource TextBlckFontSize}"
Text="Country"
Foreground="Black"
/>
<ComboBox Style="{StaticResource ComboBoxStype}"
ItemsSource="{Binding CbGroupLists,Mode=OneWay}"
SelectedValue="{Binding TxGroupId,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="GroupId"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding GroupName,Mode=OneWay}"/>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource TextBlckFontSize}"
Foreground="Black"
Text="City"
/>
<ComboBox Style="{StaticResource ComboBoxStype}"
ItemsSource="{Binding CbItemLists,Mode=OneWay}"
SelectedValue="{Binding TxItemId,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="ItemId"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding ItemName,Mode=OneWay}"/>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
</Grid>
</Page>
The second combo box shows the city of the country which is selected in the first combo box.
Conditional combo boxes are not working properly
Please change the type of CbItemList to ObservableCollection that represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. It will work.
private ObservableCollection<CbItem> CbItemList = new ObservableCollection<CbItem>();
public ObservableCollection<CbItem> CbItemLists
{
get { return CbItemList; }
set { this.SetProperty(ref this.CbItemList, value); }
}
Well, You should use ObservableCollection instead of List, because ObservableCollection is a type collection that provides notifications when items get added or removed. ObservableCollection class have own implementation of INotifyPropertyChanged. So when this collection has some changes, It reflects or notifies to UI. So do some modification you code
private ObservableCollection<CbItem> CbItemList
public ObservableCollection<CbItem> CbItemLists
{
get { return CbItemList ?? (CbItemList = new ObservableCollection<CbItem>()); }
set { this.SetProperty(ref this.CbItemList, value); }
}
I have a ComboBox where a user can select what JobType they are working on. The ComboBox has a list of AllJobTypes. The problem stems from when a user adds a new JobType, I add the JobType to the AllJobTypes ObservableCollection, then sort it. When the sorting happens the ComboBox get's de-selected and not really sure why. The JobConfig.SelectedJobType.Name never changes in this process. Is there a way to sort an observable collection where it doesn't break the ComboBox?
public class JobTypeList : ObservableCollection<JobType> {
public void SortJobTypes() {
var sortableList = new List<JobType>(this);
sortableList.Sort((x, y) => x.Name.CompareTo(y.Name));
//this works but it creates a bug in the select for JobTypes
for (int i = 0; i < sortableList.Count; i++) {
this.Move(this.IndexOf(sortableList[i]), i);
}
}
And in the XAML
<ComboBox Grid.Column="0" SelectionChanged="JobTypeComboBox_SelectionChanged"
Name="JobTypeComboBox"
ItemsSource="{Binding Path=AllJobTypes}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding
Path=JobConfig.SelectedJobType.Name}" />
Instead of sorting the collection in the view model, you should bind the ComboBox's ItemsSource to a CollectionViewSource, where you can specify a SortDescription:
<Window ...
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
...>
<Window.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding AllJobTypes}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<ComboBox ItemsSource="{Binding Source={StaticResource cvs}}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding JobConfig.SelectedJobType.Name}"/>
...
</Window>
For further information see How to: Sort and Group Data Using a View in XAML
Here's a version using ItemsSource/SelectedItem. Note that you can add a new item to the list and sort it without losing the currently selected item in the view.
The window
<Window
x:Class="SortingAList.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:SortingAList"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox
Text="{Binding NewJobType, Delay=1000}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="200" />
<ComboBox
Grid.Row="1"
ItemsSource="{Binding JobTypes}"
SelectedItem="{Binding SelectedJobType}"
DisplayMemberPath="Name"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="200" />
</Grid>
</Window>
The code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify([CallerMemberName]string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class ViewModel : Notifier
{
private JobType _selectedJobType;
private string _newJobType;
public JobTypeList JobTypes { get; private set; }
public JobType SelectedJobType { get => _selectedJobType; set { _selectedJobType = value; Notify(); } }
public string NewJobType { get => _newJobType; set { _newJobType = value; Notify(); AddNewJobType(value); } }
public ViewModel()
{
JobTypes = new JobTypeList();
JobTypes.Add(new JobType { Name = "Butcher" });
JobTypes.Add(new JobType { Name = "Baker" });
JobTypes.Add(new JobType { Name = "LED maker" });
}
private void AddNewJobType(string name)
{
if(JobTypes.Any(x => x.Name == name)) return;
JobTypes.Add(new JobType { Name = name });
JobTypes.SortJobTypes();
}
}
public class JobType : Notifier
{
private string _name;
public string Name { get => _name; set { _name = value; Notify(); } }
}
Using your JobTypesList
public class JobTypeList : ObservableCollection<JobType>
{
public void SortJobTypes()
{
var sortableList = new List<JobType>(this);
sortableList.Sort((x, y) => x.Name.CompareTo(y.Name));
//this works but it creates a bug in the select for JobTypes
for(int i = 0; i < sortableList.Count; i++)
{
this.Move(this.IndexOf(sortableList[i]), i);
}
}
}
I am trying to have a WPF window with a ComboBox, with which you select an item, and then use TextBoxes below to edit the properties of the currently selected item, eg Name and Age.
How do I do this with Data Binding, ie. to bind the name TextBox to the Name property of the currently selected item in the ComboBox?
My XAML is
<Window x:Class="BindingTest001.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>
<TextBox HorizontalAlignment="Left" Height="23" Margin="10,75,0,0" TextWrapping="Wrap" Text="{Binding Path=CURR.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="497"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="10,103,0,0" TextWrapping="Wrap" Text="{Binding Path=CURR.Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
<ComboBox ItemsSource="{Binding Path=MO}" SelectedValue="{Binding Path=CURR, Mode=TwoWay}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="497" Name="MyComboBox"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="322,181,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</Window>
Where my code behind is
public partial class MainWindow : Window
{
ObservableCollection<myObj> mo;
public MainWindow()
{
InitializeComponent();
mo = new ObservableCollection<myObj>();
mo.Add(new myObj("Test1", 2));
var ct = new MainWindowDatacontext(this);
this.DataContext = ct;
this.MyComboBox.SelectedIndex = 0;
}
private class MainWindowDatacontext : INotifyPropertyChanged
{
MainWindow parent;
myObj curr;
public MainWindowDatacontext(MainWindow mainWindow)
{
this.parent = mainWindow;
}
public ObservableCollection<myObj> MO
{
get
{
return parent.mo;
}
}
public myObj CURR
{
get
{
return curr;
}
set
{
this.curr = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void executePropertyChanged(string s)
{
if(PropertyChanged!=null)
{
PropertyChanged(this.parent, new PropertyChangedEventArgs(s));
}
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
mo.Add(new myObj("Test2", 10));
}
}
But the Data Binding only works for the ComboBox- the TextBoxes never bind to anything.
myObj is very simple:
public class myObj : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void propertyChanged(string s)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(s));
}
}
string name;
public string Name
{
get { return name; }
set {
name = value;
propertyChanged("Name");
}
}
int age;
public myObj(string p1, int p2)
{
this.name = p1;
this.age = p2;
}
public int Age
{
get { return age; }
set { age = value;
propertyChanged("Age");
}
}
public override string ToString()
{
return String.Format("{0}, {1}", name, age);
}
}
Because you want to bind to the property of another element in your application you should use Binding.ElementName Property. So Change your TextBox's Binding like this:
Text="{Binding SelectedItem.Name, ElementName=MyComboBox,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Text="{Binding SelectedItem.Age, ElementName=MyComboBox,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
You are using SelectedValue in your ComboBox rather than SelectedItem.
The former is for when you want to select one property out of the object in the ComboBox's ItemsSource rather than the object itself. It's used in conjunction with the SelectedValuePath.
As you want to edit more than one property of the selected item you need to use SelectedItem.
I am currently attempting to display items from an ObservableCollection(myClass). The class itself just has some public string properties. I know that the collection is being updated from a stream source correctly but for some reason it's not updating the list box with the properties I want it to. It's very likely that my XAML has some error in it:
<Window x:Class="PoSClientWPF.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 Margin="10">
<ListBox x:Name="pumpListBox" ItemsSource="{Binding PumpCollection}" Grid.IsSharedSizeScope="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="ID" />
<ColumnDefinition SharedSizeGroup="State" />
</Grid.ColumnDefinitions>
<TextBlock Margin="2" Text="{Binding pumpID}" Grid.Column="0"/>
<TextBlock Margin="2" Text="{Binding state}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
From researching other posts about this very error. I've included adding this.DataContext = this; to my MainWindow as well as having:
public ObservableCollection<PumpItem> PumpCollection
{
get { return pumpCollection; }
}
In order to bind the ItemsSource to it. I think there is an error in how I'm declaring the bindings in XAML but I'm not sure. I'm trying to add the properties pumpID and state to the listbox from the class instance.
The class pumpItem is shown below:
public enum pumpState
{
Available,
customerWaiting,
Pumping,
customerPaying
};
public enum fuelSelection
{
Petrol,
Diesel,
LPG,
Hydrogen,
None
};
public class PumpItem
{
public string pumpID;
public double fuelPumped;
public double fuelCost;
public fuelSelection selection;
public pumpState state;
public PumpItem(string _ID)
{
this.pumpID = _ID;
this.fuelPumped = 0;
this.fuelCost = 0;
this.selection = fuelSelection.None;
this.state = pumpState.Available;
}
}
Any pointers or help much appreciated.
You can't bind to fields. Change these to public properties
public class PumpItem
{
private string pumpID;
public string PumpId
{
get
{
return pumpId;
}
set
{
pumpId = value;
}
}
private double fuelPumped;
public double FuelPumped
{
get
{
return fuelPumped;
}
set
{
fuelPumped = value;
}
}
private double fuelCost;
public double FuelCost
{
get
{
return fuelCost;
}
set
{
fuelCost= value;
}
}
public fuelSelection selection;
public fuelSelection Selection
{
get
{
return selection;
}
set
{
selection = value;
}
}
public pumpState state;
public pumpState State
{
get
{
return state;
}
set
{
state = value;
}
}
public PumpItem(string _ID)
{
this.PumpID = _ID;
this.FuelPumped = 0;
this.FuelCost = 0;
this.Selection = fuelSelection.None;
this.State = pumpState.Available;
}
}
XAML
<TextBlock Margin="2" Text="{Binding PumpID}" Grid.Column="0"/>
<TextBlock Margin="2" Text="{Binding State}" Grid.Column="1"/>
Check the Output console for binding errors
I am new-bee at WPF, i am trying to populate my combox control which is there within my listbox
XAML :
<Window.Resources>
<DataTemplate x:Key="UserTemplate" >
<StackPanel Orientation="Horizontal" >
<ComboBox Name="rule" ItemsSource="{Binding}" DisplayMemberPath="DataContext.RuleType" Width="85" Height="20"
SelectedValuePath="DataContext.RuleType" SelectedValue="{Binding Path=DataContext.RuleType}"/>
<TextBlock Text="{Binding Path= Name1}" Width="85" Margin="5,5,5,5"></TextBlock>
<Button Content="Delete" Click="cmdDeleteUser_Clicked" Margin="5,5,5,5" />
<Button Content="Add" Click="cmdAddUser_Clicked" Margin="5,5,5,5" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Name="lbUsers" ItemsSource="{Binding }" ItemTemplate="{StaticResource UserTemplate}"/>
</Grid>
CODE BEHIND:
public ObservableCollection<User> Users;
ObservableCollection<Listdata> listeddata;
ObservableCollection<Records> Record;
public MainWindow()
{
InitializeComponent();
Users = new ObservableCollection<User>() {
new User() { Name = "", Age = "" },
};
DataboundListbox.Records record = new Records();
RuleType = record.record_Rule();
lbUsers.DataContext = Users;
}
private string _Name;
public string Name1
{
get { return _Name; }
set
{
if (value != _Name)
{
_Name = "John";
NotifyPropertyChanged("Name");
}
}
}
private List<string> _RuleType;
public List<string> RuleType
{
get { return _RuleType; }
set
{
if (value != _RuleType)
{
_RuleType = value;
NotifyPropertyChanged("RuleType");
}
}
}
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private void cmdDeleteUser_Clicked(object sender, RoutedEventArgs e)
{
Button cmd = (Button)sender;
if (cmd.DataContext is User)
{
User deleteme = (User)cmd.DataContext;
Users.Remove(deleteme);
}
}
private void cmdAddUser_Clicked(object sender, RoutedEventArgs e)
{
Button cmd = (Button)sender;
if (cmd.DataContext is User)
{
var addedUser = new User() { Name = "", Age = "" };
Users.Add(addedUser);
}
}
private List<string> _prp;
public List<string> prp
{
get { return _prp; }
set
{
if (value != _prp)
{
_RuleType = value;
NotifyPropertyChanged("prp");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
Before I can answer your question there are some confusions that should be cleared up.
If User has already a member named Name then what's Name1 in parent class for?
If RuleType is a list, how come it's set as the SelectedValue of your ComboBox, Shouldn't it be ComboBox.itemsSource instead? If it should, then where is the property defined to keep the ComboBox.SelectedValue?
How come there is an Add button inside the UserTemplate? Delete button is ok but i think Add belongs outside of the ListBox.
If i understand your issue correctly, then this is the solution I can think of.
Fisrt: User needs a property like SelectedRule to keep Combobox.SelectedItem:
public class User : INotifyPropertyChanged
{
// implementation of INotifyPropertyChanged
string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
int _age;
public int Age
{
get
{
return _age;
}
set
{
_age = value;
NotifyPropertyChanged("Age");
}
}
string _selectedRule;
public string SelectedRule
{
get
{
return _selectedRule;
}
set
{
_selectedRule = value;
NotifyPropertyChanged("SelectedRule");
}
}
}
Second: Your DataTemplate should change like this:
<Window.Resources>
<DataTemplate x:Key="UserTemplate" >
<StackPanel Orientation="Horizontal" >
<ComboBox Name="rule" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=RuleType}" DisplayMemberPath="." Width="85" Height="20"
SelectedItem="{Binding SelectedRule}"/>
<TextBlock Text="{Binding Path= Name}" Width="85" Margin="5,5,5,5"></TextBlock>
<Button Content="Delete" Click="cmdDeleteUser_Clicked" Margin="5,5,5,5" />
</StackPanel>
</DataTemplate>
</Window.Resources>
Finally the ListBox part changes as below:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Name="lbUsers" ItemsSource="{Binding}" ItemTemplate="{StaticResource UserTemplate}"/>
<Button Grid.Row="1" Content="Add" Click="cmdAddUser_Clicked" Margin="5,5,5,5" />
</Grid>
If you're gonna bring Add button out like the above code, then you should remove if (cmd.DataContext is User) from cmdAddUser_Clicked method.
Problem :
The main problem is on this two line:
{Binding Path=DataContext.RuleType}
{Binding Path= Name1}
Since you already declare your dataContext, DataContext.RuleType will causes the compiler to search for yourdatacontext.DataContext.RuleType which is obviously not the thing you want.
lbUsers.DataContext = Users;
Your data context is a collection of User class and does not contain Name1. Thus Binding Path=Name1 will return "property not found" error
Solution
In WPF, MVVM ( model view viewmodel) pattern is highly encouraged. One of its main feature is it seperate GUI logic from Business Logic, making the code cleaner and easier to maintain.
Step 1: Create a ViewModel
public class UserViewModel:INotifyPropertyChanged
{
private string name;
private string age;
private string rule;
private List<string> ruleType;
public String Name
{
get { return name; }
set { name = value; NotifyPropertyChanged("Name"); }
}
public String Age
{
get { return age; }
set { age = value; NotifyPropertyChanged("Age"); }
}
public String Rule
{
get { return rule; }
set { rule = value; NotifyPropertyChanged("Rule"); }
}
public List<string> RuleType
{
get { return ruleType; }
set { ruleType = value; NotifyPropertyChanged("RuleType"); }
}
public UserViewModel()
{
name = "name";
age = "";
ruleType = new List<string>();
}
#region NotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
}
Step 2 : Link your data context to the viewmodel
public MainWindow()
{
InitializeComponent();
Users = new ObservableCollection<UserViewModel>();
//setup your data here
//example:
UserViewModel userViewModel = new UserViewModel();
//populate your combobox here
userViewModel.RuleType.Add("rule1")
userViewModel.RuleType.Add("rule2");
userViewModel.RuleType.Add("rule3");
Users.Add(new UserViewModel());
lbUsers.DataContext = Users ;
}
Step 3 : Update your xaml
<Window.Resources>
<DataTemplate x:Key="UserTemplate" >
<StackPanel Orientation="Horizontal" >
<ComboBox Name="rule" ItemsSource="{Binding RuleType}" Width="85" Height="20"
SelectedValue="{Binding Rule}"/>
<TextBlock Text="{Binding Path= Name}" Width="85" Margin="5,5,5,5"></TextBlock>
<Button Content="Delete" Click="cmdDeleteUser_Clicked" Margin="5,5,5,5" />
<Button Content="Add" Click="cmdAddUser_Clicked" Margin="5,5,5,5" />
</StackPanel>
</DataTemplate>
</Window.Resources>
When i am typing, bahman already post a quite detailed answer.So i stopped here. If you require any explaination or solution from me just asked will do.
In future if you suspect any error regarding binding, you can search your output window.
If you see your output window you possibly will found this
System.Windows.Data Error: 40 : BindingExpression path error: 'DataContext' property not found on 'object' ''User' (HashCode=9080996)'. BindingExpression:Path=DataContext.RuleType; DataItem='User' (HashCode=9080996); target element is 'ComboBox' (Name=''); target property is 'SelectedValue' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'Name1' property not found on 'object' ''User' (HashCode=9080996)'. BindingExpression:Path=Name1; DataItem='User' (HashCode=9080996); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')