WPF GroupBox HeaderTemplate and DataBinding - wpf

I define a headertemplate into a wpf groupbox and the databinding doesn't work. I don't understand why.
<GroupBox>
<GroupBox.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="/PopuAssuNetApplication.UI.Control;component/Images/Members.png" Width="24" />
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{x:Static Member=resx:Resources.PersonsInContractGroupBox}">
<Binding Path="CurrentContract.Federation" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
<Binding Path="CurrentContract.Type" Converter="{StaticResource contractTypeConverter}" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
<Binding Path="CurrentContract.Number" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<WpfComponent:WaitControl Margin="7,0,0,0" VerticalAlignment="Top" Width="24" Height="24" MarginCenter="4">
<WpfComponent:WaitControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMembersOfContractBusy, UpdateSourceTrigger=PropertyChanged, ElementName=PersonsInContract}" Value="true">
<Setter Property="WpfComponent:WaitControl.Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsMembersOfContractBusy, UpdateSourceTrigger=PropertyChanged, ElementName=PersonsInContract}" Value="false">
<Setter Property="WpfComponent:WaitControl.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</WpfComponent:WaitControl.Style>
</WpfComponent:WaitControl>
</StackPanel>
</DataTemplate>
</GroupBox.HeaderTemplate>

The problem is that the HeaderTemplate is used for templating the Header thus within the HeaderTemplate your DataContext is whatever you bind or assign to the Header property of your GroupBox.
Think of the Header property as almost like the DataContext for the header of the control. Normally the DataContext property inherits its value from its parent but since not every control has a Header the Header is blank unless you set it.
By binding your Header explicitly to the current DataContext Header="{Binding}" your example should work as you expect. To help illustrate how this works I've created a simple example below that shows how the Header and DataContext work independently from each other for providing data to either the body or header of the control.
<GroupBox Header="HEADER TEXT" DataContext="BODY TEXT">
<GroupBox.HeaderTemplate>
<DataTemplate>
<Button Content="{Binding}"
Background="LightGreen" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center" Content="{Binding}" />
</GroupBox>
This will yield a GroupBox that looks like the following.
I think that by default in databinding, wpf always gets data from the DataContext property. Seems not in datatemplate
Your assumption is correct about DataContext and it does work in the DataTemplate as I've demonstrated it's just that in the Header's template the DataContext is the value from the Header Property and not the DataContext itself.

The GroupBox does not have a member called "CurrentContract". Most probably, you want to accesss a property called "CurrentContract" from the corresponding ViewModel?! The ViewModel is the GroupBox's DataContext, so you have to change the Binding Paths to something like...
<Binding Path="DataContext.CurrentContract.Type" Converter="{StaticResource contractTypeConverter}" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">

<GroupBox >
<GroupBox.HeaderTemplate>
<DataTemplate>
<RadioButton Content="myR"
IsChecked="{Binding rIsChecked, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}}" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<GroupBox.Content>
<Grid IsEnabled="{Binding rIsChecked}">
</Grid>
</GroupBox.Content>
</GroupBox>
Just propagate the GroupBox DC to the DataTemplate content...works like a charm...

The lesson learned above is useful in general for DataTemplates, but I actually found out recently there is a better way to change the header of a groupbox:
<GroupBox>
<GroupBox.Header>
<CheckBox IsChecked="{Binding Path=mSomeBoolean}"/>
</GroupBox.Header>
</GroupBox>
This way there is no need to define a relative source in the bindings.
Also please note this issue with GroupBoxes and the header.

This is what worked for me:
<HeaderedContentControl Header="{Binding}" Style="{StaticResource TallHeaderedContentStyle}">
<HeaderedContentControl.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=HeaderText"} />
</DataTemplate>
</HeaderedContentControl.HeaderTemplate>

Related

How to use the DisplayMemberPath in a ListView with an ItemContainerStyle?

Within a ListView control, how to use the DisplayMemberPath property when I want to change the ItemContainerStyle?
I used a ListView control in the ControlTemplate of my control and the DisplayMemberPath property is set via Binding from outside of control.
<ListView ItemsSource="{TemplateBinding ItemsSource}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Item:" />
<TextBlock Text="{Binding}" />
<TextBlock Text=", " />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Is it possible to solve this in XAML? How is it solved in the original ControlTemplate?
I try to solve it with a MultiValueConverter, where I bind the Collection Item and DisplayMemberPath.
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource NameToValueConverter}">
<Binding />
<Binding Path="DisplayMemberPath" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListView}}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Is it necessary? Or is it the display value already resolved by the original ListView control?
Is it possible to solve it by XAML?
No, you use either a DisplayMemberPath or an ItemTemplate/ItemContainerStyle but not both.
Also, the DisplayMemberPath property is supposed to be set to a string that specifies a name of a property of the underlying data class. It is not a dependency property that you can bind to.

make binding more understandable

I created style with ItemTemplate for ListView.
<Style x:Key = "Thumbnails"
TargetType = "{x:Type ListView}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Image Width="128"
Height="128"
Margin="8"
Stretch="UniformToFill">
<Image.Source>
<MultiBinding Converter="{StaticResource UriToBitmapConverter}">
<Binding Path="FullPath"/> <!-- how create this binding more understandable -->
<Binding RelativeSource="{RelativeSource Mode=Self}" Path="Width"/>
<Binding RelativeSource="{RelativeSource Mode=Self}" Path="Height"/>
</MultiBinding>
</Image.Source>
</Image>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
On ItemSource property I bind ObservableCollection<Photos>. Photos contains only one property FullPath.
<ListView x:Name="Thumbnails"
Style="{StaticResource Thumbnails}"
ItemsSource="{Binding Photos,Mode=OneWay, UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
My problem is in multibinding in DataTemplate.
<Binding Path="FullPath"/>
I would like created this binding more understandable and tell that I bind property FullPath of object collection which is bind to ListView.ItemSource.
Any ideas? Thank

What's wrong with this binding?

I'm trying to assign DataContext to a MenuItem, which is part of ListBox.
<Style x:Key="ContextMenuStyle" TargetType="telerik:RadMenuItem">
<Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=telerik:RadListBox}, Path=DataContext}" />
</Style>
<DataTemplate x:Key="TemplateSelector">
<ContentPresenter Content="{Binding}" Name="contentPresenter">
<telerik:RadContextMenu.ContextMenu>
<telerik:RadContextMenu>
<telerik:RadMenuItem Header="Connect" Click="RadMenuItem_Click" Style="{StaticResource ResourceKey=ContextMenuStyle}" />
<telerik:RadMenuItem Header="Disconnect" />
<telerik:RadMenuItem Header="Delete Database" />
</telerik:RadContextMenu>
</telerik:RadContextMenu.ContextMenu>
</ContentPresenter>
</DataTemplate>
<Grid>
<telerik:RadListBox x:Name="lsbDevices" ItemsSource="{Binding Path=Devices}" ItemTemplate="{StaticResource TemplateSelector}"
SelectedItem="{Binding SelectedDevice, Mode=TwoWay}" Grid.Row="0" />
</Grid>
Here's what I do. RadListBox's DataContext is set to my ViewModel. I want to assign this ViewModel to every RadMenuItem's DataContext through ContextMenuStyle, but it's not working. RadListBox's DataContext is properly set to my modelview, but RadMenuItem's datacontext is null. What am I missing?
Thanks
ContextMenus are not part of the same VisualTree as the rest of the UI, so your RelativeSource binding is not finding the ListBox
You can find the UI object the ContextMenu is attached to by using the PlacementTarget property of the ContextMenu
<Style x:Key="ContextMenuStyle" TargetType="telerik:RadMenuItem">
<Setter Property="DataContext" Value="{Binding PlacementTarget.DataContext,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:RadContextMenu}}}" />
</Style>

WPF Validation.Errors Contains Nothing

I am having problems displaying the Validation.Errors in my ItemsControl. The Validation.Errors does not contain anything. I am NOT using BindingGroup but I use my own custom TextBoxes. Here is the ItemsControl code:
<ItemsControl x:Name="errorList" ItemsSource="{Binding Path = (Validation.Errors), ElementName=gvAddCustomer}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="18" Text="{Binding Path=ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My TextBox uses the ErrorTemplate to display the errors beside the TextBox control and it displays correctly with the error message. Here is the style:
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt"
Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Can anyone explain why the Validation.Errors contains nothing when I bind to the ItemsControl?
I did my validation very similarly:
<TextBox
Style="{StaticResource TextBoxValidationError}"
Name="PatientFirstName" TabIndex="0">
<TextBox.Text>
<Binding Path="Patient.PatientFirstName" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<bs:NameRequiredRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The only drawback is that it does not fire the validation until you enter the first character. You can force it do it in the constructor like this:
System.Windows.Data.BindingExpression be;
DependencyProperty txtProp = System.Windows.Controls.TextBox.TextProperty;
be = PatientFirstName.GetBindingExpression(txtProp);
be.UpdateSource();

WPF Data Binding Error in ListBox

I have a ListBox:
<ListBox x:Name="HistogramListBox" Grid.Column="1" Margin="8,2,8,0"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Template="{StaticResource HistogramListBoxControlTemplate}"
ItemContainerStyle="{StaticResource HistogramListBoxItem}"
ItemTemplate="{DynamicResource BucketTemplate}" />
That uses a DataTemplate that in turn uses a ValueConverter to determine the height of the ListBoxItem:
<DataTemplate x:Key="BucketTemplate">
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<Rectangle Grid.Row="0" StrokeThickness="1" VerticalAlignment="Bottom"
Stroke="{Binding ElementName=MainElement, Path=BucketStroke}"
Fill="{Binding ElementName=MainElement, Path=BucketFill}" >
<Rectangle.Height>
<MultiBinding Converter="{StaticResource HistogramValueToPercentageConverter}">
<Binding Mode="OneWay" Path="ItemCount" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:Histogram}}" />
</MultiBinding>
</Rectangle.Height>
</Rectangle>
</StackPanel>
</Grid>
</StackPanel>
</DataTemplate>
The ListBox ItemsSource is an int[].
When I execute the code it says it can't find 'ItemCount' on an Int32. I thought it got the item count from the ListBox (I'm obviously wrong).
Can someone tell me how I can get my ValueConverter to know what item I am on.
Thanks
Dan
Assuming your first converter parameter is intended to be the actual value being charted and the second the Histogram object:
<Rectangle.Height>
<MultiBinding Converter="{StaticResource HistogramValueToPercentageConverter}">
<Binding />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:Histogram}}" />
</MultiBinding>
</Rectangle.Height>
This is because the DataContext is the integer itself, at least that appears to be the case from the error message you gave.
By the way, you would normally set the ListBox's ItemsSource using a binding, not from code-behind. This leads to much cleaner separation of UI and code. I noticed no ItemsSource= was shown in your example code so I thought I should mention this.
The data context of the items in the data template is the data item itself, which is an int. If you want to a property on the ListBox, you'll need to reach outside of your current context to do so. You can use a RelativeSource to do this:
{Binding Items.Count, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}
You can try this for your Binding:
<Binding Path="Items.Count">
<Binding.RelativeSource>
<RelativeSource AncestorType="{x:Type ListBox}" />
</Binding.RelativeSource>
</Binding>

Resources