What does a WPF DataGrid bind to the headers DataTemplate? - wpf

When creating a WPF DataGrid, I have the option to set a DataTemplate to each column's HeaderTemplate, like this:
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<!-- just some random content, not important, but notice the bindings use RelativeSource-->
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name" VerticalAlignment="Center"/>
<Button Command="{Binding Path=DataContext.SortFoldersByNameCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}" Content="▲"
Foreground="{Binding Path=DataContext.IsFoldersSortByName, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</StackPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
Now, notice that for my bindings to work inside that template, I need to set a RelativeSource, because this template is not bound to the grid's DataContext anymore.
The question is: what is bound to the header's DataTemplate?
If I simply try to put a <TextBlock Text="{Binding}"/>, for instance, the text is empty. I don't know what other kinds of tests I could do to find out.

The short answer is that the datacontext is nothing.
I threw a quick template into some datagrid I have on disk.
I used this thing to answer a few questions years back.
The header isn't in the visual tree and does not inherit datacontext.
My quick and dirty header template:
<DataGridTextColumn Binding="{Binding LastName}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="XXXX"/>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
Examine that textblock using Snoop...
Datacontext is empty:
This is why you do all that relativesource stuff to get to your property in the datacontext.
As an aside.
If you're doing much wpf development I recommend you give Snoop a go.
It's still way better than the in built thing.

Besides using Snoop (great tool!), you can also check that by adding a converter to the binding and inspecting it with a breakpoint. (The inspection is necessary if the binding is null, which is the case)
The Path=. indicates the current DataContext.
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={local:TestConverter}}"/>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
Where the converter is defined as:
class TestConverter : MarkupExtension, IValueConverter
{
//source to target
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.GetType().FullName;
}
//target to source
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}

Related

ListView inside ListView + control.Visibility

I'm creating a questionnaire app. My way of doing this is to create a ListView which contains question text and another ListView which contains list af answers(as RadioButtons).
The problem came when there are question which have an answer "Others" which require a TextBox for user to type some text. How can I achieve this? I mean i want to make TextBox visible only when collection of answers contains RadioButton with content "Other".
Below is my xaml code for ListView.
<ListView SelectionChanged="myList_SelectionChanged" ItemsSource="{Binding OCquestions}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="20 0 20 0">
<TextBlock Text="{Binding Path=questionText}"/>
<ListView Name="ListaLista" SelectionChanged="myList_SelectionChanged" ItemsSource="{Binding Path=listOfAnswer}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton GroupName="{Binding Path=questId}" Content="{Binding Path=answerText}" Checked="RadioButton_Checked"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// HERE I WANT A TEXTBOX WHICH IS VISIBLE ONLY WHEN listOfAnswer collection contain a RadioButton with Content "Others"
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have no idea how to achieve this. I'm not familiar with Converters. Can anyone give me some tip ?
You need some Trigger to show/hide the TextBox, something like this:
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton GroupName="{Binding Path=questId}"
Content="{Binding Path=answerText}"
Checked="RadioButton_Checked" Name="radio"/>
<TextBox Name="other" Visibility="Collapsed"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding answerText}" Value="Other">
<Setter TargetName="radio" Property="Content" Value=""/>
<Setter TargetName="other" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You can see that the DataTrigger listens to answerText, if it's "Other" just set the Content of Radio to empty string and set the TextBox's Visibility to Visible to show it. This TextBox will be shown on the right of the RadioButton.
First add a ValueConverter:
public abstract class BaseConverter : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class AnswerCollectionToVisibilityConverter : BaseConverter, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ICollection<ListOfAnswers> answers = value as ICollection<ListOfAnswers>;
if (answers != null)
{
foreach (Answer answer in answers)
{
if (OtherRadioButtonIsHere)
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then add a TextBox that uses the ValueConverter to set the Visibility:
<TextBox Visibility="{Binding Path=listOfAnswer, Converter={AnswerCollectionToVisibilityConverter}}" />

How to sort text from XML file as number in WPF DataGrid?

The content of my DataGrid (Product and Price, for example) is loaded from an XML file and every thing is treated as text when sorted by the DataGrid.
How to tell the DataGrid to treat Price as number when sorting? Thanks.
Here's the relevant XAML (there's NO code behind). I want to sort Length as number:
<DataGridTemplateColumn Header="Length" SortMemberPath="Length" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextAlignment="Right" VerticalAlignment="Center" Text="{Binding XPath=Length}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
You could add a Converter to your Binding.
Step 1 : Create the converter class.
[ValueConversion(typeof(string), typeof(int))]
public class StringToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Int32.Parse(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString();
}
}
A little validation might be advisable, but this is only a rudimentary example.
Step 2: instantiate the converter. Make sure you add the Namespace declaration to your Xaml.
<Window.Resources>
<StringToIntConverter x:Name="stringToInt"/>
</Window.Resources>
Step3 : use it
<DataGridTemplateColumn Header="Length" SortMemberPath="Length" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextAlignment="Right" VerticalAlignment="Center" Text="{Binding XPath=Length, Converter={StaticResource stringToInt}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Without code, this is just a guess, but I'm assuming you are doing a xml deserialization into an object, that is put in a list and then your datagrid is bound to the list. If you make the price data field in the list class an int, then datagrid will treat it as a number.

Silverlight: How can I update an IValueConverter bound Textbox upon changing values in an Entity?

So, I have a DataGrid with a TextBlock that displays the aggregrate value of two textboxes in the grid. I do this by binding using a value converter. This works on the load but I need it to update upon changing the values of the other entities which its aggregating over. Here is some of my code:
Here is the my PagedCollectionView in my ViewModel which is bound to the View.
private PagedCollectionView _grievances;
public PagedCollectionView Grievances
{
get
{
if (_grievances == null)
{
_grievances = new PagedCollectionView(Context.lict_grievances);
_grievances.SortDescriptions.Add(new SortDescription("grievance_type_id", ListSortDirection.Ascending));
}
return _grievances;
}
}
Here is my the DataGrid in my View:
<sdk:DataGrid x:Name="grdGrievances" AutoGenerateColumns="False" ItemsSource="{Binding Path=Grievances}" HorizontalContentAlignment="Center">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Header="Total # of Outcomes">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource GrievanceOutcomeSum}}" Margin="15,0,0,0"></TextBlock>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn Header="Resolved">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=outcome_resolved, Mode=TwoWay}"
TextChanged="ResolvedTextBox_TextChanged" HorizontalAlignment="Center"></TextBox>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn Header="Pending">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=outcome_pending, Mode=TwoWay}"></TextBox>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
Here is my Value Converter for the aggregated textblock:
public class GrievancesAggregateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
lict_grievance entity = (lict_grievance)value;
short i = 0;
if (entity != null)
{
if (entity.outcome_resolved != null)
i += (short)entity.outcome_resolved;
if (entity.outcome_pending != null)
i += (short)entity.outcome_pending;
}
return i;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
So, upon changing the values in the other 2 textboxes I need it to refresh the aggregrated value in the textblock. How can I accomplish this? Im at a loss right now and browsing the web I couldnt find any solutions.
Thanks a bunch,
Evan
Evan, Colin Eberhardt has an interesting so called "MultiBinding" solution on his blog and is doing something very similar.
"MultiBinding is a WPF feature that
allows you to bind a single property
to a number of sources, with the
source values being combined by a
value converter. This is a feature
that is missing from Silverlight"
(Quote)
This should solve your problem. Best regards!
Why don't you bind the TextBlock (Total # of Outcomes) to a property that will fire PropertyChanged. Then set that value from the ResolvedTextBox_TextChanged event.
Try something like this:
<StackPanel Orientation="Vertical">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding FirstTextBox}">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=textBlock}"
PropertyName="Text"
Value="{Binding Converter={StaticResource TestConverter}}" />
</ei:PropertyChangedTrigger>
<ei:PropertyChangedTrigger Binding="{Binding SecondTextBox}">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=textBlock}"
PropertyName="Text"
Value="{Binding Converter={StaticResource TestConverter}}" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<TextBlock x:Name="textBlock" />
<TextBox x:Name="firstTextBox"
Text="{Binding FirstTextBox, Mode=TwoWay}" />
<TextBox x:Name="secondTextBox"
Text="{Binding SecondTextBox, Mode=TwoWay}" />
</StackPanel>
try calling OnApplyTemplate
eg. grid.OnApplyTemplate()

WPF pass parent binding object to the converter

I have ItemsControl that is bound to collection of type Student.
Inside the ItemTemplate I have a TextBox that uses IValueConverter to do some custom calculations and logic. I want to pass the actual Student object to the value converter, instead a property of it. How can I do that? Here's a sample of my code.
<ItemsControl ItemsSource="{Binding StudentList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding ????, Converter={StaticResource MyConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In the code I have this
public class MyValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// I want 'value' to be of type Student.
return null;
}
}
You can just leave out the path. That way you get at the actual object bound to.
<TextBlock Text="{Binding Converter={StaticResource MyConverter}}"/>
or if you want to be explicit about it:
<TextBlock Text="{Binding Path=., Converter={StaticResource MyConverter}}"/>

Show the validation error template on a different control in WPF

I have a UserControl that contains other controls and a TextBox. It has a Value property that is bound to the TextBox text and has ValidatesOnDataErrors set to True.
When a validation error occurs in the Value property binding, the error template (standard red border) is shown around the entire UserControl.
Is there a way to show it around the TextBox only?
I'd like to be able to use any error template so simply putting border around textbox and binding its color or something to Validation.HasError is not an option.
Here's my code:
<DataTemplate x:Key="TextFieldDataTemplate">
<c:TextField DisplayName="{Binding Name}" Value="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
</DataTemplate>
<controls:FieldBase x:Name="root">
<DockPanel DataContext="{Binding ElementName=root}">
<TextBlock Text="{Binding DisplayName}"/>
<TextBox x:Name="txtBox"
Text="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"
IsReadOnly="{Binding IsReadOnly}"/>
</DockPanel>
UserControl (FieldBase) is than bound to ModelView which performs validation.
to accomplish this task I've used this solution. It uses converter, that "hides" border by converting (Validation.Errors).CurrentItem to Thickness.
<Grid>
<Grid.Resources>
<data:ValidationBorderConverter
x:Key="ValidationBorderConverter" />
</Grid.Resources>
<Border
BorderBrush="#ff0000"
BorderThickness="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem,
onverter={StaticResource ValidationBorderConverter}}">
<TextBox
ToolTip="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Border>
</Grid>
ValidationBorderConverter class is pretty simple:
[ValueConversion(typeof(object), typeof(ValidationError))]
public sealed class ValidationBorderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return (value == null) ? new Thickness(0) : new Thickness(1);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Resources