WPF - Simple timeline in DataGrid - wpf

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

Related

WPF DataGrid column headers squeezed before ItemSource is set

I currently have a datagrid in WPF with only two columns, and its item source is set within the .cs codes when attempting to bind it to its specific item source, but initially it is not referenced to any itemsource. The problem is, if there is no item source yet, the columns are squeezed up even though i have specified the width of the columns. (in attached picture)
The columns are looking fine until i added the <datagrid.groupstyle> portion, may i ask if i am missing something or am i doing something wrong.
Below is the codes inside my .xaml for this datagrid.
<DataGrid Visibility="Visible"
x:Name="IFCInfoDataGrid" Width="auto" Height="auto" HeadersVisibility="Column"
AutoGenerateColumns="False" Margin="12" IsReadOnly="True" Grid.ColumnSpan="2"
SelectionUnit="FullRow" IsSynchronizedWithCurrentItem="False" >
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<DockPanel Background="LightBlue">
<TextBlock Text="{Binding Path=Name}" Foreground="Blue" Margin="30,0,0,0" Width="100" />
</DockPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}" >
<Setter Property="Margin" Value="0,0,0,5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander
BorderBrush="#FF002255"
IsExpanded="True"
Background="Tan"
BorderThickness="0,0,0,1">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" FontWeight="Bold" Text="{Binding Path=Name}" Width="200" />
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding IFCParameter}" Header="IFC Parameter" Width="0.5*"
ElementStyle="{StaticResource DataGridRowStyle}" IsReadOnly="True" CanUserSort="False"/>
<DataGridTextColumn Binding="{Binding ParameterValue}" Header="Value" Width="0.5*"
ElementStyle="{StaticResource DataGridRowStyle}" IsReadOnly="True" CanUserSort="False"/>
</DataGrid.Columns>
</DataGrid>
Set the <GroupStyle.Panel> element to an ItemsPanelTemplate with a DataGridRowsPresenter:
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.HeaderTemplate>
...
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
...
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>

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.

How to change HierarchicalDataTemplate?

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>

WPF custom listbox selectdItem with button

I managed to build a custom listbox where each item is loaded from a database showing in a stackpanel the name and the lastname. After these 2 textboxes there should be a button, which is correctly binded to an ICommand of the ViewModel.
The button correctly calls the right method, but it doesn't delete the selectedPerson because that object is null.
This is the WPF of the listbox
<Style x:Key="CustomHorizontalListbox" TargetType="{x:Type ListBox}">
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel Orientation="Horizontal" Width="200" Margin="0,0,0,0">
<TextBox Text="{Binding FirstName}" Width="60" BorderThickness="0" Margin="0" IsReadOnly="True"></TextBox>
<TextBox Text="{Binding LastName}" Width="100" BorderThickness="0" Margin="0" IsReadOnly="True"></TextBox>
<Button Width="20" Height="20" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.OnDeletePatient}" CommandParameter="{Binding}"></Button>
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
This is the relative method on the ViewModel
private void DeletePatient()
{
patientsManager.DeletePatient(SelectedPatient);
ListOfPatients = new ObservableCollection<RealPatient>(patientsManager.GetAllRealPatients());
SelectedPatient = null;
}
And this is how the custom listbox is included in the View
<ListBox Grid.Row="2" Grid.Column="3" Style="{StaticResource CustomHorizontalListbox}" ItemsSource="{Binding ListOfPatients}" SelectedItem="{Binding SelectedPatient}">
</ListBox>
So the problem is that the breakpoint at the DeleteMethod shows the SelectedPatient = null..
What do i miss?... Even when i click on the list item and not on the single button the SelectedPatient doesn't change
thanks
Here my full sample application that works:
<Window.Resources>
<Style x:Key="CustomHorizontalListbox" TargetType="{x:Type ListBox}">
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel Orientation="Horizontal" Width="200" Margin="0,0,0,0">
<TextBox Text="{Binding FirstName}" Width="60" BorderThickness="0" Margin="0" IsReadOnly="True"></TextBox>
<TextBox Text="{Binding LastName}" Width="100" BorderThickness="0" Margin="0" IsReadOnly="True"></TextBox>
<Button Width="20" Height="20" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.OnDeletePatient}" CommandParameter="{Binding}"></Button>
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Resources>
<Storyboard x:Key="OnGotKeyboardFocus1">
<BooleanAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="{x:Null}" Storyboard.TargetProperty="(Selector.IsSelected)">
<DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<EventTrigger RoutedEvent="Keyboard.GotKeyboardFocus">
<BeginStoryboard Storyboard="{StaticResource OnGotKeyboardFocus1}"/>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Window.DataContext>
<Regions:ViewModel/>
</Window.DataContext>
<Grid>
<ListBox Style="{StaticResource CustomHorizontalListbox}" ItemsSource="{Binding ListOfPatients}" SelectedItem="{Binding SelectedPatient}" >
</ListBox>
</Grid>
Here the definition of my SelectedPatient property in the ViewModel:
private Patient _SelectedPatient;
public Patient SelectedPatient
{
get { return _SelectedPatient; }
set
{
_SelectedPatient = value;
NotifyPropertyChanged(m => m.SelectedPatient);
}
}
That will set your selected Item whenever you click in any place of your ListBox item row.
I wish I had a deeper explanation on why this works, but I found it in the MSDN forum:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/1642c5f9-e731-4a3d-9eed-0f574d90d925 I used that fix for the app I'm working with.

Wpf DataGrid sub-group style

I am grouping data-grid to two level.I mean each main group have one or many sub groups.
<controls:DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Style="{DynamicResource newExpanderStyle}" HorizontalAlignment="Left"
Margin="5,0,0,0" VerticalAlignment="Top" Background="{DynamicResource NormalBrushGrid}" >
<Expander.Header>
<StackPanel Background="#E5E5E5" Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" FontSize="12" Margin="5,0" />
<TextBlock Text="{Binding Path=ItemCount}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</controls:DataGrid.GroupStyle>
I would like to differentiate sub group from main group.How can i apply different color to sub-group header
Thanks in advance
Chand.
The groups do not provide much information, but if you only have one sublevel you can use CollectionViewGroup.IsBottomLevel to differentiate. e.g.
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content.IsBottomLevel"/>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</GroupStyle.HeaderTemplate>
The templated parent is a ContentPresenter and the Content of that is an internal group class.

Resources