How to change HierarchicalDataTemplate? - wpf

How can I change HierarchicalDataTemplate by DataTrigger in my TreeView?
<TreeView Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
ItemsSource="{Binding Nodes}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="group">
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding Title}" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="page">
<Setter Property="Content">
<Setter.Value>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Page.Name}" />
<TextBlock Text="{Binding Page.Format}" />
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In ItemSource of TreeView i put list of Node objects:
public class Node
{
public string Title { get; set; }
public Page Page { get; set; }
public string Type { get; set; }
public List<Node> Nodes { get; set; } = new List<Node>();
}
And this is the result:
What am I doing wrong?

In WPF, the "correct" way to create complex content is with a template.
I tried the XAML below, and it worked. Where I bind Content="{Binding}" on the inner ContentControl, that just binds the content of the contentcontrol to the DataContext of the parent -- in this case, the Node object.
<TreeView
Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
ItemsSource="{Binding Nodes}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<ContentControl
x:Name="PART_ContentControl"
Content="{Binding}"
/>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Group">
<Setter Property="ContentTemplate" TargetName="PART_ContentControl">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="Page">
<Setter Property="ContentTemplate" TargetName="PART_ContentControl">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Page.Name}" />
<TextBlock Text="{Binding Page.Format}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Related

WPF - Simple timeline in DataGrid

i need to realize a simple timeline in a datagrid (72 months).
Today i started with a test (class and xaml below), where i create a property for each month as bool, create a datagridview with 74 columns (Title,Amount and 72 months), and bind each month-column (DataGridTemplateColumn) to the bool value (with da datatrigger to turn it red). Finally it looks ok and works, but i think, there must be a better way to realize it (e.g. don't create a property for each month/don't create a DataGridTemplateColumn for each month)
Thanks in advance for your inputs!
Best regards,
Flo
Class:
public class TestItem
{
public string Ttile { get; set; }
public int Amount { get; set; }
public bool Month1 { get; set; }
public bool Month2 { get; set; }
}
Xaml:
<DataGrid x:Name="TimeLineDataGrid" Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="False" HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn x:Name="TitleTextColumn" Binding="{Binding Title}">
<DataGridTextColumn.Header>
<TextBlock TextAlignment="Center" Text="laufende und geplante
Vorhaben" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn x:Name="AmountColumn" Binding="{Binding Amount}" Header="Amount">
</DataGridTextColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="textbox1" HorizontalAlignment="Stretch" Margin="0" Padding="0" VerticalAlignment="Stretch">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Month1}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="textbox1" HorizontalAlignment="Stretch" Margin="0" Padding="0" VerticalAlignment="Stretch">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Month2}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Had the same problem. It's not a great solution, but i've made just one DataGridTemplateColumn with lots of different additional classes and resources. Took a lot of time - not a great solution, but m.b. will be helpful for you, so now it looks as something like that:
<DataGridTemplateColumn
x:Name="MainGanttColumn" Width="SizeToCells">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="BorderBrush" Value="#FFB2BECE"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFC3D4EB" Offset="1"/>
<GradientStop Color="#FFECF5FC"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="ControlTimeLine" TargetType="DataGridColumnHeader">
<Border x:Name="BackgroundBorder" Margin="0" BorderThickness="1,0,1,0" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}">
<Border.Child>
<ItemsControl DataContext="{Binding Source={StaticResource ProxyElement},Path=Data.GanttCalendar}" Margin="0" Style="{StaticResource GanttHeaderStyle}"/>
</Border.Child>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ItemsControl Panel.ZIndex="3" Margin="0" ItemsSource="{Binding GanttTasks}" Name="Lines">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:GanttElementsPanel DataContext="{Binding Source={StaticResource ProxyElement},Path=Data.GanttCalendar}" MinDate="{Binding MinDate}" MaxDate="{Binding MaxDate}" GanttVariant="{Binding GanttVariant}" DayWidth="{StaticResource GridLineDayWidth}" MonthWidth="{StaticResource GridLineMonthWidth}" YearWidth="{StaticResource GridLineYearWidth}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="local:GanttElementsPanel.StartDate" Value="{Binding Path=Start}"/>
<Setter Property="local:GanttElementsPanel.EndDate" Value="{Binding Path=End}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Style="{StaticResource RectangleStyle}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentControl Panel.ZIndex="0" Margin="0" Padding="0" ContentTemplate="{StaticResource GanttGridLinesTemplate}" Content="{Binding Source={StaticResource ProxyElement},Path=Data.GridLines}" Height="{Binding ElementName=Lines.ActualHeight}"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Result Image

HowTo implement commands for selection of treenodes in WPF treeview

Again I'm a bit lost in WPF treeview.
I populated my treeview with some data and I'd like to fire a command, when clicking on a node and get its values in that command.
My treeview-xaml looks like this:
<TreeView DataContext="{Binding ProjectTree}" ItemsSource="{Binding ProjectNode}" DockPanel.Dock="Left" Margin="0 0 2 0" x:Name="ProjectTree" Grid.Column="0">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Margin="3" Source="{Binding ItemType, Converter={x:Static misc:TreeItemImageConverter.Instance }}" Width="20" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
How can I trigger a command on click on a treeviewitem here?
SOLUTION
After some experiments, It works for me that way:
xaml:
<TreeView DataContext="{Binding ProjectTree}" ItemsSource="{Binding ProjectNode}" DockPanel.Dock="Left"
x:Name="ProjectTree" Margin="0 0 2 0" Grid.Column="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding TreeNodeSelected}"
CommandParameter="{Binding ElementName=ProjectTree, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Margin="3" Source="{Binding ItemType, Converter={x:Static misc:TreeItemImageConverter.Instance }}" Width="20" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In the Viewmodel of the treeview:
C#
public RelayCommand TreeNodeSelected { get; private set; }
readonly ReadOnlyCollection<PlcAddressViewModel> _rootNodes;
readonly PlcAddressViewModel _rootAddress;
#region Constructor
public ProjectTreeviewModel(PlcAddress rootAddress)
{
_rootAddress = new PlcAddressViewModel(rootAddress);
_rootNodes = new ReadOnlyCollection<PlcAddressViewModel>(
new PlcAddressViewModel[]
{
_rootAddress
});
TreeNodeSelected = new RelayCommand(ExecuteTreeNodeSelected, canExecuteMethod);
}
#endregion Constructor
#region Commands
private bool canExecuteMethod(object parameter)
{
return true;
}
private void ExecuteTreeNodeSelected(object parameter)
{
PlcAddressViewModel selectedNode = (PlcAddressViewModel)parameter;
Console.WriteLine("Found this node: " + selectedNode.Name);
return;
}
#endregion Commands
Thanks to #mm8 and #BionicCode
You could handle an event like for example SelectedItemChanged using an interaction trigger:
<TreeView DataContext="{Binding ProjectTree}"
ItemsSource="{Binding ProjectNode}"
x:Name="ProjectTree"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged" >
<i:InvokeCommandAction Command="{Binding MouseEnterCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
...
</TreeView.ItemContainerStyle>
...
</TreeView>
Handling events in an MVVM WPF application
How to add System.Windows.Interactivity to project?
But why don't you just bind the SelectedItem property to a source property and handle your logic in the setter of this one? This would be MVVM way of doing this.
Edit: Since the SelectedItem property a TreeView is read-only, you'll have to use a behaviour to be able to bind to it. There is an example of how to do this here.

How to specify a control within a datagridcell using style/data triggers

In WPF, I'm trying to create a custom data grid that displays a cell as combobox when there is a databound list of data (a range for data), and a normal textbox if there isn't a limit. Additionally, i'm using the included validation for IDataErrorInfo and an MVVM pattern. Most of it works very well, but the devil is in the details. I'm seeing a problem where the validation on my combobox is not working as I expect it to.
I believe the issue I have is when I define both controls using a data trigger, but I'm not sure why it behaves the way it does. If I remove the validation on my textbox, the validation on the combobox works beautifully. When i put the properties back into the textbox to specify validation, the combobox validation no longer works.
Here is my xaml code on how to specify which control to use based on my data trigger:
<UserControl x:Class="GlobalVariableEditor.GlobalVariableEditorUserControl"
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:viewmodel="clr-namespace:GlobalVariableEditor.ViewModel"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:GlobalVariableEditor"
xmlns:clr="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="500" Width="auto" Height="auto">
<UserControl.DataContext>
<viewmodel:GlobalVariableEditorViewModel x:Name="_GVEViewModel"/>
</UserControl.DataContext>
<UserControl.Resources>
<CollectionViewSource x:Key="VariablesViewSource" Source="{Binding VariablesData}">
</CollectionViewSource>
<Style x:Key="RangeStyleNormal" TargetType="ComboBox">
<Setter Property="HorizontalContentAlignment" Value="center"/>
<!--<EventSetter Event="SelectionChanged" Handler="ComboBox_SelectionChanged"></EventSetter>-->
<Setter Property="IsEditable" Value="False" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="IsTextSearchCaseSensitive" Value="True"/>
</Style>
</UserControl.Resources>
<GroupBox x:Name="grpGVEditor" Header="Global variable editor" FontSize="12">
<Grid x:Name="MainGrid">
<Grid.RowDefinitions>
<!--This is for any declarations or headers we may need for initial information (where to load xml file from, etc)-->
<RowDefinition x:Name="TopRowDefinition" Height="*" MinHeight="30" MaxHeight="50" />
<RowDefinition x:Name="RevisionRowDefinition" Height="*" MinHeight="30" MaxHeight="50" />
<!--this is where the data from the xml file should be populated to-->
<RowDefinition x:Name="DataRowDefinition" Height="*" MinHeight="200"/>
<!--this is where the buttons should be placed (cancel, apply, etc)-->
<!--<RowDefinition x:Name="FilterRowDefinition" Height="*" MinHeight ="25" MaxHeight="30"/>-->
<RowDefinition x:Name="ButtonRowDefinition" Height="*" MinHeight ="30" MaxHeight="50" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label Height="30" Width="110" Content="XML file location: " BorderThickness="1" BorderBrush="Black" Margin="3,0,3,0"/>
<Label x:Name="lblCustomXMLFilePath" VerticalContentAlignment="Center" Height="30" Width="350" Content="{Binding FilePath}" BorderThickness="1" BorderBrush="Black" Margin="0,0,3,0" ToolTip="{Binding FilePath}" Background="Gray"/>
<!--<Button x:Name="btnLoad" Height="30" Width="50" Content="Load" Margin="0,5,5,5" Click="btnLoad_Click" Visibility="Hidden"/>-->
<Button Height="30" Width="50" Content="Revert" Margin="0,0,3,0" ToolTip="Revert to TP values" Visibility="Hidden"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Label Name="lblRevID" Height="30" Width="110" Content="File Revision ID: " BorderThickness="1" BorderBrush="Black" Margin="3,0,3,0"/>
<TextBox Name="txtRevID" Padding="3" Height="30" Width="100" VerticalContentAlignment="Center" TextAlignment="Left" BorderThickness="1" BorderBrush="Black" Margin="0,0,3,0" Text="{Binding FileVersion}" IsReadOnly="True" Background="Gray">
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding FileVersion}" Value="-1">
<Setter Property="TextBox.Background" Value="Salmon" />
<Setter Property="TextBox.ToolTip" Value="Invalid revision number. Settings file is in read-only mode." />
</DataTrigger>
<DataTrigger Binding="{Binding FileVersion}" Value="0">
<Setter Property="TextBox.Background" Value="Salmon" />
<Setter Property="TextBox.ToolTip" Value="Invalid revision number. Settings file is in read-only mode." />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="tbDummy" Visibility="Hidden"></TextBox>
</StackPanel>
<DockPanel Grid.Row="2" HorizontalAlignment="Stretch" MinHeight="200" Margin="0,5,0,0">
<DataGrid x:Name="PropertyGrid" SelectionMode="Single" SelectionUnit="Cell" RowHeaderWidth="0" IsTextSearchCaseSensitive="True" GridLinesVisibility="All"
AutoGenerateColumns="False" AlternationCount="2" ItemsSource="{Binding FilteredGridEntries}" Margin="3,0,0,0">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent"/>
</Style.Resources>
<Setter Property="IsEnabled" Value="{Binding HasWriteAccess}" ></Setter>
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Background" Value="White" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Background" Value="WhiteSmoke" />
</Trigger>
<MultiDataTrigger >
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsReadOnly}" Value="True"/>
<Condition Binding="{Binding IsChecked, ElementName=_cboShowAll}" Value="False"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<DataTrigger Binding="{Binding IsReadOnly}" Value="True">
<Setter Property="Background" Value="Gray"/>
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Resources>
<Style x:Name="CenterContent" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ToolTip" Value="{Binding Column.(ToolTipService.ToolTip), RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
<Image x:Name="rowHeaderImage"
x:Key="rowHeaderTemplate"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Width="64"
Margin="1,0">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly}" Value="true">
<Setter Property="Source" Value="/Resources/access.ico"/>
</DataTrigger >
<DataTrigger Binding="{Binding IsReadOnly}" Value="false">
<Setter Property="Source" Value="/Resources/no_access.jpg"/>
</DataTrigger >
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataGrid.Resources>
<DataGrid.ItemBindingGroup>
<BindingGroup/>
</DataGrid.ItemBindingGroup>
<DataGrid.Columns>
<!--This is the Name column-->
<DataGridTemplateColumn Header="Name" ToolTipService.ToolTip="Global variable name/ID" MinWidth ="200" Width="2*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="Gray"></Setter>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsReadOnly="True" Background="Transparent" ToolTip="{Binding Name}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--This is the Site column-->
<!--<DataGridTemplateColumn Header="Site" ToolTipService.ToolTip="Site" Width="1*" MaxWidth ="30" IsReadOnly="False" Visibility="Hidden">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox TextAlignment="Center" Text="{Binding Site, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" IsReadOnly="{Binding IsReadOnly}" Background="Transparent"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This is the Slot column-->
<!--<DataGridTemplateColumn Header="Slot" ToolTipService.ToolTip="Slot" Width="1*" MaxWidth ="30" IsReadOnly="False" Visibility="Hidden">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox TextAlignment="Center" Text="{Binding Slot, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" IsReadOnly="{Binding IsReadOnly}" Background="Transparent"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This is the High Limit column-->
<!--<DataGridTemplateColumn Header="High Limit" ToolTipService.ToolTip="High limit" MaxWidth="125" Width="1*" Visibility="Hidden">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox TextAlignment="Center" Text="{Binding HiLimit, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" IsReadOnly="{Binding IsReadOnly}" Background="Transparent"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This is the Low Limit column-->
<DataGridTemplateColumn Header="Value" MinWidth="200" ToolTipService.ToolTip="Global variable programmed value" Width="3*">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--By default, allow the user to free edit the combobox.
This allows backwards compatibility with existing TP settings-->
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding HasRange}" Value="true">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Range, Mode=OneWay}"
MinWidth="50"
Text="{Binding ProgrammedValue,
UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=True,
NotifyOnValidationError=True}"
Style="{StaticResource RangeStyleNormal}"
Width="auto" >
</ComboBox>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding HasRange}" Value="false">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox
HorizontalAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
MinWidth="50"
Text="{Binding ProgrammedValue, Mode=TwoWay,
UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=True,
NotifyOnValidationError=True
}"
TextAlignment="Center"
Width="Auto">
</TextBox>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--This is the Low Limit column-->
<!--<DataGridTemplateColumn Header="Low Limit" ToolTipService.ToolTip="Lower limit" Width="1*" MaxWidth="125" Visibility="Hidden">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox TextAlignment="Center" Text="{Binding LowLimit, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" IsReadOnly="{Binding IsReadOnly}" Background="Transparent"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This is the Fleet column-->
<DataGridTemplateColumn Header="Fleet Variable" ToolTipService.ToolTip="Global variable scope" MinWidth ="75" Width="1*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="Gray"></Setter>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding IsFleet}" IsReadOnly="True" Background="Transparent" ToolTip="{Binding IsFleet}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--<DataGridCheckBoxColumn Header="Is fleet" Width="1*" MinWidth="50" ToolTipService.ToolTip="Variable of fleet scope" Binding="{Binding IsFleet}" IsReadOnly="True">
</DataGridCheckBoxColumn>-->
</DataGrid.Columns>
</DataGrid>
</DockPanel>
<!--These are the buttons at the bottom of the control-->
<Grid Grid.Row="3">
<StackPanel Height="40" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,2" Width="auto">
<Label Height="25" HorizontalAlignment="Left" Width="40" Margin="5,2,3,0">Filter:</Label>
<TextBox x:Name="_tboFilter" HorizontalAlignment="Left" Height="25" Margin="0,3,0,0" Width="200" MaxLength="40" MaxLines="1" MinLines="1" AcceptsTab="True" TextAlignment="Left" Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" />
<CheckBox Name="_cboShowAll" HorizontalAlignment="Left" Height="25" IsChecked="False" Margin="15,7,0,0">
<TextBlock Text="Show all" />
</CheckBox>
</StackPanel>
<StackPanel Height="40" Width="auto" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,2">
<Button x:Name="btnTestError" HorizontalAlignment="Right" Height="25" Width="60" Content="TestError" ToolTip="Fire error events for logging" Margin="0,0,3,0" Command="{Binding TestErrorButtonCommand}" Visibility="{Binding EnableQADebuggingTools}" />
<!--<Button x:Name="btnApply" Height="25" Width="50" Content="Apply" ToolTip="Apply changes" Margin="0,3,3,0" Click="btnApply_Click" IsEnabled="{Binding HasDataChanged}" />-->
<Button x:Name="btnApply" HorizontalAlignment="Right" Height="25" Width="50" Content="Apply" ToolTip="Apply changes" Margin="0,0,3,0" Command="{Binding ApplyChangesButtonCommand}" >
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<!-- Button enabled if the following conditions is met: HasDataChanged is true -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasDataChanged}" Value="True"/>
<Condition Binding="{Binding HasWriteAccess}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<!--<Button x:Name="btnCancel" Height="25" Width="50" Content="Cancel" Margin="0,3,3,0" ToolTip="Revert to TP values" Click="btnCancel_Click" Command="{Binding CancelButtonCommand}"/>-->
<Button x:Name="btnCancel" HorizontalAlignment="Right" Height="25" Width="50" Content="Cancel" Margin="0,0,3,0" ToolTip="Revert to TP values" Click="btnCancel_Click"/>
</StackPanel>
</Grid>
</Grid>
</GroupBox>
</UserControl>
IDataErrorInfo is implemented thusly:
public string this[string columnName]
{
get
{
string result = null;
try
{
if (columnName == "ProgrammedValue")
{
//Validation routine: data can not be empty field.
if (this.HasRange)
{
// This is a combobox
if (ProgrammedValue == null)
{
result = "Field must have selection";
}
else
{
if (Range != null && !Range.Contains(ProgrammedValue) )
{
result = "Field must have selection";
}
}
}
else
{
// This is a text box:
if (string.IsNullOrEmpty(ProgrammedValue))
{
result = "Field can not be empty.";
}
}
}
}
catch (Exception ex)
{
// Eat the exception.. really should never get here, but if there is an exception
result = null;
}
return result;
}
}
In my model, the bound properties are defined:
public string ProgrammedValue
{
get
{
return _programmedValue;
}
set
{
_programmedValue = value;
RaisePropertyChanged("ProgrammedLimit");
}
}
public List<string> Range
{
get
{
return _range;
}
set
{
_range = value;
RaisePropertyChanged("Range");
}
}
public bool HasRange
{
get
{
return _hasRange;
}
set
{
_hasRange = value;
RaisePropertyChanged("HasRange");
}
}
RangeStyleNormal definition:
<UserControl.Resources>
<Style x:Key="RangeStyleNormal" TargetType="ComboBox">
<Setter Property="HorizontalContentAlignment" Value="center"/>
<EventSetter Event="SelectionChanged" Handler="ComboBox_SelectionChanged"></EventSetter>
<Setter Property="IsEditable" Value="False" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="IsTextSearchCaseSensitive" Value="True"/>
</Style>
</UserControl.Resources>
As previously stated, when I remove ValidatesOnDataErrors and NotifyOnValidationError from the textbox properties, the combobox works like a charm. I know that the validation routine I have specified works correctly. I'm hoping someone here can see some obvious coding error(s) I've made and explain what I did wrong and how to correct it. Thanks in advance.

Datagrid different control on same column and getting their values

I have managed to create a datagrid that contains different type of control on same column. However, I am not able to get the user input in these controls.
Item
public class Item
{
/// <summary>
/// item name
/// </summary>
public string ParameterName { get; set; }
/// <summary>
/// Control type
/// </summary>
public ControlType ControlType { get; set; }
/// <summary>
/// New item value
/// </summary>
public object ParameterValue { get; set; }
/// <summary>
/// Current item value
/// </summary>
public string CurrentParameterValue { get; set; }
}
ControlType
public enum ControlType
{
TextBox,
ComboBox,
CheckBox,
TextBlock
}
DataGrid
<DataGrid x:Name="dgridGeneral" ItemsSource="{Binding}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserResizeColumns="False" SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name" Width="200">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ParameterName}" Style="{StaticResource TextHeader}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="New Value" Width="200">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ControlType}" Value="CheckBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsChecked="{Binding ParameterValue, Mode=TwoWay}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ControlType}" Value="ComboBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox ItemsSource="{Binding ComboBoxControlItemSource}" SelectedIndex="{Binding ParameterValue, Mode=TwoWay}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ControlType}" Value="TextBlock">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding ParameterValue}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ControlType}" Value="TextBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding ParameterValue, Mode=TwoWay}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Current Value" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CurrentParameterValue}" Style="{StaticResource TextBodyNormal}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have also tried to add additional properties for different control types in Item class. For instance, a "Text" property for TextBox, a "IsCheck" property for CheckBox, a SelectedIndex property for ComboBox. However, I still was not able to obtain the user input.
How do I obtain the inputs from the user?
The ParameterValue property of the Item will be set if you set the UpdateSourceTrigger property of the bindings to PropertyChanged:
<Style.Triggers>
<DataTrigger Binding="{Binding ControlType}" Value="CheckBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox Content="{Binding}" IsChecked="{Binding ParameterValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ControlType}" Value="ComboBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox ItemsSource="{Binding ComboBoxControlItemSource}"
SelectedIndex="{Binding ParameterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ControlType}" Value="TextBlock">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding ParameterValue}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ControlType}" Value="TextBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding ParameterValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>

Changing DataTemplate based on Attribute in an XElement

Ok I have an XElement that looks like:
<authentication mode="Forms">
<forms loginUrl="login.aspx" name=".LOGIN" protection="All" timeout="4800" path="/" />
</authentication>
Then in my XAML I setup a ContentControl that looks like:
<ContentControl Content="{Binding Data}">
<ContentControl.ContentTemplate>
<DataTemplate>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Attribute[mode].Value}" Value="Forms">
<Setter Property="ContentTemplate" Value="{StaticResource FormsTemplate}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Where Data is my public property that contains the XElement. My template looks like:
<DataTemplate x:Key="FormsTemplate">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Login URL"/>
<TextBox Text="{Binding Path=Element[forms].Attribute[loginUrl].Value}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name"/>
<TextBox Text="{Binding Path=Attribute[name].Value}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Protection"/>
<TextBox Text="{Binding Path=Attribute[protection].Value}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Timeout"/>
<TextBox Text="{Binding Path=Attribute[timeout].Value}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Path"/>
<TextBox Text="{Binding Path=Attribute[path].Value}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="passport">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Redirect URL"/>
<TextBox Text="{Binding Path=Attribute[redirectUrl].Value}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
Why doesn't this work? Nothing shows up on the screen when I do this.
I solved it by using a Style Trigger instead. Below is what I had to use for this to work.
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Data.Attribute[mode].Value}" Value="Forms">
<Setter Property="ContentTemplate" Value="{StaticResource FormsTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Data.Attribute[mode].Value}" Value="Passport">
<Setter Property="ContentTemplate" Value="{StaticResource PassportTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>

Resources