I have a ListBox...
<ListBox Margin="10" ItemsSource="{Binding Employees}"
ItemTemplate="{DynamicResource EmployeesTemplate}"
HorizontalContentAlignment="Stretch" BorderThickness="0"
ScrollViewer.CanContentScroll="False"/>
...that has a custom DataTemplate:
<Window.Resources>
<DataTemplate x:Key="EmployeesTemplate">
<Border BorderThickness="1" BorderBrush="Black" SnapsToDevicePixels="True"
DockPanel.Dock="Top" Margin="0,0,0,5" Padding="5">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Text="{Binding Path=FirstName}" Padding="0,0,5,0"/>
<TextBlock Text="{Binding Path=LastName}"/>
</StackPanel>
<TextBlock Grid.Column="1" Text="{Binding Path=Title}"/>
</Grid>
<StackPanel>
<TextBlock Text="{Binding Path=DateOfBirth, StringFormat={}{0:MM/dd/yyyy}}"/>
<TextBlock Text="{Binding Path=Address}"/>
<TextBlock Text="{Binding Path=PhoneNumber}"/>
<TextBlock Text="{Binding Path=Salary, StringFormat={}{0:C}}"/>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
When I select an item from the ListBox, the selection highlighting includes the 5-point Margin that is assigned to the bottom of each item (Border in DataTemplate):
You'll notice the similar situation on the left side, where the highlight overflows just a little bit. I did not notice that until now... hmm. So, I would like to restrict the selection highlight to the border area and nothing outside of it and be able to retain the margin spacing between the items.
How would I accomplish that? I tried manipulating Padding and Margin as much as I could, but I could not figure it out. Maybe I have to create a custom ListBox template?
I'm not sure if there is a simpler way but it works for me. I added a DataTemplate.Triggers section to my DataTemplate...
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}, Path=IsMouseOver}" Value="True">
<Setter Property="Background" Value="LightBlue" TargetName="EmployeesTemplateBorder"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="True">
<Setter Property="Background" Value="RoyalBlue" TargetName="EmployeesTemplateBorder"/>
</DataTrigger>
</DataTemplate.Triggers>
...and a ListBox.Resrouces section to the ListBox (to get rid of the default highlight behavior):
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
</ListBox.Resources>
Obviously, that doesn't do anything to the foreground, but that's not the concern right now, since that one is relatively easy comparing to this highlight routine.
Related
I have the following xaml markup:
<GroupBox Margin="10" Padding="20" Header="Case 5 - Custom Error Object">
<GroupBox.DataContext>
<local:ViewModel4/>
</GroupBox.DataContext>
<StackPanel>
<Label Name="AdornerElement" Style="{StaticResource ResourceKey=AdornerElementStyle}"/>
<TextBox Text="{Binding Path=UserName, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
Margin="0 10"
Validation.ValidationAdornerSite="{Binding ElementName=AdornerElement}">
</TextBox>
</StackPanel>
</GroupBox>
and the following style:
<Style x:Key="AdornerElementStyle" TargetType="Label">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<StackPanel Orientation="Horizontal" Background="LightCoral">
<Image Source="/clipart.png" Width="24" Margin="10"/>
<ItemsControl ItemsSource="{Binding ElementName=AdornerElement, Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)}"
VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ErrorContent.ValidationMessage}"
Style="{StaticResource CustomErrorTypeStyle}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=AdornerElement, Path=Validation.HasError}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Everything works fine except Trigger.
If I set up the property "Visibility" to "Visible" initially, then I can see that error messages are shown correctly.
If I use style shown before, then the Label remains to be collapsed.
Please, help me to use triggers correctly to achieve the final result.
This is not how Validation.ValidationAdornerSite works. This attached property just defines the element that will be adorned with the validation error template. By default this is the element that is validating, the Binding.Target to be more precise.
When you set Validation.ValidationAdornerSite the binding engine will automatically set the attached Validation.ValidationAdornerSiteFor property to reference the element that Validation.ValidationAdornerSite was originally set on.
This means that Validation.HasError and other related attached properties are always set on the Binding.Target and not on the adorner site.
That's why your triggers don't work: they are triggering on the Label instead of the TextBox (where the validation/binding error is registered).
To fix it, the DataTrigger must get the Validation.HasErrors attached property value of the Validation.ValidationAdornerSiteFor attached property, which references the original Binding.Target (the TextBox).
<Style x:Key="AdornerElementStyle"
TargetType="Label">
<Setter Property="Visibility"
Value="Collapsed" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<StackPanel Orientation="Horizontal"
Background="LightCoral">
<Image Source="/clipart.png"
Width="24"
Margin="10" />
<ItemsControl ItemsSource="{Binding ElementName=AdornerElement, Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)}"
VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent.ValidationMessage}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=AdornerElement, Path=(Validation.ValidationAdornerSiteFor).(Validation.HasError)}"
Value="True">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Remarks
It's not clear why you are using a Label to display the error messages. You could simply define an error template, which is a ControlTemplate that is referenced by the attached Validation.ErrorTemplate property. This would simplify your code significantly.
The following example creates the exact same visual error feedback but without the hassle for an additional Label and associated triggers to manage the visibility of the error messages. All this is all handled by the WPF binding engine.
<Window>
<Window.Resources>
<!-- The Validation.Errors property is the DataContext of this template -->
<ControlTemplate x:Key="ErrorTemplate">
<StackPanel>
<Border BorderBrush="Red"
BorderThickness="1"
Background="LightCoral">
<StackPanel Orientation="Horizontal">
<Image Source="/clipart.png"
Width="24"
Margin="10" />
<ItemsControl ItemsSource="{Binding}"
VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent.ValidationMessage}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<Border BorderBrush="Transparent"
BorderThickness="1"
HorizontalAlignment="Left">
<!-- Placeholder for the Binding.Target -->
<AdornedElementPlaceholder x:Name="AdornedElement" />
</Border>
</StackPanel>
</ControlTemplate>
</Window.Resource>
<StackPanel>
<TextBox Text="{Binding Path=UserName, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{StaticResoucre ErrorTemplate}" />
</StackPanel>
</Window>
I have searched around for this, found some solutions related to Winforms, and some even just saying it is really difficult in WPF to accomplish, but those posts are quite old.
If I have a standard ListBox, which is declared as:
<ListBox
x:Name="listBox"
HorizontalAlignment="Left"
Height="240"
Margin="401,68,0,0"
VerticalAlignment="Top"
Width="345"
SelectionChanged="listBox_SelectionChanged"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Grid.ColumnSpan="2"/>`
and programmatically:
System.ComponentModel.BindingList<string> listItems = new System.ComponentModel.BindingList<string>();
listBox.ItemsSource = listItems;
Is there a way for these strings to be wrapped within the ListBox?
Not hard at all:
<ListBox
....
>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding}"
TextWrapping="Wrap"
/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If you got StackPanel please get rid of them. They are bad with wrapping. Use GRID instead like #EdPlunkett suggested.
To use this:
<ItemsControl ItemsSource="{Binding MyErrors, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Template="{StaticResource ErrorListContainerTemplate}"
ItemContainerStyle="{StaticResource ErrorListStyle}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
/>
Here is my style code:
<Style TargetType="{x:Type TextBlock}" x:Key="WrappingStyle">
<Setter Property="TextWrapping" Value="WrapWithOverflow"/>
</Style>
<Style TargetType="ContentPresenter" x:Key="ErrorListStyle">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TextBoxBorderErrorColor}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border Margin="0,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Fill="{DynamicResource TextBoxBorderErrorColor}" VerticalAlignment="Top" HorizontalAlignment="Left">
<Path.Data>
<EllipseGeometry RadiusX="2.5" RadiusY="2.5"/>
</Path.Data>
</Path>
<ContentPresenter Grid.Column="1" Content="{Binding}" VerticalAlignment="Top">
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource WrappingStyle}"/>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
I have a dynamically generated DataGrid bound to a DataTable property in my ViewModel.
I have AutoGenerateColumnHeaders = true, and it's working fine. However, I'm using a DataTemplate to cover the Header with a StackPanel containing a Label and Button. I cannot seem to figure out how to bind the Label Content to the DataGridColumnHeader. I have tried with and without FindAncestor, but I believe the following is the closest to where I need to be...Question is on the Label Content="{}"
<local:UserControlViewBase.Resources>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<StackPanel Width="Auto" Orientation="Horizontal">
<Label Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:UserControlViewBase}},Path=DataContext.TestList.ColumnName}" Padding="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Button Content="Ok" Padding="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:UserControlViewBase.Resources>
//local:UserControlViewBase is just a UserControl with some extra bells and whistles added.
I'm fairly new to WPF and I'm assuming I'm just missing something with the binding - I'm still learning.
Thanks.
This is what I did to get it to work. I had to change the findancestor to look for the DataGridColumnHeader instead of the user control. I then was able to access the Column.Header property:
<local:UserControlViewBase.Resources>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<StackPanel Width="Auto" Orientation="Horizontal">
<Label Width="75" Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridColumnHeader}},Path=Column.Header}" Padding="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Button Content="Ok" Padding="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:UserControlViewBase.Resources>
I am trying to create a Custom tooltip Control. This control is inherited from ToolTip class. My Custom Tooltip will have a header and a content area. Content could be normal text or any other content (Image, richtextbox etc). Following is the Template Style that custom tooltip control.
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customControls:FlyoutHelp}">
<Border BorderThickness="0" Margin="15" Width="300">
<Border.Effect>
<DropShadowEffect Opacity="0.7" />
</Border.Effect>
<StackPanel TextBlock.FontFamily="Trebuchet MS" TextBlock.FontSize='12'>
<TextBlock Background="{StaticResource DellBlue}" Height="23" Foreground="#FFFFFF" Padding="0,4,0,0" TextAlignment="Center" Text="{Binding HeaderText, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Border Background="{StaticResource DellLightGrey}" TextBlock.Foreground="{StaticResource DarkestGrey}" Padding="8">
<ContentControl Content="{Binding HelpContent, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</Border>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
Now as you can see in my template that I am using ContentControl for showing the content of the tooltip. The problem is when my HelpContent is just plain String, it doesn't wrap that text. I can't replace ContentControl with TextBlock because HelpContent could be some other type too (image, richtextbox etc). Can anyone please provide me what is the best way to fix this problem? I will be really very thankful.
Replace ContentControl tag with:
<ContentPresenter Content="{TemplateBinding HelpContent}">
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
[Note: you can leave it as a ContentControl, but ContentPresenter is lighter and follows conventions]
Change StackPanel to Grid since it doesn't know the width for it to wrap.
<Grid TextBlock.FontFamily="Trebuchet MS" TextBlock.FontSize='12'>
<Grid.RowDefinitions>
<RowDefinitions/>
<RowDefinitions/>
<Grid.RowDefinitions/>
<TextBlock Grid.Row="0" Background="{StaticResource DellBlue}" Height="23" Foreground="#FFFFFF" Padding="0,4,0,0" TextAlignment="Center" Text="{Binding HeaderText, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Border Grid.Row="1" Background="{StaticResource DellLightGrey}" TextBlock.Foreground="{StaticResource DarkestGrey}" Padding="8">
<ContentControl Content="{Binding HelpContent, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</Border>
</Grid>
i have problem with styling the combobox's togglebutton.
my combobox xaml code looks like this:
<ComboBox Width="Auto"
HorizontalContentAlignment="Stretch"
FontFamily="HelveticaNeue-Bold"
FontSize="20"
FontWeight="Bold"
Foreground="#FFC0C0C0"
Padding="0,0,0,0"
Style="{DynamicResource navigationComboBox}"
ItemsSource="{Binding Tournaments}"
SelectedValue="{Binding SelectedTournament}">
<ComboBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
</ComboBox.Resources>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Orientation="Vertical"
Width="auto"
Height="{Binding Tournaments, Converter={StaticResource CollectionToHeightConverter}}"/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<DockPanel x:Name="comboDock">
<DockPanel.Background>
<ImageBrush ImageSource="{Binding Converter={StaticResource ImagePathConverter}, ConverterParameter=comboboxitem-line.png}" />
</DockPanel.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="41" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DockPanel x:Name="comboArrow"
Grid.Column="0"
Visibility="Collapsed">
<DockPanel.Background>
<ImageBrush ImageSource="{Binding Converter={StaticResource ImagePathConverter}, ConverterParameter=SportsSubMenuActive.png}" />
</DockPanel.Background>
</DockPanel>
<TextBlock x:Name="comboText"
Grid.Column="1"
FontFamily="HelveticaNeue-Bold"
FontSize="20"
FontWeight="Bold"
Foreground="#FFC0C0C0"
Padding="0,0,10,0"
Text="{Binding Path=Name}"
TextAlignment="Left"
TextWrapping="Wrap" />
</Grid>
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="True">
<Setter TargetName="comboArrow" Property="Visibility" Value="Visible" />
<Setter TargetName="comboText" Property="Foreground" Value="#F94B01" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
so i'm trying to remove the background set to on togglebutton, but it is not possible. if i remove the background on itemtemplate, then i can style the toggle button background. is there any special order in the process to prevent me from changing the background of the togglebutton content if it is set in itemtemplate?
thanks in advance guys,
Kristo
Setting a property directly takes priority over any Setter(s). Instead of setting the property directly, use a Style for your DockPanel and use a Setter to set the background similar to what you do in your DataTrigger. That should allow you to change the background property elsewhere.