change properties inside datatemplate of listboxItem if selected - wpf

I have the following style:
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock x:Name="Text" Text="{Binding Name}" Margin="0, 5" FontSize="16"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
I want to change the foreground color of "Text" if the "ListBoxItem" is selected. I know from here: Change WPF DataTemplate for ListBox item if selected how to change the DataTemplate. but since I just want to change colors, this solution makes unnecessary duplication in codd - It would be a greater problem if my DataTemplate would be very complex and long.
How do I achieve change of a single property of an object inside the DataTemplate?

If you actually want the same item template always (whether the item is selected or not), but you want a different foreground color when the item is selected, that's easy. Don't set the ContentTemplate in a trigger, because ContentTemplate doesn't change. Just set the foreground color with a trigger. If you don't mess with the foreground color in the template, it will inherit whatever the ListBoxItem has for that property.
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock
x:Name="Text"
Text="{Binding Name}"
Margin="0, 5"
FontSize="16"
/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
A more elaborate version of the same style:
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse
Height="12"
Width="12"
VerticalAlignment="Center"
x:Name="Ellipse"
Fill="Yellow"
Stroke="DeepSkyBlue"
StrokeThickness="1"
/>
<TextBlock
x:Name="Text"
Text="{Binding Name}"
Margin="5,5,0,5"
FontSize="16"
/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="True"
>
<Setter
TargetName="Ellipse"
Property="Stroke"
Value="Orange"
/>
<Setter
TargetName="Ellipse"
Property="StrokeThickness"
Value="3"
/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>

Related

WPF DataTrigger not working On ComboBox?

I've got the following pure XAML:
<DockPanel>
<ComboBox Name="combo" Height="24" Width="60">
<Border Background="Gray" Padding="20,10">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=combo, Path=IsDropDownOpen}" Value="True">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</ComboBox>
<TextBlock Text="{Binding ElementName=combo, Path=IsDropDownOpen}"></TextBlock>
</DockPanel>
I would expect the datatrigger to change the background colour of the border object to red as soon as the combobox is opened, but instead nothing happens.
Since you have set the background property directly on the ComboBox the trigger is not going to override that value.
This behavior is explained on MSDN.
You have to set it in the style instead like this:
<Border Padding="20,10">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Gray" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=combo, Path=IsDropDownOpen}" Value="True">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>

Editable WPF ListBox contains textbox

I want to have dynamic editable listbox in wpf application. I am using textbox inside listbox, and I am binding Observable collection to that listbox. Now, I want to make textbox editable on mouse click. So, user can make change to textbox and save new textbox text.
<ListBox Name="ListTwo" ItemsSource="{Binding CollectionUrl, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="TextBoxList" Text="{Binding Path=urlString}" IsEnabled="False" >
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You should use IsReadOnly property. In the trigger you should check IsFocused property.
In the following example, I changed foreground color to indicate which TextBox is in the edit mode.
Example:
<ListBox Name="ListTwo" ItemsSource="{Binding CollectionUrl, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="TextBoxList" Text="{Binding Path=urlString}" MinWidth="100">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Foreground" Value="Green"/>
<Setter Property="IsReadOnly" Value="False" />
</Trigger>
<Trigger Property="IsFocused" Value="False">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="IsReadOnly" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If you want to allow users save changes after edit value in the TextBox, you can add button and show in the actual editing row:
<ListBox Name="ListTwo" ItemsSource="{Binding CollectionUrl, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBox Name="TextBoxList" Text="{Binding Path=urlString}" MinWidth="100">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Foreground" Value="Green"/>
<Setter Property="IsReadOnly" Value="False" />
</Trigger>
<Trigger Property="IsFocused" Value="False">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="IsReadOnly" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Button Content="Save" Grid.Column="1" Command="{Binding SaveChanges}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TextBoxList, Path=IsFocused}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My approach to accessing the text box (which was proving a nightmare) was to use your save button approach and in the Button's Button_Click function I used sender to retrieve the Button's parent, cast to (in your case) Grid. Then you can use that to access the Grid's Children with .Children[0] being your TextBox.
Bit of a Kluge because your code has to 'know' the type of the parent and the index of the child TextBox but these will not change. If necessary the purists can iterate through the Children to identify the required child explicitly.
Hope this helps someone.

How to get rid of the red border when a DataGrid cell is invalid?

How do I do this? I want to get rid of that annoying red border that shows on each invalid datagrid cell.
You can just add this line to your DataGrid:
<DataGrid Validation.ErrorTemplate="{x:Null}" />
I had this same issue but in my case I wasn't using IDataError or INotifyDataErrorInfo. I'm using a custom ValidationRule and styles to handle my validation logic and presentation. I was already using the DataGrid RowStyle to display a custom style for the row that has errors so I thought it would be easy to do something similar with the DataGridCell.
Things to note:
You can't just define a style and set the DataGrid.CellStyle. Instead you have to use the ElementStyle and/or the EditingElementStyle.
The style TargetType has to match the DataGridColumn type that the cell is using. So for a DataGridComboBoxColumn the TargetType should be ComboBox
DataGrid Column XAML:
<DataGridComboBoxColumn Header="Column1"
ItemsSource="{Binding Path=Column1Path}"
DisplayMemberPath="Column1DisplayPath"
ElementStyle="{StaticResource DGComboColValidationStyle}"
EditingElementStyle="{StaticResource DGComboColValidationStyle}">
<DataGridComboBoxColumn.SelectedItemBinding>
<Binding Path="Column1Path" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<Validation:CustomValidationRule ValidationStep="UpdatedValue" ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</DataGridComboBoxColumn.SelectedItemBinding>
</DataGridComboBoxColumn>
Style Definitions
<Style x:Key="DGComboColValidationStyle" TargetType="{x:Type ComboBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border BorderThickness="1"
BorderBrush="{Binding ElementName=adorner1, Path=DataContext[0].ErrorContent.ValidationType, Converter={StaticResource ValidationTypeColorConverter}}"
CornerRadius="3">
</Border>
<AdornedElementPlaceholder Name="adorner1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DGTextColValidationStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border BorderThickness="1"
BorderBrush="{Binding ElementName=adorner2, Path=DataContext[0].ErrorContent.ValidationType, Converter={StaticResource ValidationTypeColorConverter}}"
CornerRadius="3">
</Border>
<AdornedElementPlaceholder Name="adorner2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DGRowValidationStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="DGRowValidationTemplate">
<Grid>
<Ellipse Width="12" Height="12" Stroke="Black" StrokeThickness="0.5"
Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent.ValidationType, Converter={StaticResource ValidationTypeColorConverter}}"/>
<TextBlock FontWeight="Bold" Padding="4,0,0,0"
Margin="0" VerticalAlignment="Top" Foreground="White" Text="!"
ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent.ValidationMessage}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent.ValidationType, Converter={StaticResource ValidationTypeColorConverter}}"/>
</Trigger>
<Trigger Property="Validation.HasError" Value="false">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="DGCellStyle" TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="false">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent.ValidationMessage}"/>
</Trigger>
</Style.Triggers>
</Style>
Reference:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/6d2d6513-7bca-4359-a12b-46da3c380b0a/wpf-4-datagrid-editingelementstyle-and-validationerrortemplate-adorner-layer?forum=wpf
Set the ValidatesOnDataErrors and ValidatesOnExpcetions to False for your binding of your cell.
In case you want your validations, then you have to override the Validation Template for your control. Please refer to my answer here -
Validation Error Style in WPF, similar to Silverlight
I hated the red border because it was put in the adorner and adorners sit on top of the window, meaning that if your element is partially/fully hidden by another element (like it is in a grid) the full adorner still shows :(
That didn't mean I didn't want some customization though, so I can still highlight my items in pink, or change their foreground and have the DataGridCell correctly clip everything off. You can do this by using a style trigger.
Hope this helps!
<DataGrid.Resources>
<Style TargetType="{x:Type TextBlock}" x:Key="TextBlockErrorStyle">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<!-- Just the adorned element means NO RED BORDER -->
<AdornedElementPlaceholder Name="controlWithError" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Background" Value="Pink"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
...
<DataGridTemplateColumn Header="Description" MinWidth="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
Style="{StaticResource ResourceKey=TextBlockErrorStyle}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
You can check this as well
How to: Implement Validation with the DataGrid Control

Change WPF StackPanel background color when an element in the panel has focus

If I have a set of controls within a StackPanel, is there a generic way to change the background of the stackpanel when any control within the StackPanel gains focus? (and obviously switch the background back when no control in the StackPanel has focus). The following code works for me, but it would be nice to have a generic way to accomplish this task versus having to list each control in every StackPanel in my page.
Thanks!
<StackPanel Margin="5">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused, ElementName=chkOccupiedByMortgagor}" Value="true">
<Setter Property="Background" Value="Gray" />
<Setter Property="Opacity" Value=".5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsFocused, ElementName=chkOccupiedByNewOwner}" Value="true">
<Setter Property="Background" Value="Gray" />
<Setter Property="Opacity" Value=".5" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<CheckBox Margin="2" x:Name="chkOccupiedByMortgagor">Mortgagor</CheckBox>
<CheckBox Margin="2" x:Name="chkOccupiedByNewOwner">New Owner</CheckBox>
<CheckBox Margin="2" x:Name="chkOccupiedByTenant">Tenant</CheckBox>
<CheckBox Margin="2" x:Name="chkOccupiedByUnknownOccupant">Unknown Occupant</CheckBox>
</StackPanel>
Yes. You can do that. Just use IsKeyboardFocusWithin property for the trigger, like this:
<StackPanel Margin="5">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsKeyboardFocusWithin}" Value="True">
<Setter Property="Background" Value="Gray" />
<Setter Property="Opacity" Value=".5" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<CheckBox Margin="2">Mortgagor</CheckBox>
<CheckBox Margin="2">New Owner</CheckBox>
<CheckBox Margin="2">Tenant</CheckBox>
<CheckBox Margin="2">Unknown Occupant</CheckBox>
</StackPanel>
Do remember though, that you need to tell the trigger to look for the property in the same element, hence, RelativeSource={RelativeSource Self}. Alternatively, you can name the stack panel and use this xaml too:
<StackPanel Margin="5" x:Name="stackPanel">
...
<DataTrigger Binding="{Binding ElementName=stackPanel, Path=IsKeyboardFocusWithin}" Value="True">
...

How can I "Click Through" a control in WPF?

I have an order entry form that has a ListBox with a list of line items. I have my items template, and one of the values is a ComboBox in each of my Items.
Now, my form can also create Credit memo's in addition to purchase orders, but when I am creating a credit memo, I want to put the words "Credit Memo" over the list box, however, the TextBlock covers the ComboBox in two of my line items. I would like to pass my click event through the TextBlock to the ComboBoxes but I'm not sure how to do it.
This is what I have, ( Maybe I am coming at this totally wrong, I am kinda a noob with WPF )
<ListBox SelectionMode="Single" Grid.Row="2"
ItemsSource="{Binding Path=LineItems}" HorizontalContentAlignment="Stretch"
IsSynchronizedWithCurrentItem="True" Background="#66FFFFFF">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="WhiteSmoke"/>
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsPartBackOrder}" Value="True">
<Setter Property="Background" Value="Orange" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Entities:SalesOrderLineItem}" >
<OrderEntry:SalesOrderLineItemCreate DataContext="{Binding}" DeleteSalesOrderLineItem="DeleteSalesOrderLineItem" Margin="0,3,3,0" >
<OrderEntry:SalesOrderLineItemCreate.Resources>
<Style TargetType="{x:Type OrderEntry:SalesOrderLineItemCreate}">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource=
{
RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}
},
Path=IsSelected
}" Value="True">
<Setter Property="Background" Value="LightBlue" />
<Setter Property="Foreground" Value="Black" />
</DataTrigger>
</Style.Triggers>
</Style>
</OrderEntry:SalesOrderLineItemCreate.Resources>
</OrderEntry:SalesOrderLineItemCreate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="2"
Text="Credit Memo"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="48" Height="Auto"
FontStyle="Italic"
Foreground="Red"
Opacity=".25">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=OrderType}" Value="CR">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=OrderType}" Value="CU">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock IsHitTestVisible="False" .../>

Resources