Different DataTemplate depending on the enumeration value of a property - c#

I want something similar to what was asked here - but, I need the templates to depend on the value of a property, which is an enum.
The class looks simlar to this:
class ResultBlock
{
public string Name { get; set; }
public BlockType Type { get; set; }
public IList<ResultBlock> ChildBlocks { get; private set; }
}
Where BlockType has three different values, BLOCK, FILE, FOLDER - Now, I want to create a data template to present differently, depending on what value ResultBlock.Type has in the current object.
I tried doing this with DataType=, but that didn't work, obviously. I'm sure there is some way to do this very easily in XAML only.
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type docom:ResultBlock}" ItemsSource="{Binding ChildBlocks}">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type docom:BlockType.BLOCK}">
<TextBlock Text="BLOCK:{Binding Name}" />
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>

You can trigger on the property, e.g.:
<HierarchicalDataTemplate DataType="{x:Type docom:ResultBlock}"
ItemsSource="{Binding ChildBlocks}">
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding BlockType}" Value="BLOCK">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Data template for BLOCK blocks -->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- More triggers -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</HierarchicalDataTemplate>
Yes, it's verbose. (You can define the templates for the different types as keyed resources and then reference them in the Setters though)

<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
<Grid>
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
</Grid>
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is Task)
{
Task taskitem = item as Task;
if (taskitem.Priority == 1)
return
element.FindResource("importantTaskTemplate") as DataTemplate;
else
return
element.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}
This is implemented for ListBox but the idea can be same for DataGrid/TreeView .I hope this will help.

one way is to use a TemplateSelector and within the selector do what you want depending on your BlockType.
or you create wrapperviewmodels like:
public class ResultBlockBlock{}
public class ResultBlockFile{}
public class ResultBlockFolder{}
then you can go the DataTemplate DataType way

Related

Adding custom style in WPF

I have created custom style as below
public class MenuStyle: StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
// my code
}
}
I am using this style in xaml file as below.
I am using it as below.
Added namespace as below
xmlns:style="clr-namespace:MedicalStore.Styles"
Added resource as
<UserControl.Resources>
<style:MenuStyle x:Key="MenuStyle"></style:MenuStyle>
<Style TargetType="MenuItem" x:Key="SelectedMenuItem">
<Setter Property="Background" Value="White"></Setter>
</Style>
</UserControl.Resources>
and using it as below
<Menu DockPanel.Dock="Top" FontSize="22" Background="Green" HorizontalAlignment="Right" x:Name="MainMenu"
ItemsSource="{Binding Path=MenuItems}" DisplayMemberPath="Text"
ItemContainerStyleSelector="{Binding MenuStyle}">
</Menu>
but as I run my app, debugger never goes to MenuStyle class. What is the issue?
A good approach is to implement Style-properties in your MenuStyle-Class.
public class MenuStyle : StyleSelector
{
// Declare all the style you're going to need right here
public Style StyleMenuItem { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
// here you can check which type item is and return the corresponding style
if(item != null && typeof(item) == typeof(YourType))
{
return StyleMenuItem;
}
}
}
With that, you can do following in your XAML
<UserControl.Resources>
<Style TargetType="MenuItem" x:Key="SelectedMenuItemStyle">
<Setter Property="Background" Value="White"></Setter>
</Style>
<style:MenuStyle x:Key="MenuStyle" StyleMenuItem="{StaticResource SelectedMenuItemStyle}"/>
</UserControl.Resources>
Note that I changed the x:Key of your Style to make things more clear.
Next thing is in your Menu:
<Menu DockPanel.Dock="Top" FontSize="22" Background="Green" HorizontalAlignment="Right" x:Name="MainMenu"
ItemsSource="{Binding Path=MenuItems}" DisplayMemberPath="Text"
ItemContainerStyleSelector="{StaticResource MenuStyle}">
</Menu>
There, you have to use StaticResource instead of Binding.
This should be all.

Disable a subview depending viewmodel bool property (MVVM architecture)

I have a view that contains multiple subview (UserControl).
<StackPanel>
<local:SubViewGPS/>
<local:SubViewText/>
</StackPanel>
This view binds to a ViewModel and I would like to load or not load subview depending on bool properties of my ViewModel
private bool isGPSCompatible;
public bool IsGPSCompatible {
get { return isGPSCompatible; }
set {
if (isGPSCompatible != value) {
isGPSCompatible = value;
NotifyPropertyChanged();
}
}
}
private bool isTextCompatible;
public bool IsTextCompatible {
get { return isTextCompatible; }
set {
if (isTextCompatible != value) {
isTextCompatible = value;
NotifyPropertyChanged();
}
}
}
I actually don't want to "Disable" or change the "Visibility" but really avoid to load the component if the property is false. According this post: Different views / data template based on member variable the combination of DataTemplate and DataTrigger seems to be a way to reach the goal but I was wondering if it exist something simpler. Thanks for your help
I finally used this solution:
<UserControl x:Class="RLinkClient.LocationView"
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:local="clr-namespace:client"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="GPSLocationViewTemplate">
<local:GPSControl/>
</DataTemplate>
<DataTemplate x:Key="NoGPSViewTemplate">
<TextBlock Text="GPS Disabled" TextAlignment="Center" VerticalAlignment="Center" FontStyle="Italic" Opacity="0.1" FontWeight="Bold" FontSize="14"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource NoGPSViewTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding GPSCapable}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource GPSLocationViewTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</UserControl>
but Frank answer is totally acceptable if I could change the ViewModel structure.
As you're using MVVM, you could create separate sub-viewmodels for the different views and add them to some ObservableCollection, depending on their properties:
<!-- This would replace your StackPanel -->
<ItemsControl ItemsSource="{Binding Capabilities}">
<ItemsControl.Resources>
<DataTemplate DataType="localVm:GpsViewModel">
<local:SubViewGPS />
</DataTemplate>
<DataTemplate DataType="localVm:TextViewModel">
<local:SubViewText />
</DataTemplate>
<!-- ... -->
</ItemsControl.Resources>
</ItemsControl>
You would have an ObservableCollection in your view model, like this:
public ObservableCollection<ICapability> Capabilities { get; private set; }
and add sub-viewmodels implementing ICapability to this as needed.
Based on the condition, I suggest adding the view object in code instead of XAML. Only instantiate when needed to avoid unnecessary initialization routines. That is, don't instantiate the view before checking the condition:
if (IsGPSCompatible )
myStackPanel.Children.Add((new SubViewGPSView()));
if (IsTextCompatible )
myStackPanel.Children.Add((new SubViewText()));

How to bind an element defined inside the control template for a custom control to a property defined in a subclass (of custom control class)

I would like to bind an element inside a control template for a custom control to a property defined in a child class defined inside the custom control class. What might be the syntax for doing that (see {Binding ???})?
Some code...
C# code:
public class CustmCntrl : Control
{
// blablabla
public class SubChildClass: INotifyPropertyChanged
{
public double X { get; private set; }
public string info { get; private set; }
}
}
XAML:
<Style TargetType="{x:Type local:CustmCntrl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustmCntrl}">
<Grid> ... </Grid>
<ItemsControl
ItemsSource="{Binding stuffToDisplay, RelativeSource={RelativeSource TemplatedParent}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<Grid> ... </Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:X
ToolTip="{Binding info ???}">
<local:X.RenderTransform>
<TranslateTransform X="{Binding X ???}"/>
</local:X.RenderTransform>
</local:X>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I can't test this at the moment, but you should be able to bind by adding the name of the SubChildClass class. Try something like this:
<TextBlock Text="{Binding SubChildClass.PropertyName, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourCustomControl}}}">
If that doesn't work, try this:
<TextBlock Text="{Binding (SubChildClass.PropertyName), RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourCustomControl}}}">
You can find out more from the Property Path Syntax page on MSDN.
UPDATE >>>
Yeah, after getting a chance to test this out in a project, it seems that you can't data bind directly with class properties like that. However, if you just wanted to declare the class there as a container for some basic data items in your control then that is fine. As long as you define some DependencyPropertys to hold them, you can use that class just fine:
private static DependencyProperty ItemsProperty = DependencyProperty.Register("Items",
typeof(ObservableCollection<SubChildClass>), typeof(CustomControl1));
public ObservableCollection<SubChildClass> Items
{
get { return (ObservableCollection<SubChildClass>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
In Generic.xaml:
<ListBox ItemsSource="{Binding Items,
RelativeSource={RelativeSource AncestorType={x:Type local:CustmCntrl}}}" />

Define different DataTemplate for same source

I'm trying to show collection of bool with a DataTemplate for ListView.
Here's the code:
In MainWindow.xaml
<Window.Resources>
<DataTemplate x:Key="ListItemCheckBoxTemplate">
<CheckBox IsChecked="{Binding Mode=OneWay}"/>
</DataTemplate>
<DataTemplate x:Key="ListItemRadioButtonTemplate">
<RadioButton IsChecked="{Binding Mode=OneWay}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="ListViewSample" />
<ListBox ItemsSource="{Binding MyData}" ItemTemplate="{StaticResource ListItemCheckBoxTemplate}" Grid.Column="1"/>
</Grid>
In MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
private List<bool> myBooleanCollection;
public List<bool> MyData
{
get { return myBooleanCollection; }
set { myBooleanCollection = value; }
}
public ViewModel()
{
myBooleanCollection = new List<bool>();
myBooleanCollection.Add(true);
myBooleanCollection.Add(false);
myBooleanCollection.Add(true);
myBooleanCollection.Add(true);
myBooleanCollection.Add(false);
}
}
Instead of ItemTemplate ListItemCheckBoxTemplate I want to apply ListItemRadioButtonTemplate with Radio button. How I can specify the use of ItemTemplate for the same source in xaml. Do I need to change the DataSource for the same or
do I have some way to specify the DataTemplate in xaml based on condition.
You did not specify what is the condition on which you want to change template but you can add another property to your view model, say AllowMultiple:
public class ViewModel: INotifyPropertyChanged
{
private List<bool> myBooleanCollection;
public List<bool> MyData
{
get { return myBooleanCollection; }
set { myBooleanCollection = value; }
}
private bool _allowMultiple;
public bool AllowMultiple
{
get { return _allowMultiple; }
set
{
if (_allowMultiple != value)
{
_allowMultiple = value;
OnPropertyChanged("AllowMultiple");
}
}
}
}
and then change ListBox.Style to:
<ListBox ItemsSource="{Binding MyData}">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate" Value="{StaticResource ListItemCheckBoxTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=AllowMultiple}" Value="False">
<Setter Property="ItemTemplate" Value="{StaticResource ListItemRadioButtonTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
assuming that ViewModel implements INotifyPropertyChanged you can then change ItemTemplate buy changing AllowMultiple against view model
You can specify DataTemplate for each item with the followign attribute:
ItemTemplate="{StaticResource ListItemCheckBoxTemplate}
Change this to:
ItemTemplate="{StaticResource ListItemRadioButtonTemplate}
If you need multiple item templates in the same ListBox then you can provide custom template selection logic via the DataTemplateSelector Class
If you want to do it in Xaml, you will need to expose property which you can bind to as a selector. Have a look at A Data Template Selector in Xaml
You need create DataTemplateSelector and using this for ItemTemplateSelector. You also need to determine the condition under which you will return the DataTemplate. Example:
XAML
<Window x:Class="YourNamespace.MainWindow" ...
xmlns:this="clr-namespace:YourNamespace"
<Window.Resources>
<DataTemplate x:Key="OneTemplate">
<CheckBox IsChecked="{Binding Mode=OneWay}" />
</DataTemplate>
<DataTemplate x:Key="SecondTemplate">
<RadioButton IsChecked="{Binding Mode=OneWay}" />
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding MyData}">
<ListBox.ItemTemplateSelector>
<this:MyItemTemplateSelector OneTemplate="{StaticResource OneTemplate}"
SecondTemplate="{StaticResource SecondTemplate}" />
</ListBox.ItemTemplateSelector>
</ListBox>
Code-behind
public class MyItemTemplateSelector : DataTemplateSelector
{
public DataTemplate OneTemplate { get; set; }
public DataTemplate SecondTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
bool myItem = (bool)item;
if (myItem == true)
{
return OneTemplate;
}
return SecondTemplate;
}
}

WPF ComboBox selection change after switching tabs

I made a project based on nested tabs.
the nested tabs are different instance of the same viemModel and the same UI.
when I switch between the tabs he comboboxes present in the tabs chenge thei selection depending on the tab that is loosing focus.
I add both the viewmodels and the view of my test project.
thank you in advance for your help
main window
<Window.Resources>
<DataTemplate DataType="{x:Type local:IntermediateViewModel}">
<local:IntermediateView />
</DataTemplate>
<DataTemplate x:Key="HeaderedTabItemTemplate">
<Grid>
<ContentPresenter
Content="{Binding Path=Header, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" >
</ContentPresenter>
</Grid>
</DataTemplate>
<Style x:Key="SimpleTabItemStyle" TargetType="TabItem">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" BorderThickness="1" BorderBrush="#555959">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center"
ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True" Height ="40" MinWidth ="90"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#555959" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="DefaultTabControlTemplate">
<TabControl IsSynchronizedWithCurrentItem="True"
BorderThickness="0"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource HeaderedTabItemTemplate}"
ItemContainerStyle="{StaticResource SimpleTabItemStyle}"
SelectionChanged="TabControl_SelectionChanged"
/>
</DataTemplate>
<!---->
</Window.Resources>
<Grid MinHeight="200" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition Height="260*" />
<RowDefinition Height="51*" />
</Grid.RowDefinitions>
<Border >
<ContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{DynamicResource DefaultTabControlTemplate}"
/>
</Border>
<Button Grid.Row="1" Content="Add" Command="{Binding AddCommand}"/>
</Grid>
view model (create a different istance each time)
class MainWindowViewModel : WorkspacesViewModel<IntermediateViewModel>
{
public MainWindowViewModel()
{
this.WorkspacesView.CurrentChanged += new EventHandler(WorkspacesView_CurrentChanged);
}
void WorkspacesView_CurrentChanged(object sender, EventArgs e)
{
}
RelayCommand myVar = null;
public ICommand AddCommand
{
get
{
return myVar ?? (myVar = new RelayCommand(param =>
{
SetWindow(new IntermediateViewModel("AA" + this.Workspaces.Count) );
}));
}
}
first level tab
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ClassViewModel}">
<local:ClassView />
</DataTemplate>
</UserControl.Resources>
<Border>
<ContentControl Content="{Binding Path=CurrentWorkspace, Mode=OneWay}" Loaded="ContentControl_Loaded" DataContextChanged="ContentControl_DataContextChanged" IsVisibleChanged="ContentControl_IsVisibleChanged" LayoutUpdated="ContentControl_LayoutUpdated" TargetUpdated="ContentControl_TargetUpdated" Unloaded="ContentControl_Unloaded" />
</Border>
first level viewmodel
class IntermediateViewModel : WorkspacesViewModel
{
public string Header { get; set; }
public IntermediateViewModel(string header)
{
Header = header;
SetWindow(new ClassViewModel(header));
}
}
nested tab
<UserControl.Resources>
<CollectionViewSource x:Key="StatusView" Source="{Binding Path=StatusList}"/>
</UserControl.Resources>
<Grid>
<ComboBox Name="_spl2Status" ItemsSource="{Binding Source={StaticResource StatusView}}"
SelectedValue="{Binding Path=MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="FL_TYPE"
DisplayMemberPath="ID_TYPE" Margin="76,12,0,0" Height="40" VerticalAlignment="Top" HorizontalAlignment="Left" Width="146"
DataContextChanged="_spl2Status_DataContextChanged"
IsVisibleChanged="_spl2Status_IsVisibleChanged"
Loaded="_spl2Status_Loaded"
SelectionChanged="_spl2Status_SelectionChanged"
>
</ComboBox>
</Grid>
nested tab view model
public enum myTypes
{
tipo0 = 0,
tipo1 = 1,
tipo2 = 2,
}
class ClassViewModel : WorkspaceViewModel
{
public ClassViewModel(string name)
{
Name = name;
}
public string Name { get; set; }
private List<IntEnumType> _statusList = null;
public List<IntEnumType> StatusList
{
get
{
if (_statusList == null)
_statusList = new List<IntEnumType>()
{
new IntEnumType((int)myTypes.tipo0, myTypes.tipo0.ToString()),
new IntEnumType((int)myTypes.tipo1, myTypes.tipo1.ToString()),
new IntEnumType((int)myTypes.tipo2, myTypes.tipo2.ToString()),
};
return _statusList;
}
}
private int myVar = 1;
public int MyProperty
{
get
{
return myVar;
}
set
{
if (myVar != value)
{
myVar = value;
OnPropertyChanged(() => MyProperty);
}
}
}
}
public class TabItemStyleSelector : StyleSelector
{
public Style MainTabItem { get; set; }
public Style ChildrenTabItem { get; set; }
public Style SpecificationTabItem { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
//if (item is IHome)
// return MainTabItem;
//else if (item is SpecificationItemViewModel)
// return SpecificationTabItem;
//else
return ChildrenTabItem;
}
}
The code is a little hard to completely follow, but I'm guessing that the issue is that there is only one instance of your ClassViewModel and it is where the selection for the combo box is stored {Binding Path=MyProperty, so whatever is stored in MyProperty will be reflected in all instances of the combo box regardless of where they live.
Well this is a bit late, but as I'm facing the same issue, I want to share my analysis.
When you change your tabs, you change the DataContext of the current Tab to your other ViewModel and hence also the ItemsSource of your ComboBox.
In case your previously selected Item (SelectedItem) is not contained within the new ItemsSource, the ComboBox fires a SelectionChanged-Event and therefore sets the SelectedIndex to -1.
Altough this default behaviour of the ComboBox might make sense, it's very annoying in many cases.
We've derived an own class from ComboBox, handling that. But it's not very satisfying as you loose some default behaviour you most probably need.
The problem is in your loaded event handlers.
When you switch tabs your unloading one tab and loading a new one.
I suspect your changing MyComboBox.SelectedIndex in _spl2Status_Loaded.

Categories