I have a DataGrid with 2 columns. Based on the first column which is bound to ParameterDataType I want to load the appropriate template in the second column.
Problem with this code is before the DataGrid has been loaded, the template selector is executing as a result the item is null. Is there a way to execute the Template Selector after the ControlTemplate's DataContext is set. Please help.
Here is my xaml:
<uwpControls:DataGrid Grid.Row="4"
ItemsSource="{x:Bind ViewModel.ServiceMethodsData,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False">
<uwpControls:DataGrid.Resources>
<DataTemplate x:Key="StringTemplate">
<TextBox Width="150" Height="30" VerticalAlignment="Center"></TextBox>
</DataTemplate>
<DataTemplate x:Key="IntegerTemplate">
<controls:TextBoxNumeric Width="150" Height="30" VerticalAlignment="Center"></controls:TextBoxNumeric>
</DataTemplate>
<DataTemplate x:Key="BooleanTemplate">
<CheckBox IsChecked="False"></CheckBox>
</DataTemplate>
<local:MethodValueDataTemplateSelector x:Key="MethodValueTemplateSelector"
StringTemplate="{StaticResource StringTemplate}"
IntegerTemplate="{StaticResource IntegerTemplate}"
BooleanTemplate="{StaticResource BooleanTemplate}"/>
</uwpControls:DataGrid.Resources>
<uwpControls:DataGrid.Columns>
<uwpControls:DataGridTextColumn Header="First Column"
Binding="{Binding ParameterDataType, Mode=OneWay}"/>
<uwpControls:DataGridTemplateColumn Header="Second Column">
<uwpControls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="MethodValueContentControl"
Content="{Binding Path=.}"
ContentTemplateSelector="{StaticResource MethodValueTemplateSelector}"></ContentControl>
</DataTemplate>
</uwpControls:DataGridTemplateColumn.CellTemplate>
</uwpControls:DataGridTemplateColumn>
</uwpControls:DataGrid.Columns>
</uwpControls:DataGrid>
Here is my DataTemplate selector
public class MethodValueDataTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
//I want to do something like if(DataContext.ParameterDataType=="Int") return IntegerTemplate etc
return StringTemplate;
}
}
Here is my ViewModel
public class ServiceUtilityMethodsViewModel
{
private ObservableCollection<VmServiceMethodsViewDataGridModel> _serviceMethodsData;
public ObservableCollection<VmServiceMethodsViewDataGridModel> ServiceMethodsData
{
get => _serviceMethodsData;
set => Set(ref _serviceMethodsData, value);
}
public ServiceUtilityMethodsViewModel(INavigationService navigationService) : base(navigationService)
{
PopulateServiceData();
}
private void PopulateServiceData()
{
ServiceMethodsData = new ObservableCollection<VmServiceMethodsViewDataGridModel>();
ServiceMethodsData.Add(new VmServiceMethodsViewDataGridModel()
{
ParameterName = "Param1",
ParameterDataType = "String"
});
ServiceMethodsData.Add(new VmServiceMethodsViewDataGridModel()
{
ParameterName = "Param2",
ParameterDataType = "Int"
});
ServiceMethodsData.Add(new VmServiceMethodsViewDataGridModel()
{
ParameterName = "Param3",
ParameterDataType = "bool"
});
}
}
}
Here is my Model class
public class VmServiceMethodsViewDataGridModel : BindableBaseThreadSafe
{
private string _parameterName;
private string _parameterDataType;
public string ParameterName
{
get => _parameterName;
set => Set(ref _parameterName, value);
}
public string ParameterDataType //I want the template selector to work based on this column.
{
get => _parameterDataType;
set => Set(ref _parameterDataType, value);
}
}
You should assign the DataTemplateSelector to DataGridTemplateColumn.CellTemplateSelector and DataGridTemplateColumn.CellEditingTemplateSelector directly.
I didn't check the UWP version. I think the UWP DataGridTemplateColumn doesn't have this template selector properties. In this case you can stick to your current XAML, but don't forget to define a CellEditingTemplate too (e.g., replace the TextBlock with a TextBox for the CellEditingTemplate version - better use a TextBlock in your default CellTemplate as it looks better). The properties CellTemplate and CellEditingTemplate exist for sure in the UWP version.
XAML DataGrid definition
<uwpControls:DataGrid ItemsSource="{x:Bind ViewModel.ServiceMethodsData,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False">
<uwpControls:DataGrid.Resources>
<DataTemplate x:Key="StringTemplate">
<TextBox Width="150" Height="30" VerticalAlignment="Center" />
</DataTemplate>
<DataTemplate x:Key="IntegerTemplate">
<controls:TextBoxNumeric Width="150" Height="30" VerticalAlignment="Center" />
</DataTemplate>
<DataTemplate x:Key="BooleanTemplate">
<CheckBox IsChecked="False" />
</DataTemplate>
<local:MethodValueDataTemplateSelector x:Key="MethodValueTemplateSelector"
StringTemplate="{StaticResource StringTemplate}"
IntegerTemplate="{StaticResource IntegerTemplate}"
BooleanTemplate="{StaticResource BooleanTemplate}" />
</uwpControls:DataGrid.Resources>
<uwpControls:DataGrid.Columns>
<uwpControls:DataGridTextColumn Header="First Column"
Binding="{Binding ParameterDataType, Mode=OneWay}" />
<uwpControls:DataGridTemplateColumn Header="Second Column"
CellTemplateSelector="{StaticResource MethodValueTemplateSelector}"
CellEditingTemplateSelector="{StaticResource MethodValueTemplateSelector}">
</uwpControls:DataGridTemplateColumn>
</uwpControls:DataGrid.Columns>
</uwpControls:DataGrid>
The DataTemplateSelector is also quite simple.
The parameters of the SelectTemplateCore override are the item and the item's container (which is a FrameWorkElement and a ContentControl most of the time).
The item is always the data model and the DataContext of the current row. In your case the item is of type VmServiceMethodsViewDataGridModel.
The container is the FrameWorkElement that wraps the model for rendering e.g. ListBoxItem. In your case the container should be of type DataGridRow.
Simply cast the item parameter to the appropriate type and evaluate it.
MethodValueDataTemplateSelector.cs
public class MethodValueDataTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
// Return if the control is not loaded yet and the item is therefore null
// or the item is of the wrong type
if (!(item is VmServiceMethodsViewDataGridModel dataModel))
{
return null;
}
// I want to do something like:
// if(DataContext.ParameterDataType=="Int") return IntegerTemplate etc
switch (dataModel.ParameterDataType)
{
case string value when value.Equals("Int", StringComparison.OrdinalIgnoreCase):
return IntegerTemplate;
case string value when value.Equals("String", StringComparison.OrdinalIgnoreCase):
return StringTemplate;
case string value when value.Equals("Bool", StringComparison.OrdinalIgnoreCase):
return BooleanTemplate;
default:
return null;
}
}
}
Related
I have a WPF DataGrid bound to a DataTable. The DataTable has columns containing strings and columns containing custom objects (Requirement):
public class PdfFormData
{
public string SupplierName { get; set; }
public List<Requirement> Requirements = new List<Requirement>();
public class Requirement
{
public string ID { get; set; }
public string SupplierStatus { get; set; }
public string SupplierComment { get; set; }
public string OEMStatus { get; set; }
}
}
I'm adding the columns to the DataGrid with this loop:
PHDataGrid.ItemsSource = dataSet.Tables["ReqIfTable"].DefaultView;
PHDataGrid.Columns.Clear();
foreach (DataColumn dataColumn in dataSet.Tables["ReqIfTable"].Columns)
{
if(dataColumn.DataType == typeof(ReqForms.PdfFormData.Requirement))
{
DataGridTemplateColumn dgColumn = new DataGridTemplateColumn();
dgColumn.Header = dataColumn.ColumnName;
dgColumn.CellTemplate = (DataTemplate)FindResource("dgTemplateRequirement");
dgColumn.CanUserSort = true;
dgColumn.IsReadOnly = true;
PHDataGrid.Columns.Add(dgColumn);
}
else
{
DataGridTextColumn dgColumn = new DataGridTextColumn();
dgColumn.Header = dataColumn.ColumnName;
Binding dgBinding = new Binding(dataColumn.ColumnName);
dgColumn.Binding = dgBinding;
dgColumn.CanUserSort = true;
dgColumn.IsReadOnly = true;
PHDataGrid.Columns.Add(dgColumn);
}
}
And I have defined a template for showing my Requirement objects:
<Window.Resources>
<DataTemplate x:Key="dgTemplateRequirement">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Requirement.SupplierStatus}" />
<TextBlock Text="{Binding Requirement.SupplierComment}" />
<TextBlock Text="{Binding Requirement.OEMStatus}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
The Binding to the Requirement properties (SupplierStatus, SupplierComment, ...) is not working and stays empty. How can I bind to the properties of my custom object that is stored in the DataTable?
The bindings do not work, because the DataContext of the the templated cell is a DataRowView.
You can either create a converter as in this related post or just access the members directly with an indexer binding path, where requirement is the name of your column in the DataTable, e.g.:
<TextBlock Text="{Binding Row[requirement].SupplierStatus}" />
I set the DataContext of the StackPanel to the Requirement object, so it is even easier to bind.
<DataTemplate x:Key="dgTemplateRequirement">
<StackPanel Orientation="Vertical" DataContext="{Binding Row[requirement]}">
<TextBlock Text="{Binding SupplierStatus}" />
<TextBlock Text="{Binding SupplierComment}" />
<TextBlock Text="{Binding OEMStatus}" />
</StackPanel>
</DataTemplate>
I found the solution in this post:
WPF DataGrid - Databind to DataTable cell in CellTemplates DataTemplate
You have to use a converter to reach from the table to the actual object.
I have an ItemsControl:
<ItemsControl x:Name="myList" ItemTemplateSelector="{DynamicResource mySelectorTemplate}" ItemsPanel="{StaticResource myPanelTemplate}"/>
I want to take one of the items and make a button with his logic:
<Button Style="{StaticResource myButtonStyle}"/>
One of the items of myList has a boolean with true value. This is the item with which the button will be made:
Private myCollection As ObservableCollection(Of Items.Subitems)
myList.ItemsSource = myCollection
When myCollection.myBooolean = true, is the field that gives to the item this special treatment.
What would be an efficient way to do this?
There are many possibilitys for this - but this looks like the best way.
You want to use a DataTemplateSelector. You can choose n DataTemplates with this by your own overwritten logic.
C#
Sample Class:
public class MySampleClass
{
public string Name;
public bool MyFlag;
}
Sample Template Selector
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate IsTrueTemplate
{
get;
set;
}
public DataTemplate IsFalseTemplate
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
MySampleClass temp = item as MySampleClass;
if(temp != null)
{
if (temp.MyFlag)
{
return IsTrueTemplate;
}
else
{
return IsFalseTemplate;
}
// And so on
}
else
{
return base.SelectTemplate(item, container);
}
}
}
Usage in XAML
<ListBox>
<ListBox.ItemTemplateSelector>
<example:MyTemplateSelector>
<example:MyTemplateSelector.IsTrueTemplate>
<DataTemplate>
<StackPanel>
<Button Style="{StaticResource myButtonStyle}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</example:MyTemplateSelector.IsTrueTemplate>
<example:MyTemplateSelector.IsFalseTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</example:MyTemplateSelector.IsFalseTemplate>
</example:MyTemplateSelector>
</ListBox.ItemTemplateSelector>
</ListBox>
example is your DataTemplateSelector-Namespace.
I would recommend you - to store the DataTemplates in the Resources and set it via StaticResource.
I have a sample MVVM WPF application and I'm having problems creating DataTemplates for my dynamically loaded model. Let me try explain:
I have the following simplified classes as part of my Model, which I'm loading dynamically
public class Relationship
{
public string Category { get; set; }
public ParticipantsType Participants { get; set; }
}
public class ParticipantsType
{
public ObservableCollection<ParticipantType> Participant { get; set; }
}
public class ParticipantType
{
}
public class EmployeeParticipant : ParticipantType
{
public EmployeeIdentityType Employee { get; set; }
}
public class DepartmentParticipant : ParticipantType
{
public DepartmentIdentityType Department { get; set; }
}
public class EmployeeIdentityType
{
public string ID { get; set; }
}
public class DepartmentIdentityType
{
public string ID { get; set; }
}
Here is how my View Model looks like. I created a generic object Model property to expose my Model:
public class MainViewModel : ViewModelBase<MainViewModel>
{
public MainViewModel()
{
SetMockModel();
}
private void SetMockModel()
{
Relationship rel = new Relationship();
rel.Category = "213";
EmployeeParticipant emp = new EmployeeParticipant();
emp.Employee = new EmployeeIdentityType();
emp.Employee.ID = "222";
DepartmentParticipant dep = new DepartmentParticipant();
dep.Department = new DepartmentIdentityType();
dep.Department.ID = "444";
rel.Participants = new ParticipantsType() { Participant = new ObservableCollection<ParticipantType>() };
rel.Participants.Participant.Add(emp);
rel.Participants.Participant.Add(dep);
Model = rel;
}
private object _Model;
public object Model
{
get { return _Model; }
set
{
_Model = value;
NotifyPropertyChanged(m => m.Model);
}
}
}
Then I tried creating a ListBox to display specifically the Participants Collection:
<ListBox ItemsSource="{Binding Path=Model.Participants.Participant}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The problem is:
I don't know how to create a template that can handle both type of ParticipantTypes, in this case I could have EmployeeParticipant or DepartmentParticipant so depending on that, the data binding Path would be set to Employee or Department properties accordingly
I though about creating a DataTemplate for each type (e.g. x:Type EmployeeParticipant) but the problem is that my classes in my model are loaded dynamically at runtime so VisualStudio will complain that those types don't exist in the current solution.
How could I represent this data in a ListBox then if my concrete types are not known at compile time, but only at runtime?
EDIT: Added my test ViewModel class
You can still create a DataTemplate for each type but instead of using DataType declarations to have them automatically resolve you can create a DataTemplateSelector with a property for each template (assigned from StaticResource in XAML) that can cast the incoming data item to the base class and check properties or otherwise determine which template to use at runtime. Assign that selector to ListBox.ItemTemplateSelector and you'll get similar behavior to what DataType would give you.
That's not a good view-model. Your view-model should be view-centric, not business-centric. So make a class that can handle all four cases from a visual perspective, then bridge your business classes over to that view-model.
EDIT:
Working off your code:
<ListBox ItemsSource="{Binding Path=Model.Participants}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<TextBlock Text={Binding Id} />
<TextBlock Text={Binding Name} />
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I changed the binding, I assume that was a mistake?
I would create a ViewModel for Participant:
public class Participant_VM : ViewModelBase
{
private string _name = string.Empty;
public string Name
{
get
{
return _name ;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(() => Name);
}
private string _id= string.Empty;
public string Id
{
get
{
return _id;
}
set
{
if (_id== value)
{
return;
}
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Modify the ListBox as follows.
<ListBox ItemsSource="{Binding Model.Participants.Participant}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type loc:DepartmentParticipant}">
<Grid>
<TextBlock Text="{Binding Department.ID}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type loc:EmployeeParticipant}">
<Grid>
<TextBlock Text="{Binding Employee.ID}"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
<ContentPresenter Content="{Binding }"/>
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
loc refers to the namespace in which the DepartmentParticipant and EmployeeParticipant are present. Hope you are familiar with adding namespaces.
I am trying to bind the ComboBox below to the list of Characters in the ObservableCollection, but it wont show anything. Any ideas why?
XAML:
<TabControl ItemsSource ="{Binding TextEditors}"
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox> ItemsSource="{Binding TextLines}"
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<ComboBox
ItemsSource="{Binding DataContext.InvCharacter, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}"
DisplayMemberPath="name"
SelectedValuePath="cid"
SelectedValue="{Binding cid}">
</ComboBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
this is the class i am referring to:
class TextEditorVM: IViewModel {
public ObservableCollection<TextLineVM> TextLines { get { return textLines; } set { textLines = value;} }
public ObservableCollection<T_Character> InvCharacters { get { return invCharacters; } set { invCharacters = value; } }
public TextEditorVM(T_Dialogue dialogue)
{
DialogueManager.Instance.Register(this);
this.TextLines = new ObservableCollection<TextLineVM>();
this.InvCharacters = new ObservableCollection<T_Character>();
}
}
and the MainVM:
class MainVM : IViewModel
{
public ObservableCollection<TextEditorVM> TextEditors { get { return textEditors; } set { textEditors = value; OnPropertyChanged("TextEditors"); }
}
my T_Character Class looks like this now :
public class T_Character
{
public String cid { get; set; }
public String name { get; set; }
public T_Character(String cid, String name)
{
this.cid = cid;
this.name = name;
}
}
The DataContext of the TabControl is of type MainVM. The RelativeSource of the ComboBox binding should not be the TabControl but rather the ListBox.
Your InvCharacters property is on your TextEditorVM object which is in your ObservableCollection, however your binding is referencing TabControl.DataContext, which is MainVM, and does not contain that property.
Switch your RelativeSource binding to reference TabItem (it gets created automatically when you bind TabControl.ItemsSource) or ListBox to reference your TextEditorVM object
<ComboBox ItemsSource="{Binding DataContext.InvCharacters,
RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}">
</ComboBox>
I have an ItemsControl with a DataTemplate that has been defined. My ItemsControl definition looks like the following:
<ItemsControl x:Name="myItemsControl" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<CheckBox x:Name="myCheckBox" Content="{Binding Name}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is a simplified version of my DataTemplate. Regardless, when a user clicks a button on the page, I want to loop through the items in myItemsControl and determine if the CheckBox element associated with the item is checked.
How do I determine if a CheckBox is checked for a specific item within an ItemsControl?
Thank you!
Add a property to your data class and databind it, then iterate over the collection itself.
public class myDataClass
{
public string Name { get; set;}
public bool IsSomething { get; set; }
}
<CheckBox x:Name="myCheckBox" Content="{Binding Name}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
You can try something like traditional iteration:
public bool? TestMyCheckbox(string bindingName)
{
foreach (var item in myItemsControl.Items)
{
if (item.GetType() == typeof(CheckBox))
{
var checkbox = (CheckBox)item;
if (checkbox.Content.Equals(bindingName))
{
return (checkbox.IsChecked);
}
}
}
return null;
}
Additionaly (this may better fit your needs) you can look for a list of checkboxes bindings that are checked:
public IEnumerable<object> TestMyCheckboxes(ItemsControl control)
{
return from Control x in control.Items
where x.GetType().Equals(typeof(CheckBox)) && ((CheckBox)x).IsChecked == true
select ((CheckBox)x).Content;
}