My problem is EMPTY Combobox list. I've walked through a lot of web site (during 3 days), but couldn't figure out it.
I've written a program that show a List of Students, clicking I can change properties them. So, I can change succsefuly all properties except Faculty (ComboBox).
Click to see View my programm
I used MVVM....
I have Class Student (Model). Here, General is enum StudentFaculty....
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _15._01._2018.Model
{
public enum StudentFaculty { Programmer, SysAdministration, Designer};
class Student : INotifyPropertyChanged
{
private string _name;
private string _lastname;
private StudentFaculty _faculty;
private double _averageMark;
public string Name
{
get { return _name; }
set
{
if(_name == value) return;
_name = value;
OnPropertyChanged("Name");
}
}
public string Lastname
{
get { return _lastname; }
set
{
if (_lastname == value) return;
_lastname = value;
OnPropertyChanged("Lastname");
}
}
public StudentFaculty Faculty
{
get { return _faculty; }
set
{
if (_faculty == value) return;
_faculty = value;
OnPropertyChanged("Faculty");
}
}
public double AverageMark
{
get { return _averageMark; }
set
{
if (_averageMark == value) return;
_averageMark = value;
OnPropertyChanged("AverageMark");
}
}
public Student() { }
public Student(string name, string lastname, StudentFaculty faculty, double averageMark)
{
Name = name;
Lastname = lastname;
Faculty = faculty;
AverageMark = averageMark;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Class ApplicationViewModel. Here, I make List (Faculties) from enum in Constructor, also SelectedFaculty...
using _15._01._2018.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _15._01._2018.ViewModel
{
class ApplicationViewModel : INotifyPropertyChanged
{
private Student _selectedStudent;
private string _selectedFaculty;
public ObservableCollection<Student> Students { get; set; }
public List<string> Faculties { get; set; }
public Student SelectedStudent
{
get { return _selectedStudent; }
set
{
if (_selectedStudent == value) return;
_selectedStudent = value;
OnChangedProperty("SelectedStudent");
}
}
public string SelectedFaculty
{
get { return _selectedFaculty; }
set
{
if (_selectedFaculty == value) return;
_selectedFaculty = value;
OnChangedProperty("SelectedFaculty");
}
}
public ApplicationViewModel()
{
Students = new ObservableCollection<Student>
{
new Student("Vova", "Zyabkin", StudentFaculty.Programmer, 9.4),
new Student("Vadym", "Lazariev", StudentFaculty.Programmer, 9),
new Student("SvEta", "Belyaeva", StudentFaculty.Designer, 9.8),
new Student("Vova", "Skachkov", StudentFaculty.SysAdministration, 8.7)
};
Faculties = new List<string>(Enum.GetNames(typeof(StudentFaculty)));
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnChangedProperty(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
XAML
<Window x:Class="_15._01._2018.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:_15._01._2018"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
</Style>
<Style TargetType="TextBox">
<Setter Property="FontSize" Value="14" />
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Students}" SelectedItem="{Binding SelectedStudent}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Name}"></TextBlock>
<TextBlock Text="{Binding Path=Lastname}"></TextBlock>
<!--<TextBlock Text="{Binding Path=Faculty}"></TextBlock>-->
<TextBlock Text="{Binding Path=AverageMark}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<StackPanel DataContext="{Binding SelectedStudent}">
<TextBlock Text="D A T A"></TextBlock>
<TextBlock Text="Name:"></TextBlock>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Lastname:"></TextBlock>
<TextBox Text="{Binding Lastname, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Faculty:"></TextBlock>
<ComboBox ItemsSource="{Binding Faculties}" SelectedItem="{Binding SelectedFaculty}">
</ComboBox>
<TextBlock Text="Average Mark:"></TextBlock>
<TextBox Text="{Binding AverageMark, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</StackPanel>
</Grid>
</Grid>
</Window>
I have idea to create a Class with String property Faculty, but it's like with ListBox and Student.
To start off with, the DataContext for your ComboBox is set to the SelectedStudent, not the ApplicationViewModel (see the parent StackPanel). Second, you have the Faculties property returning a list of String, but the Student class has a property of StudentFaculty (binding for SelectedValue won't work).
Try the following:
Models/ViewModels:
class ApplicationViewModel : INotifyPropertyChanged
{
private Student _selectedStudent;
private StudentFaculty _selectedFaculty;
public ObservableCollection<Student> Students { get; set; }
public ObservableCollection<StudentFaculty> Faculties { get; set; }
public Student SelectedStudent
{
get => _selectedStudent;
set
{
if (_selectedStudent == value) return;
_selectedStudent = value;
OnChangedProperty("SelectedStudent");
}
}
public StudentFaculty SelectedFaculty
{
get => _selectedFaculty;
set
{
if (_selectedFaculty == value) return;
_selectedFaculty = value;
OnChangedProperty("SelectedFaculty");
}
}
public ApplicationViewModel()
{
Students = new ObservableCollection<Student>
{
new Student("Vova", "Zyabkin", StudentFaculty.Programmer, 9.4),
new Student("Vadym", "Lazariev", StudentFaculty.Programmer, 9),
new Student("SvEta", "Belyaeva", StudentFaculty.Designer, 9.8),
new Student("Vova", "Skachkov", StudentFaculty.SysAdministration, 8.7)
};
Faculties = new ObservableCollection<StudentFaculty>(Enum.GetValues(typeof(StudentFaculty)).OfType<StudentFaculty>());
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnChangedProperty(string property) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public enum StudentFaculty { Programmer, SysAdministration, Designer };
class Student : INotifyPropertyChanged
{
private string _name;
private string _lastname;
private StudentFaculty _faculty;
private double _averageMark;
public string Name
{
get => _name;
set
{
if (_name == value) return;
_name = value;
OnPropertyChanged("Name");
}
}
public string Lastname
{
get => _lastname;
set
{
if (_lastname == value) return;
_lastname = value;
OnPropertyChanged("Lastname");
}
}
public StudentFaculty Faculty
{
get => _faculty;
set
{
if (_faculty == value) return;
_faculty = value;
OnPropertyChanged("Faculty");
}
}
public double AverageMark
{
get => _averageMark;
set
{
if (_averageMark == value) return;
_averageMark = value;
OnPropertyChanged("AverageMark");
}
}
public Student() { }
public Student(string name, string lastname, StudentFaculty faculty, double averageMark)
{
Name = name;
Lastname = lastname;
Faculty = faculty;
AverageMark = averageMark;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
XAML:
<Window x:Class="WpfApp4.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:converters="clr-namespace:WpfApp4.Views.Converters"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:viewModels="clr-namespace:WpfApp4.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModels:ApplicationViewModel />
</Window.DataContext>
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
</Style>
<Style TargetType="TextBox">
<Setter Property="FontSize" Value="14" />
</Style>
<CollectionViewSource x:Key="Faculties" Source="{Binding Faculties}"></CollectionViewSource>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Students}" SelectedItem="{Binding SelectedStudent}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Name}"></TextBlock>
<TextBlock Text="{Binding Path=Lastname}"></TextBlock>
<!--<TextBlock Text="{Binding Path=Faculty}"></TextBlock>-->
<TextBlock Text="{Binding Path=AverageMark}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<StackPanel DataContext="{Binding SelectedStudent}">
<TextBlock Text="D A T A"></TextBlock>
<TextBlock Text="Name:"></TextBlock>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Lastname:"></TextBlock>
<TextBox Text="{Binding Lastname, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Faculty:"></TextBlock>
<ComboBox ItemsSource="{Binding Source={StaticResource Faculties}}" SelectedValue="{Binding Faculty, Mode=TwoWay}">
</ComboBox>
<TextBlock Text="Average Mark:"></TextBlock>
<TextBox Text="{Binding AverageMark, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</StackPanel>
</Grid>
</Grid>
Related
I have a class Employee :Person
Class Employee
[Serializable]
public enum Education
{
secondary, specialized_secondary, high
}
[Serializable]
public enum MarriageStatus
{
single, married, divorced
}
[Serializable]
public class Employee : Person, INotifyPropertyChanged
{
private Education _teaching;
private MarriageStatus _status;
private string _photoPath;
public Education Teaching
{
get { return _teaching; }
set
{
_teaching = value;
OnPropertyChanged("Teaching");
}
}
public MarriageStatus Status
{
get { return _status; }
set
{
_status = value;
OnPropertyChanged("Status");
}
}
public string PhotoPath
{
get { return _photoPath; }
set
{
_photoPath = value;
OnPropertyChanged("Status");
}
}
private ObservableCollection<Employee> _employeesList = null;
public new event PropertyChangedEventHandler PropertyChanged;
public new void OnPropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
public ObservableCollection<Employee> EmployeesList
{
get
{
if (_employeesList != null)
{
return _employeesList;
}
_employeesList = new ObservableCollection<Employee>();
_employeesList.Add(new Employee()
{
Id = 1,
FirstName = "Igor",
LastName = "Krivonos",
DateBirthday = new DateTime(1999, 8, 15),
INN = "111111111",
Teaching = Education.high,
Status = MarriageStatus.married,
PhotoPath = "Photo/IgorKrivonos.jpg"
});
return _employeesList;
}
}
}
I have realized INotifyPropety event but I am not sure about this. I am new in this field and trying everything that might help. All my Employee informaiton is shown in DataGrid When I change any cell there it will not show in debuger any changes. I want to serialize it to save changes. Why my changing is not working? Sorry for my English.
Here is my xaml code:
Window x:Class="Employee_DataGrid.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:Employee_DataGrid"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:model="clr-namespace:Employee_DataGrid.Model"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<model:Employee x:Key="employees"></model:Employee>
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="status">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="model:MarriageStatus" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="education">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="model:Education" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!--Button for serialization-->
<Button Grid.Column="1" Click="Button_Click" >
<TextBlock TextAlignment="Center" FontSize="35" FontFamily="TimesNewRoman" FontWeight="Bold" Width="30" TextWrapping="Wrap">Save Data</TextBlock>
</Button>
<DataGrid AutoGenerateColumns="False" CanUserAddRows="False"
ItemsSource="{Binding Source={StaticResource employees}, Path=EmployeesList}">
<!--DataGrid Columns-->
<DataGrid.Columns>
<!--DataGridTextColumn for Full names and Inn-->
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"></DataGridTextColumn>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"></DataGridTextColumn>
<DataGridTextColumn Header="INN" Binding="{Binding INN}"></DataGridTextColumn>
<!--DataGridComboBoxColumn for Marriage Status-->
<DataGridComboBoxColumn Header="Status"
ItemsSource="{Binding Source={StaticResource status}}"
SelectedValueBinding="{Binding Status}" >
</DataGridComboBoxColumn>
<!--DataGridTemplateColumn for Birthday-->
<DataGridTemplateColumn Header="Date Birthday">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding DateBirthday, StringFormat='MM.dd.yyyy'}"></DatePicker>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--DataGridComboBoxColumn for Education-->
<DataGridComboBoxColumn Header="Education"
ItemsSource="{Binding Source={StaticResource education}}"
SelectedValueBinding="{Binding Teaching}" >
</DataGridComboBoxColumn>
<!--DataGridTemplateColumn for Photos-->
<DataGridTemplateColumn Header="Employees Photos">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image DockPanel.Dock="Right"
HorizontalAlignment="Right"
Width="70"
Source="{Binding Path=PhotoPath}"></Image>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
I inherit some propeties from Person Class Here is my Person Class:
[Serializable]
public class Person: INotifyPropertyChanged
{
private int _id;
private string _firstName;
private string _lastName;
private System.DateTime _dateBirthday;
private string _inn;
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
public System.DateTime DateBirthday
{
get { return _dateBirthday; }
set
{
_dateBirthday = value;
OnPropertyChanged("DateBirthday");
}
}
public string INN
{
get { return _inn; }
set
{
_inn = value;
OnPropertyChanged("INN");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
Here I have realized INotifyPropety as well.
Main Window class
[Serializable]
public partial class MainWindow : Window
{
public Employee Employee { get; set; }
public MainWindow()
{
InitializeComponent();
Employee = new Employee();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Helpers.Serializing(Employee.EmployeesList, "employees.bin");
}
}
You are creating two instances of Employee, one in the code-behind and another one in the XAML markup.
You should bind to the one that you create in the code-behind:
<DataGrid AutoGenerateColumns="False" CanUserAddRows="False"
ItemsSource="{Binding Path=Employee.EmployeesList,
RelativeSource={RelativeSource AncestorType=Window}}">
...and remove this:
<model:Employee x:Key="employees"></model:Employee>
I followed few online tutorials to switch between dynamic views from a ListView.
In my MainWindow, I have a ListView in the left pane with ItemsSource of list of sub ViewModels. (Each sub ViewModel implements an Interface)
Each ViewModel have its own View as a DataTemplate.
I'm calling the GenerateReport() method of the selected view from the MainWindow. But the values of selected views becomes null.
Download my source from Github.
Steps to reproduce the issue:
Run the application and type text in the Students Report's Id and Name. (The breakpoints in StudentReportViewModels's properties are properly hitting and values updated.)
Then click Generate Report button. The StudentReportViewModels's properties values becomes null.
How to fix the issue? Please help.
Source:
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type vm:StudentsReportViewModel}">
<view:StudentsReport/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MarksReportViewModel}">
<view:MarksReport />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Reports}" SelectedItem="{Binding SelectedReport, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" ResizeBehavior="PreviousAndNext" HorizontalAlignment="Stretch"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ContentControl Content="{Binding SelectedReport.ViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ScrollViewer>
<Button Content="Generate Report" Grid.Row="2" Margin="5" HorizontalAlignment="Right" Command="{Binding GenerateReportCommand}"/>
</Grid>
</Grid>
MainWindowViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ICommand _generateReportCommand;
private Report selectedReport;
public Report SelectedReport
{
get
{
return this.selectedReport;
}
set
{
if (value != this.selectedReport)
{
this.selectedReport = value;
NotifyPropertyChanged();
}
}
}
public List<Report> Reports { get; set; }
public ICommand GenerateReportCommand
{
get
{
if (_generateReportCommand == null)
{
_generateReportCommand = new RelayCommand(
p => GenerateReport()
);
}
return _generateReportCommand;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindowViewModel()
{
Reports = new List<Report>
{
new Report{ Name = "Students Report", ViewModel = new StudentsReportViewModel()},
new Report{ Name = "Marks Report", ViewModel = new MarksReportViewModel()}
};
SelectedReport = Reports[0];
}
public void GenerateReport()
{
SelectedReport.ViewModel.GenerateReport();
}
}
StudentsReport.xaml
<TextBox Height="25" Width="100" Margin="5" Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Height="25" Width="100" Margin="5" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
StudentsReportViewModel:
public class StudentsReportViewModel : INotifyPropertyChanged, IReportViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string id;
public string Id
{
get
{
return this.id;
}
set
{
if (value != this.id)
{
this.id = value;
NotifyPropertyChanged();
}
}
}
private string name;
public string Name
{
get
{
return this.name;
}
set
{
if (value != this.name)
{
this.name = value;
NotifyPropertyChanged();
}
}
}
public StudentsReportViewModel()
{
}
public void GenerateReport()
{
System.Windows.MessageBox.Show($"Id = {Id}, Name = {Name}");
}
}
Interface:
public interface IReportViewModel
{
void GenerateReport();
}
Model:
public class Report
{
public string Name { get; set; }
public IReportViewModel ViewModel { get; set; }
}
Your StudentsReport.xaml UserControl is binding to an instance of the StudentsReportViewModel created in the XAML:
<UserControl.DataContext>
<vm:StudentsReportViewModel/>
</UserControl.DataContext>
The Generate Report button however is calling into another instance of the StudentsReportViewModel that is create in the MainWindowVieModel constructor and is stored in the Report class.
Reports = new List<Report>
{
new Report{ Name = "Students Report", ViewModel = new StudentsReportViewModel()},
new Report{ Name = "Marks Report", ViewModel = new MarksReportViewModel()}
};
You need to remove one of these instances so that the UserControl's DataContext is bound to the same view model instance that you are generating the report message from.
I suggest deleting this code from StudentsReport.xaml:
<UserControl.DataContext>
<vm:StudentsReportViewModel/>
</UserControl.DataContext>
I'm trying to use a CollectionViewSource in an ItemsControl that is nested inside another ItemsControl.
I have no XAML warnings/errors, but the data is not displayed.
If I bind the ItemsSource property directly to the ObservableCollection, there is no problem displaying the items.
My ViewModel is basically a collection nested inside another collection.
XAML
<UserControl x:Class="View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:v="clr-namespace:ViewModel"
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TabControl ItemsSource="{Binding Categories}" SelectedItem="{Binding SelectedCategory, Mode=TwoWay}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate DataType="v:CategoryViewModel">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate DataType="v:CategoryViewModel">
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Groups}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<CollectionViewSource x:Key="ValuesCollection" Source="{Binding Values}">
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription PropertyName="Name" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</DataTemplate.Resources>
<GroupBox Header="{Binding Name}" >
<ItemsControl ItemsSource="{Binding Source={StaticResource ValuesCollection}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="10,2,10,2">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Name}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Value}""/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</UserControl>
CategoryViewModel
namespace ViewModel
{
using System.Collections.ObjectModel;
using System.Diagnostics;
internal class CategoryViewModel : ObservableObjectBase
{
private string name;
private string id;
private int priority;
public CategoryViewModel()
{
this.Groups = new ObservableCollection<GroupViewModel>();
}
public ObservableCollection<GroupViewModel> Groups { get; }
public string Name
{
get
{
return this.name;
}
set
{
this.SetValue(ref this.name, value);
}
}
public string Id
{
get
{
return this.id;
}
set
{
this.SetValue(ref this.id, value);
}
}
public int Priority
{
get
{
return this.priority;
}
set
{
this.SetValue(ref this.priority, value);
}
}
}
}
GroupViewModel
namespace ViewModel
{
using System.Collections.ObjectModel;
using System.Diagnostics;
internal class GroupViewModel : ObservableObjectBase
{
private string name;
private string id;
private int priority;
public GroupViewModel()
{
this.Values = new ObservableCollection<ValueViewModel>();
}
public ObservableCollection<ValueViewModel> Values { get; }
public string Name
{
get
{
return this.name;
}
set
{
this.SetValue(ref this.name, value);
}
}
public string Id
{
get
{
return this.id;
}
set
{
this.SetValue(ref this.id, value);
}
}
public int Priority
{
get
{
return this.priority;
}
set
{
this.SetValue(ref this.priority, value);
}
}
}
}
ValueViewModel
namespace ViewModel
{
using System.Diagnostics;
internal class ValueViewModel : ObservableObjectBase
{
private string name;
private string id;
private string value;
private int priority;
public string Name
{
get
{
return this.name;
}
set
{
this.SetValue(ref this.name, value);
}
}
public string Id
{
get
{
return this.id;
}
set
{
this.SetValue(ref this.id, value);
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.SetValue(ref this.value, value);
}
}
public int Priority
{
get
{
return this.priority;
}
set
{
this.SetValue(ref this.priority, value);
}
}
}
}
Any suggestion?
Thanks in advance.
Edit:
In the output I see:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement
or FrameworkContentElement for target element.
BindingExpression:Path=Values; DataItem=null; target element is
'CollectionViewSource' (HashCode=40230093); target property is
'Source' (type 'Object')
Instead of placing the CollectionViewSource in DataTemplate.Resources, you should place it in the resources of a framework element inside the datatemplate, for example in <GroupBox.Resources>
See related: https://stackoverflow.com/a/6614745/5265292
I'm trying to display different rows in a datagrid with different cellcontent sometimes.
I have different classes for the different rows, for example
Class 1:
Name - Description - Checkbox
Class 2:
Name - Description - Textbox(user input at runtime) - Checkbox
Class 3
Name - Textbox(user input at runtime)
The classes are related by inheritance so I can use them within the same observablecollection.
I want to display these in a datagrid based on which class I chose to add, for example:
ObservableCollection<Rowitem> rowitems = new ObservableCollection<Rowitem>();
rowitems.Add(new Class1("Tom", "Nice", false));
rowitems.Add(new Class2("John", "Strange", Empty textbox , true));
rowitems.Add(new Class3("Roger", Empty Textbox));
.. meaning I would like the datagrid to display an empty textbox in the third column on the second row where there is a checkbox in the first row and nothing in the third row. Is this possible?
Here is my suggestion:
<Window x:Class="DataGridDynamicCellView.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:local="clr-namespace:DataGridDynamicCellView"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Window.DataContext>
<local:DynamicCellsDataContext />
</Window.DataContext>
<Grid>
<DataGrid ItemsSource="{Binding DataGridSource}">
<DataGrid.Resources>
<DataTemplate DataType="{x:Type local:PresentedByCheckBox}">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="{Binding IsChecked,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:PresentedByTextBox}">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding HelloWorld,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:PresentedByComplexBox}">
<StackPanel HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Horizontal">
<Ellipse Height="10" Width="10" Fill="Pink"/>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="{Binding Checked,
UpdateSourceTrigger=PropertyChanged}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding HelloWorld,
UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderBrush" Value="Green" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid></Window>
MVVM view model:
public class DynamicCellsDataContext:BaseObservableObject
{
public DynamicCellsDataContext()
{
DataGridSource = new ObservableCollection<object>
{
new PresentedByTextBox("Hello world!!!"),
new PresentedByCheckBox(true),
new PresentedByComplexBox("Hello world!!!", true),
};
}
public ObservableCollection<object> DataGridSource { get; set; }
}
public class PresentedByComplexBox:BaseObservableObject
{
private string _helloWorld;
private bool _checked;
public string HelloWorld
{
get { return _helloWorld; }
set
{
_helloWorld = value;
OnPropertyChanged();
}
}
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
OnPropertyChanged();
}
}
public PresentedByComplexBox(string helloWorld, bool isChecked)
{
HelloWorld = helloWorld;
Checked = isChecked;
}
}
public class PresentedByCheckBox:BaseObservableObject
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged();
}
}
public PresentedByCheckBox(bool isChecked)
{
IsChecked = isChecked;
}
}
public class PresentedByTextBox:BaseObservableObject
{
private string _helloWorld;
public string HelloWorld
{
get { return _helloWorld; }
set
{
_helloWorld = value;
OnPropertyChanged();
}
}
public PresentedByTextBox(string helloWorld)
{
HelloWorld = helloWorld;
}
}
The BaseObservableObject class:
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
That's all, let me know in case you will need more examples.
Best regards.
You can do this using DataTemplates:
Just add them to the Resources of your Grid:
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Class1}">
<-- Template for class1 -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:Class2}">
<-- Template for class2 -->
</DataTemplate>
</Grid.Resources>
So, I have a TabControl binded to a list of projects (each tab is a one project) - that works fine. The content of each tab is a DataGrid with a list of project's employees - that works fine as well. Now, I want to show some information on employee currently selected on DataGrid. Here's some code:
MainWindow.xaml file:
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ContentTemplate">
<DataGrid ItemsSource="{Binding Employees}" SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}" SelectionMode="Extended" SelectionUnit="FullRow" Name="employeesList">
</DataGrid>
</DataTemplate>
</Window.Resources>
and later, I want to test this binding by simply writing it in label:
<Label Name="emp" Content="{Binding SelectedEmployee}"></Label>
and MainWindowViewModel:
public Employee SelectedEmployee { get { return selectedEmployee; }
set
{
if (selectedEmployee != value)
{
selectedEmployee = value;
NotifyPropertyChanged("SelectedEmployee");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I am kind of a newbie to WPF, I've read some tips but they don't help. The label 'emp' does not show anything. What am I missing?
You are not notifying that your property has changed, Try this
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
if (selectedEmployee != value)
{
selectedEmployee = value;
LastName = value;
NotifyPropertyChanged("SelectedEmployee"); //NotifyPropertyChanged("SelectedItem");
}
}
}
Test:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication6"
Title="MainWindow" Height="350" Width="763" Name="UI" >
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding ElementName=UI,Path=Employees}" SelectedItem="{Binding ElementName=UI,Path=SelectedEmployee}" SelectionMode="Extended" SelectionUnit="FullRow" Name="employeesList" Margin="0,41,0,0" />
<Label Content="{Binding ElementName=UI,Path=SelectedEmployee.Name}" Height="28" HorizontalAlignment="Left" Name="label1" VerticalAlignment="Top" Width="288" />
<Label Content="{Binding ElementName=employeesList,Path=SelectedItem.Name}" Height="28" HorizontalAlignment="Left" Name="label2" VerticalAlignment="Top" Width="288" Margin="294,0,0,0" />
</Grid>
</Window>
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Employee> _employees = new ObservableCollection<Employee>();
private Employee _selectedEmployee;
public MainWindow()
{
InitializeComponent();
Employees.Add(new Employee { Name = "sa_ddam213" });
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set { _employees = value; }
}
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set { _selectedEmployee = value; NotifyPropertyChanged("SelectedEmployee"); }
}
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
public class Employee
{
public string Name { get; set; }
}
This seems to work as expected, or am I missing something?