I have a combobox that I set isEditable = true but when I press Down arrow from keyboard I get this message
MoalemYar.DataClass.DataTransferObjects+StudentsDto
Instead of my selected item, this is my combobox
<ComboBox
x:Name="cmbEditStudent"
IsEditable="True"
SelectedValue="{Binding LName}"
SelectedValuePath="Id">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LName}" />
<TextBlock Text=" - " />
<TextBlock Text="نام پدر(" />
<TextBlock Text="{Binding FName}" />
<TextBlock Text=")" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Try this hope it will helps you
<ComboBox Height="25" Margin="80,12,12,0" Name="comboBox1" VerticalAlignment="Top"
ItemsSource="{Binding StudentList}" IsEditable="True" TextSearch.TextPath="StudentName">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StudentName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
By default, the first item in a combo box is selected by the down arrow, then you can press up/down arrows keys to change the selected item accordingly.
Try the following code.
In XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox
x:Name="cmbEditStudent"
IsTextSearchEnabled="True"
TextSearch.TextPath = "Name"
IsEditable="True"
ItemsSource="{Binding StudentsList}"
SelectedItem="{Binding SelectedStudent}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LName}" />
<TextBlock Text=" - " />
<TextBlock Text="نام پدر(" />
<TextBlock Text="{Binding FName}" />
<TextBlock Text=")" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Width="200" Height="20" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
In its View Model
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Student> studentsList = new ObservableCollection<Student>();
public ObservableCollection<Student> StudentsList
{
get { return studentsList; }
set
{
studentsList = value;
NotifyPropertyChanged();
}
}
private Student selectedStudent;
public Student SelectedStudent
{
get { return selectedStudent; }
set
{
selectedStudent = value;
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
StudentsList.Add(new Student("Paul", "LName1", "FName1"));
StudentsList.Add(new Student("Alex", "LName2", "FName2"));
StudentsList.Add(new Student("Steve", "LName3", "FName3"));
}
#region Notify Property
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propName = null)
{
if (!string.IsNullOrWhiteSpace(propName))
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}));
}
}
#endregion
}
Student Class
public class Student : NotifiableBase
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged();
}
}
private string lName;
public string LName
{
get { return lName; }
set
{
lName = value;
NotifyPropertyChanged();
}
}
private string fName;
public string FName
{
get { return fName; }
set
{
fName = value;
NotifyPropertyChanged();
}
}
public Student(string name, string lName, string fName)
{
this.name = name;
this.lName = lName;
this.fName = fName;
}
}
NotifiableBase
public class NotifiableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propName = null)
{
if (!string.IsNullOrWhiteSpace(propName))
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}));
}
}
}
Related
I'm trying to find a way to add content to a tab page using the TabControl without creating new tabs. I have a ViewModel that holds the values for the tab header and tab content. Currently, when the 'Add Tab' button is clicked, it will add a new tab with the correct heading, however the tab content will have the data missing. I understand why my work doesn't work, which is why I would like to find out if it is possible to separate these two processes. I am new to WPF and would appreciate any help.
XAML:
<TabControl ItemsSource="{Binding}" Grid.Column="1" Grid.Row="1" Grid.RowSpan="5">
<TabControl.ItemTemplate>
<DataTemplate DataType="local:MyTab">
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="local:MyTab">
<StackPanel>
<TextBlock Text="First Name:" />
<TextBlock Binding="{Binding FirstName}" Margin="0,0,0,10"/>
<TextBlock Text="Second Name:" />
<TextBlock Binding="{Binding SecondName}" Margin="0,0,0,10"/>
<TextBlock Text="ID Number:" />
<TextBlock Binding="{Binding Id}" Margin="0,0,0,10"/>
<TextBlock Text="Age:" />
<TextBlock Binding="{Binding Age}" Margin="0,0,0,10"/>
<TextBlock Text="Gender:" />
<TextBlock Binding="{Binding Gender}" Margin="0,0,0,10"/>
<TextBlock Text="Address:" />
<TextBlock Binding="{Binding Address}" Margin="0,0,0,10"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
C#:
public partial class MainWindow : Window
{
ObservableCollection<MyTab> tabs = new ObservableCollection<MyTab>();
string firstName;
string secondName;
string id;
int age;
string gender;
string address;
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
firstName = firstNameTxtBox.Text;
secondName = surnameTxtBox.Text;
var tab = new MyTab() { Header = firstName + " " + secondName };
tabs.Add(tab);
DataContext = tabs;
firstNameTxtBox.Clear();
surnameTxtBox.Clear();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
id = idTxtBox.Text;
age = Convert.ToInt32(ageTxtBox.Text);
gender = genderTxtBox.Text;
address = addressTxtBox.Text;
var tab = new MyTab();
tab.Data.Add(new MyTabData() { FirstName = firstName, SecondName = secondName, Id = id, Age = age, Gender = gender, Address = address });
tabs.Add(tab);
DataContext = tabs;
idTxtBox.Clear();
ageTxtBox.Clear();
genderTxtBox.Clear();
addressTxtBox.Clear();
}
}
As I understood, MyTab is your class, that will look like that :
public class MyTab : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
{
if (object.Equals(variable, valeur)) return false;
variable = valeur;
NotifyPropertyChanged(nomPropriete);
return true;
}
private string name = "";
public string Name
{
get { return this.name; }
set
{
if (value != null && this.name != value)
{
this.name = value;
this.NotifyPropertyChanged("Name");
}
}
}
private string surname = "";
public string Surname
{
get { return this.surname; }
set
{
if (value != null && this.surname != value)
{
this.surname = value;
this.NotifyPropertyChanged("Surname");
}
}
}
// firsName, Id and so on...
public MyTab()
{
}
}
First of all, your class must be INotifyPropertyChanged so the binding of TextBoxes will work.
Then your MainWindows : Window, INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
private ObservableCollection<MyTab> listMyTab { get; set; } = new ObservableCollection<MyTab>();
public ObservableCollection<MyTab> ListMyTab { get { return this.listMyTab; } set { this.listMyTab = value; this.NotifyPropertyChanged("ListMyTab"); } }
public MainWindow()
{
InitializeComponent();
this.DataContext=this;
}
You must set the dataContext (if you ant to do it properly (MVVM), you may set the context in a different file named ViewModelMainWindows.cs for example.
The ViewModel part :
private ObservableCollection<MyTab> listMyTab { get; set; } = new ObservableCollection<MyTab>();
public ObservableCollection<MyTab> ListMyTab { get { return this.listMyTab; } set { this.listMyTab = value; this.NotifyPropertyChanged("ListMyTab"); } }
Then xaml look like that :
<TabControl ItemsSource="{Binding ListMyTab}" Grid.Column="1" Grid.Row="1" Grid.RowSpan="5">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Label Grid.Column="1" Content="{Binding FirstName}" Margin="3" />
</Grid>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="local:MyTab">
<StackPanel>
<Label Content="First Name:" />
<TextBlock Binding="{Binding FirstName}" Margin="0,0,0,10"/>
<Label Content="Second Name:" />
<TextBlock Binding="{Binding SecondName}" Margin="0,0,0,10"/>
<Label Content="ID Number:" />
<TextBlock Binding="{Binding Id}" Margin="0,0,0,10"/>
<Label Content="Age:" />
<TextBlock Binding="{Binding Age}" Margin="0,0,0,10"/>
<Label Content="Gender:" />
<TextBlock Binding="{Binding Gender}" Margin="0,0,0,10"/>
<Label Content="Address:" />
<TextBlock Binding="{Binding Address}" Margin="0,0,0,10"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
TabControl's content is linked to ListMyTab (one TabItem for each object MyTab).
Then the content of each TabItem is binded to each item.
So when you will edit FirstName in the TextBlock (I would use TextBox instead), the header will be automaticaly updated (this is Binding power).
If you want to add a new item, then add somewhere a button, with that :
private void Button_Add_Click(object sender, RoutedEventArgs e)
{
this.ListMyTab.Add(new MyTab());//add default values if necessary
}
It will add an item in observable collection, then a new tab will appear.
Also for your tab appearance, I'd advise you to have a look at that :
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
In the application there is a Listbox bound to an ObservableCollection, then the selected item is bound itself to some labels: when a property of the item is changed in the label the actual object (in this case Multimedia) is updated (as I debugged) but then the listbox doesn't update the displayed value.
The Multimedia class implements INotifyPropertyChanged but I'm not sure if I am using it correctly.
Everything else is working without any problem (the add button adds a new element to the list and it is displayed as it should).
I looked around on different forums and also on stackoverflow and tried different variants but still the property, when updated, it is not updated in the ListBox.
This is the XMAL:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="135" />
<RowDefinition Height="*" />
<RowDefinition Height="45" />
</Grid.RowDefinitions>
<ListBox Name="mediaListBox" ItemsSource="{Binding Path=MyData}" Grid.Row="0"/>
<Grid Grid.Row="1" DataContext="{Binding ElementName=mediaListBox, Path=SelectedItem}">
...
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=Title}" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=Artist}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=Genre}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Path=Type}" />
</Grid>
<Button Name="cmdAddMedia" Grid.Row="1" Click="cmdAddMedia_Click" Height="45" Margin="0,0,0,2" Grid.RowSpan="2" VerticalAlignment="Bottom">Add Item</Button>
</Grid>
Then here there is the C# code of the main window:
public partial class MainWindow : Window
{
public MultiMediaList MyData { get; set; }
public void AddStuff()
{
MyData.Add(new Multimedia() { Title = "My Way", Artist = "Calvin Harris", Genre = "Pop", Type = Multimedia.MediaType.CD });
MyData.Add(new Multimedia() { Title = "Inglorious Bastards", Artist = "Quentin Tarantino", Genre = "Violence", Type = Multimedia.MediaType.DVD });
}
public MainWindow()
{
MyData = new MultiMediaList();
AddStuff();
InitializeComponent();
DataContext = this;
}
...
}
And finally the Multimedia class and the MultiMediaList class:
public class Multimedia : INotifyPropertyChanged
{
public enum MediaType { CD, DVD };
private string _title;
private string _artist;
private string _genre;
private MediaType _type;
public string Title
{
get { return _title; }
set
{
_title = value;
NotifyPropertyChanged("Title");
}
}
...
public override string ToString()
{
return _title + " - " + _artist + " [" + _genre + "] - " + getTypeString();
}
private string getTypeString()
{
if(Type == MediaType.CD) { return "CD"; }
else { return "DVD"; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
MultimediaList is just an empty class inheriting from ObservableCollection
public class MultiMediaList: ObservableCollection<Multimedia>
{
}
If you need I can also upload the full code
Hope you can help me and tell me what I am doing wrong.
Apparently you are expecting that the ListBox automagically calls the Multimedia object's ToString() method whenever one if its properties changes. That's not the case.
Instead of relying on ToString, declare a proper ItemTemplate for the ListBox:
<ListBox Name="mediaListBox" ItemsSource="{Binding MyData}" Grid.Row="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Title}"/>
<Run Text="-"/>
<Run Text="{Binding Artist}"/>
<Run Text="["/><Run Text="{Binding Genre}"/><Run Text="]"/>
<Run Text="{Binding Type}"/>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The TextBlock might be written shorter:
<TextBlock>
<Run Text="{Binding Title}"/> - <Run Text="{Binding Artist}"/> [<Run Text="{Binding Genre}"/>] <Run Text="{Binding Type}"/>
</TextBlock>
This is an example exhibiting the behaviour I'm having trouble with. I have a datagrid which is bound to an observable collection of records in a viewmodel. In the datagrid I have a DataGridTemplateColumn holding a combobox which is populated from a list in the viewmodel. The datagrid also contains text columns. There are some textboxes at the bottom of the window to show the record contents.
<Window x:Class="Customer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Customer"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:SelectedRowConverter x:Key="selectedRowConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<DataGrid x:Name="dgCustomers" AutoGenerateColumns="False"
ItemsSource="{Binding customers}" SelectedItem="{Binding SelectedRow,
Converter={StaticResource selectedRowConverter}, Mode=TwoWay}"
CanUserAddRows="True" Grid.Row="0" SelectionChanged="dgCustomers_SelectionChanged">
<DataGrid.Columns>
<DataGridTemplateColumn Width="Auto" Header="Country">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbCountry" ItemsSource="{Binding DataContext.countries,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="name" SelectedValuePath="name" Margin="5"
SelectedItem="{Binding DataContext.SelectedCountry,
RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" SelectionChanged="cmbCountry_SelectionChanged" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Name" Binding="{Binding name}" Width="1*"/>
<DataGridTextColumn Header="Phone" Binding="{Binding phone}" Width="1*"/>
</DataGrid.Columns>
</DataGrid>
<Grid x:Name="grdDisplay" DataContext="{Binding ElementName=dgCustomers}" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="2" Content="Country:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<Label Grid.Column="4" Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<BulletDecorator Grid.Column="0">
<BulletDecorator.Bullet>
<Label Content="Name:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtId" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.name}" Margin="5,5,5,5"/>
</BulletDecorator>
<BulletDecorator Grid.Column="1">
<BulletDecorator.Bullet>
<Label Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtCode" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.countryCode}" Margin="5,5,5,5"/>
</BulletDecorator>
<BulletDecorator Grid.Column="2">
<BulletDecorator.Bullet>
<Label Content="Phone:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtPhone" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.phone}" Margin="5,5,5,5"/>
</BulletDecorator>
</Grid>
</Grid>
</Window>
Initially there are no records so the datagrid is empty and shows just one line containing the combobox. If the user enters data into the text columns first then a record is added to the collection and the combobox value can be added to the record. However, if the user selects the combobox value first, then the value disappears when another column is selected. How do I get the combobox data added to the record if it is selected first?
Codebehind:
public partial class MainWindow : Window
{
public GridModel gridModel { get; set; }
public MainWindow()
{
InitializeComponent();
gridModel = new GridModel();
//dgCustomers.DataContext = gridModel;
this.DataContext = gridModel;
}
private void cmbCountry_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox c = sender as ComboBox;
Debug.Print("ComboBox selection changed, index is " + c.SelectedIndex + ", selected item is " + c.SelectedItem);
}
}
The Record class:
public class Record : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private string _phone;
public string phone
{
get { return _phone; }
set
{
_phone = value;
OnPropertyChanged("phone");
}
}
private int _countryCode;
public int countryCode
{
get { return _countryCode; }
set
{
_countryCode = value;
OnPropertyChanged("countryCode");
}
}
}
Country class:
public class Country : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("id");
}
}
private int _code;
public int code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged("code");
}
}
public override string ToString()
{
return _name;
}
}
GridModel:
public class GridModel : ViewModelBase
{
public ObservableCollection<Record> customers { get; set; }
public List<Country> countries { get; set; }
public GridModel()
{
customers = new ObservableCollection<Record>();
countries = new List<Country> { new Country { id = 1, name = "England", code = 44 }, new Country { id = 2, name = "Germany", code = 49 },
new Country { id = 3, name = "US", code = 1}, new Country { id = 4, name = "Canada", code = 11 }};
}
private Country _selectedCountry;
public Country SelectedCountry
{
get
{
return _selectedCountry;
}
set
{
_selectedCountry = value;
_selectedRow.countryCode = _selectedCountry.code;
OnPropertyChanged("SelectedRow");
}
}
private Record _selectedRow;
public Record SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
Debug.Print("Datagrid selection changed");
OnPropertyChanged("SelectedRow");
}
}
}
Converters:
class Converters
{
}
public class SelectedRowConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Record)
return value;
return new Customer.Record();
}
}
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
The behaviour you are seeing is as expected. The reason behind it is that the ComboBox ItemsSource as well as SelectedItem both are bound to Properties of the Window's DataContext while the other columns are bound to your DataGrid's ItemsSource. Hence when you modify the columns other than the dropdown the data is added to the observable collection.
What you can do is after a value is selected from the drop down you need to add a record yourself (possibly by calling a function from your SelectedCountry property)
EDIT
Based on your code I made a working model making as little changes as possible to your existing code. I could not use the converter as I did not have the details of the class Customer
Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Button HorizontalAlignment="Right" Content="Add User" Margin="0,2,2,2" Command="{Binding AddUserCommand}"/>
<DataGrid x:Name="dgCustomers"
AutoGenerateColumns="False"
ItemsSource="{Binding customers}"
SelectedItem="{Binding SelectedRow}"
SelectionUnit="FullRow"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="Auto" Header="Country">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Focusable="False"
ItemsSource="{Binding DataContext.countries, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
DisplayMemberPath="name"
SelectedValuePath="code"
SelectedValue="{Binding countryCode, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Name" Binding="{Binding name, UpdateSourceTrigger=PropertyChanged}" Width="1*"/>
<DataGridTextColumn Header="Phone" Binding="{Binding phone, UpdateSourceTrigger=PropertyChanged}" Width="1*"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<Grid x:Name="grdDisplay" DataContext="{Binding ElementName=dgCustomers}" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="2" Content="Country:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<Label Grid.Column="4" Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<BulletDecorator Grid.Column="0">
<BulletDecorator.Bullet>
<Label Content="Name:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtId" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.name}" Margin="5,5,5,5"/>
</BulletDecorator>
<BulletDecorator Grid.Column="1">
<BulletDecorator.Bullet>
<Label Content="Code:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtCode" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.countryCode}" Margin="5,5,5,5"/>
</BulletDecorator>
<BulletDecorator Grid.Column="2">
<BulletDecorator.Bullet>
<Label Content="Phone:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BulletDecorator.Bullet>
<TextBox x:Name="txtPhone" Text="{Binding ElementName=dgCustomers, Path=SelectedItem.phone}" Margin="5,5,5,5"/>
</BulletDecorator>
</Grid>
</Grid>
Your GridModel class
public class GridModel : ViewModelBase
{
public ObservableCollection<Record> customers { get; set; }
public ObservableCollection<Country> countries
{
get;
private set;
}
public GridModel()
{
customers = new ObservableCollection<Record> { };
AddUserCommand = new RelayCommand(AddNewUser);
countries = new ObservableCollection<Country>
{
new Country { id = 1, name = "England", code = 44 },
new Country { id = 2, name = "Germany", code = 49 },
new Country { id = 3, name = "US", code = 1},
new Country { id = 4, name = "Canada", code = 11 }
};
}
private void AddNewUser()
{
customers.Add(new Record());
}
public ICommand AddUserCommand { get; set; }
private Record _selectedRow;
public Record SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
Debug.Print("Datagrid selection changed");
OnPropertyChanged("SelectedRow");
}
}
}
I have used MVVMLight toolkit which contains RelayCommand. You can also define your own ICommand implementation and use it instead of the toolkit
EDIT 2
Fixed the bug introduced by me which would prevent the combobox from displaying the Country if the data comes from the data base. The improved code does not require any converter either
I need to change the boldness of text in list item on first selection.
Xaml:
<DockPanel >
<TextBox DockPanel.Dock="Top" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<ListBox x:Name="list" ItemsSource="{Binding EmailsCollection}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Content="{Binding Sender}" Name="SenderLabel" FontWeight="{Binding IsRead, Converter={StaticResource Converter}}"/>
<!--Style="{StaticResource Sender}"-->
<Label Grid.Row="1" Content="{Binding Subject}" FontSize="12" HorizontalAlignment="Left" />
<Label Grid.Column="1" Content="{Binding Date}" FontSize="12" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
View model:
public Email SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
_selectedItem.IsRead = true;
OnPropertyChanged(this,"SelectedItem");
}
}
Model:
public bool IsRead
{
get { return _isRead; }
set
{
_isRead = value;
OnPropertyChanged(this, "IsRead");
}
}
How can i bind to "IsRead" property of selected item in list?
The current way goes over all Emails in the beginning and doesn't change anything after.
Simply use a DataTrigger with out the need of a converter
<DockPanel >
<TextBox DockPanel.Dock="Top" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<ListBox x:Name="list" ItemsSource="{Binding EmailsCollection}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Content="{Binding Sender}" Name="SenderLabel" >
<Label.Style>
<Style TargetType="Label">
<Setter Property="FontWeight" Value="Normal"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsRead}" Value="true">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<!--Style="{StaticResource Sender}"-->
<Label Grid.Row="1" Content="{Binding Subject}" FontSize="12" HorizontalAlignment="Left" />
<Label Grid.Column="1" Content="{Binding Date}" FontSize="12" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
Update: here the corresponding Model/ViewModel
public class Email:INotifyPropertyChanged
{
private bool _isRead;
public bool IsRead
{
get { return _isRead; }
set
{
_isRead = value;
OnPropertyChanged();
}
}
private String _sender ;
public String Sender
{
get
{
return _sender;
}
set
{
if (_sender == value)
{
return;
}
_sender = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class MainWindow : Window,INotifyPropertyChanged
{
private ObservableCollection<Email> _emailsCollection = new ObservableCollection<Email>(){new Email(){Sender = "FirstSender",IsRead = true},new Email(){Sender = "SecondSender",IsRead = false}};
public ObservableCollection<Email> EmailsCollection
{
get
{
return _emailsCollection;
}
set
{
if (_emailsCollection == value)
{
return;
}
_emailsCollection = value;
OnPropertyChanged();
}
}
private Email _selectedItem=new Email(){IsRead = true};
public Email SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
_selectedItem.IsRead = true;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Output
How can I add items to listbox when item source is a list.
XAML:
<ListBox Grid.Row="2" HorizontalAlignment="Stretch" ItemsSource="{Binding Source={StaticResource viewModel}, Path=CultureEvents}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Source={StaticResource viewModel}, Path=ItemTitle}" Height="30" HorizontalAlignment="Left" Margin="116,364,0,0" VerticalAlignment="Top" Width="334" Foreground="White" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In viewmodel I have list:
public List<CultureEvent> CultureEvents { get; set; }
And property:
public string ItemTitle
{
get
{
return ?;
}
set
{
? = value;
OnPropertyChanged(new PropertyChangedEventArgs("ItemTitle"));
}
}
But I don't know what to put into property.
private string _itemTitle
public string ItemTitle
{
get
{
return _itemTitle;
}
set
{
_itemTitle = value;
OnPropertyChanged(new PropertyChangedEventArgs("ItemTitle"));
}
You would generate The list something like this,
CultureEvents = new List<CultureEvent>();
CultureEvents.Add(new CultureEvent{Title = "Yourvalue"} );