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.
Related
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;
}
}
}
I have an object(MyCals) that is type of Cal Collection. (Cal is an object).
The MyCals consists of 2 Cal, where each Cal has List of Events (Events does not has the INotifyPropertyChanged interface implemented, it has many properties like Summary, Id,...)
<TabControl ItemsSource="{Binding Path=MyCals, UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<!-- Tab Header -->
<TextBlock Text="{Binding Path=ProductID}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<DataGrid AutoGenerateColumns="False"
HorizontalGridLinesBrush="LightGray"
ItemsSource="{Binding Path=Events, UpdateSourceTrigger=PropertyChanged}"
VerticalGridLinesBrush="LightGray">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Summary}" Header="Summary" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Now the TabControl will show 2 tabs with the right header, but the Datagrid wont update when I change select the other tab. So if the First Tab is selected, that datagrid will be showed with the Data from first Cal, but when I select the second tab, data in the datagrid are not getting updated
(Same Events in the MyCals[1])
any help is really appreciated
I don't know how you implemented the ViewModel so I did a quick test and it works.
This is the code of the ViewModel:
class MainViewModel : AbstractViewModel
{
public MainViewModel()
{
this.MyCals = new ObservableCollection<Cal>();
this.MyCals.Add(new Cal());
this.MyCals.Add(new Cal());
}
public ObservableCollection<Cal> MyCals { get; set; }
}
public class Cal
{
public Cal()
{
this.ProductID = "Product " + Guid.NewGuid().ToString();
this.Events = new ObservableCollection<Event>();
this.Events.Add(new Event());
this.Events.Add(new Event());
}
public string ProductID { get; set; }
public ObservableCollection<Event> Events { get; set; }
}
public class Event
{
public Event()
{
this.Summary = "Event Summary " + Guid.NewGuid().ToString();
}
public string Summary { get; set; }
}
Please share a bit more about the code behind if my sample didn't help.
There is a problem with my ListView binding, it is not showing anything in the list.
I have the following ViewModel:
namespace Users.ViewModel
{
public class AllUsersViewModel
{
public List<Module> _modules = new List<Module>();
#region Constructor
public AllUsersViewModel()
{
this.SetModuleList();
}
void SetModuleList()
{
_modules = ModuleRepository.GetModules();
}
}
}
And the GetModules() function is return the set just fine. I have done a check by loop through the _modules List.
Here is my XAML:
<ListBox x:Name="lstModules" Width="190" ItemsSource="{Binding _modules}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ModuleName}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What is the problem, why doesn't the ListBox populate?
_modules is a field, and you cannot bind to fields. Make a property which exposes the value of _modules:
public IEnumerable<Module> Modules
{
get { return _modules; }
}
and bind to this property instead:
<ListBox x:Name="lstModules" Width="190" ItemsSource="{Binding Modules}">
The Modules collection should be a property, not a variable.
public List<Module> Modules { get; set; }
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;
}