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
Related
I have an object consisting of a string and a string of arrays. I'm binding the string to a comboBox but what I want to also do is bind the array of that object to a listview and have it change dynamically depending on combox box value. The values in the array aren't populating, only the dataType of the array. I'm not married to using a listview but I thought it would be easiest.
Model -
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
ViewModel -
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel
{
public ObservableCollection<Example> Examples
{
get;
set;
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }});
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789","101112" }});
Examples = examples;
}
}
}
XAML -
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding Path=Name, Mode=TwoWay}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding Path=Examples}"
SelectedValue="{Binding Path=IDs}"
Height="200" Margin="0,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text= "{Binding Path=IDs}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
If you want to show in ListBox the Ids of selected item in comboBox.
You need to add SelectedItem property to your VM which also must implement INotifyPropertyChanged interface.
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set {
_selectedItem = value;
OnPropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set {
_selectedId= value;
OnPropertyChanged(nameof(SelectedId));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }});
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789","101112" }});
Examples = examples;
}
}
}
XAML
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.Ids}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="0,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text= "{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
There could several solutions to your case but most common and proprietary way is to use ValueConverter.
For this example i'll assume you have grid in the Window control. You need to add static resource:
<Window.Resources>
<local:ArrayValueConverter x:Key="arrayConverter"/>
</Window.Resources>
Then in your ListView's DataTemplate add Converter:
<TextBlock Text= "{Binding Path=IDs, Converter={StaticResource arrayConverter}}" FontWeight="Bold" />
ValueConverter himself:
public class ArrayValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string[] arr) {
return string.Join(',', arr);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
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>
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>
I am creating a application using WPF. I am able to bind Checkboxlist by using the below code:
XAML:
<DataTemplate x:Key="defaultTemplate">
<dxe:CheckEdit x:Name="lstcheckbox" Checked="lstcheckbox_Checked_1" Content="{Binding Name}" IsChecked="{Binding Path=Checked}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Checked}" Value="true">
<Setter TargetName="lstcheckbox" Property="ContentTemplate" Value="{DynamicResource Template1}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="Template1">
<TextBox Width="100"></TextBox>
</DataTemplate>
<local:SomeTemplateSelector x:Key="SomeTemplateSelector" DefaultTemplate="{StaticResource defaultTemplate}"
Template1="{StaticResource Template1}">
</local:SomeTemplateSelector>
</Window.Resources>
<Grid>
<dxd:DockLayoutManager x:Name="dockManager" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible">
<dxd:LayoutGroup Caption="Review Checklist" Orientation="Vertical" ShowCaption="True" GroupBorderStyle="Group" CaptionAlignMode="AlignInGroup">
<dxd:LayoutControlItem>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Margin="8,5,5,5">Safety Equipment</TextBlock>
<!--<ListView x:Name="lstSafEquip" Grid.Column="0" Grid.Row="1" BorderThickness="0" Margin="5,5,5,5" SelectionMode="Multiple" ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource SomeTemplateSelector}" ItemTemplate="{StaticResource ItemDataTemplate}" IsSynchronizedWithCurrentItem="true" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxHeight="200" />-->
<ListView x:Name="lstSafEquip" Grid.Column="0" Grid.Row="1" BorderThickness="0" Margin="5,5,5,5" ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource SomeTemplateSelector}" IsSynchronizedWithCurrentItem="true" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxHeight="200" />
</Grid>
</dxd:LayoutControlItem>
</dxd:LayoutGroup>
</dxd:DockLayoutManager>
</Grid>
CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
BindCheckBoxs(lstSafEquip);
}
private void BindCheckBoxs(ListView lst)
{
try
{
ObservableCollection<Data> Source = new ObservableCollection<Data> { new Data(1, "Apple"), new Data(2, "Mango"), new Data(3, "Others"), new Data(3, "Banana"), new Data(3, "Grapes") };
lst.ItemsSource = Source;
}
catch (Exception ex)
{
throw new Exception(ex.Message.ToString());
}
}
}
public class SomeTemplateSelector : DataTemplateSelector
{
public DataTemplate Template1 { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Data _Item = (Data)item;
if (_Item.Name == "Others")
{
return Template1;
}
return DefaultTemplate;
}
}
DATA.CS:
public class Data
{
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
public Data(int _ID, string _Name)
{
ID = _ID;
Name = _Name;
Checked = false;
}
}
PROBLEM:
I want to add textbox just below the checkboxlist item Others when user will checked this item.
By using the above code trigger enter textbox for each item when i checked.
please let me know where i am going wrong. I will try my best to provide more info if needed.
From what i understood from your post and comments:
Add a TextBox as Barptad suggested but use a BooleanToVisibilityCollapsed Converter as stated below:
BooleanToVisibilityCollapsedConverter.cs:
public class BooleanToVisibilityCollapsedConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return ((bool) value) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture) {
//never used
return null;
}
}
XAML:
<DataTemplate x:Key="defaultTemplate">
<StackPanel>
<dxe:CheckEdit x:Name="lstcheckbox" Checked="lstcheckbox_Checked_1" Content="{Binding Name}" IsChecked="{Binding Path=Checked}" />
<TextBox Visibility="{Binding IsChecked, ElementName=lstcheckbox}"/>
</StackPanel>
</DataTemplate>
If you use Visibility.Hidden the textbox is not visible but still takes place.
After merging all suggestions and comments. I reached on the solution.
XAML:
Added textbox in default template
<DataTemplate x:Key="defaultTemplate">
<StackPanel>
<dxe:CheckEdit x:Name="lstcheckbox" Checked="lstcheckbox_Checked_1" Unchecked="lstcheckbox_Checked_1" Content="{Binding Name}" IsChecked="{Binding Path=Checked}" />
<TextBox Name="txtOther" Visibility="{Binding Path=Visible}" Text="test" Width="100"></TextBox>
</StackPanel>
</DataTemplate>
XAML.CS:
private void lstcheckbox_Checked_1(object sender, RoutedEventArgs e)
{
Data some = (sender as CheckEdit).DataContext as Data;
if (some.Name.Contains("Others"))
{
some.Visible = ToggleVisibility(some.Visible);
}
}
private Visibility ToggleVisibility(Visibility visibility)
{
return visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
DATA.CS:
Added visible property and inherited with INotifyPropertyChanged to track change in property in Data.cs
public class Data : INotifyPropertyChanged
{
private Visibility _visible;
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
//public Visibility Visible { get; set; }
public Visibility Visible
{
get { return this._visible; }
set
{
this._visible = value;
RaisePropertyChanged("Visible");
}
}
public Data(int _ID, string _Name,Visibility _visible)
{
ID = _ID;
Name = _Name;
Visible = _visible;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
SomeTemplateSelector.CS
public class SomeTemplateSelector : DataTemplateSelector
{
public DataTemplate OthersTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return DefaultTemplate;
}
}
Hope this will help others.
Correct Me If I am worng!
U want to show textbox only under "Others" item when it is checked.
For the scenario mentioned above u need to have two different templates that changes on the value of item.
For e.g if the item is "Others" it should have template that have a text box just below check box which is only shown when checkbox is checked otherwise collapsed.
for the second type of items it should have different template even if it is checked no textbox should be displayed under it.
(Step 1) Create a Template Selector
public class ValueDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate OthersTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
DataTemplate template = DefaultTemplate;
CheckList data = item as CheckList;
if (data.Description.Contains("Others"))
{
template = OthersTemplate;
}
return template;
}
}
Reference assembly in your file.
(Step 3) Create two templates as given below
<Window.Resources>
<DataTemplate x:Key="DefaultTemplate">
<StackPanel>
<CheckBox IsChecked="{Binding Path=IsChecked}" Content="{Binding Path=Description}"></CheckBox>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OthersTemplate">
<DataTemplate.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked}" Value="false">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<StackPanel>
<CheckBox IsChecked="{Binding Path=IsChecked}" Content="{Binding Path=Description}"></CheckBox>
<TextBox x:Name="othersTextBox">
</TextBox>
</StackPanel>
</DataTemplate>
<local:ValueDataTemplateSelector x:Key="CheckListTemplateSelector"
DefaultTemplate="{StaticResource DefaultTemplate}"
OthersTemplate="{StaticResource OthersTemplate}"/>
</Window.Resources>
Please note:
The two types in the lists are simplified a lot for this example and must be kept separate.
The use of an int as connection between the types can not be changed.
The problem:
Given the code below, how do I get the ComboBox marked with ??? to:
Display the ColorDefs.Name as its content.
Set SelectedItem to the one where Models.DisplayColorNumber is equal to ColorDefs.ColorNumber.
Update the Models.DisplayColorNumber updated if the selection is changed.
In code-behind
public List<ModelData> Models { get; }
public List<DisplayColorDefinition> ColorDefs { get; }
DataContext=this;
XAML:
<ListBox ItemsSource="{Binding Models}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ??? />
<TextBlock Text="{Binding Models, Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ModelData type:
public class ModelData
{
private string name;
private int displayColorNumber;
public string Name
{
get { return name; }
set { name = value; }
}
public int DisplayColorNumber
{
get { return displayColorNumber; }
set { displayColorNumber = value; }
}
}
DisplayColorDefinition type:
public class DisplayColorDefinition
{
private int colorNumber;
private string name;
private Color displayColor;
public int ColorNumber
{
get { return colorNumber; }
set { colorNumber= value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public Color DisplayColor
{
get { return displayColor; }
set { displayColor = value; }
}
}
Use the SelectedValue and SelectedValuePath :
<ListBox ItemsSource="{Binding Models}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=DataContext.ColorDefs, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=DisplayColorNumber}"
SelectedValuePath="ColorNumber"
/>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DisplayColorNumber}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectedValue will be the property on the Model object, and SelectedValuePath will indicate which property of the DisplayColorDefinition to use for the binding.