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.
I have created a style for my custom Control (ButtonAnalysisControl). Everything is working except the ToolTip. When I move the mouse over a MenuItem the ToolTip doesn't show up. I inspected the MenuItems with Snoopand the ToolTip value is correctly set. How can I make the ToolTip work? Bonus question: How can I remove the Border in my ContextMenu?
ContextMenu:
Generic.xaml Style
<Style TargetType="anal:ButtonAnalysisControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="anal:ButtonAnalysisControl">
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="ContextMenu" Storyboard.TargetProperty="(ContextMenu.IsOpen)">
<DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
<ControlTemplate.Resources>
<anal:CustomMultiValueConvertor x:Key="CustomMultiValueConvertor"/>
<anal:IntToPercentageBrushConvertor x:Key="IntToPercentageBrushConvertor"/>
</ControlTemplate.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.ContextMenu>
<ContextMenu Name="ContextMenu" IsOpen="False" Placement="Top" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<ContextMenu.ItemsSource>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="ChildCommands"/>
</ContextMenu.ItemsSource>
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="MenuItem.Header">
<Setter.Value>
<MultiBinding Converter="{StaticResource CustomMultiValueConvertor}">
<Binding Path="Percentage" />
<Binding Path="ViewCommand.Command.Text" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="MenuItem.IsEnabled" Value="False"/>
<Setter Property="MenuItem.Background" Value="{Binding Percentage, Converter={StaticResource IntToPercentageBrushConvertor}}"/>
<Setter Property="MenuItem.Padding" Value="0"/>
<Setter Property="MenuItem.BorderThickness" Value="0"/>
<Setter Property="MenuItem.ToolTip" Value="123"/>
</Style>
</ContextMenu.ItemContainerStyle>
<ContextMenu.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ContextMenu.ItemsPanel>
</ContextMenu>
</Grid.ContextMenu>
<TextBlock TextAlignment="Center"
VerticalAlignment="Stretch"
Foreground="{StaticResource CommandBarForeground}"
Background="{StaticResource MainForegroundBrush}"
FontFamily="{StaticResource FontFamily}"
FontSize="10"
Grid.Column="0"
Grid.Row="0">
<TextBlock.Text>
<Binding Path="Text" StringFormat="{}{0}%" RelativeSource="{RelativeSource TemplatedParent}" />
</TextBlock.Text>
</TextBlock>
<Rectangle Grid.Column="0"
Grid.Row="1">
<Rectangle.Fill>
<Binding Path="BackgroundBrush" RelativeSource="{RelativeSource TemplatedParent}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I found the answer: the ToolTip didn't show up because the MenuItem is disabled.
Adding this line to my MenuItem style fixed it:
<Setter Property="ToolTipService.ShowOnDisabled" Value="True" />
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.
I am trying to use the Tag property as the source of a Binding, but the value is null when it gets to the converter.
What am I doing wrong?
Consumer
<Button Style="{StaticResource AddNewItemButtonStyle}" Tag="blah" />
Binding
<Style x:Key="AddNewItemButtonStyle" BasedOn="{StaticResource blueButtonStyle}"
TargetType="{x:Type Button}">
...
<AccessText Text="{Binding RelativeSource={RelativeSource Self},
Path=Tag, Converter={StaticResource AddNewItemForLabel}}">
</Style>
UPDATE
I added a setter for the ToolTip using the same strategy, and that does work BUT only after the second call to the converter (triggered by mousing over).
Can you see why the binding wouldn't work on the first pass?
Is there some place else besides the Tag that I can use more reliably?
2nd UPDATE
Based on Phil's input I changed my style to the xaml below. Must I add a Template property to the style?
<Style x:Key="AddNewItemButtonStyle" BasedOn="{StaticResource blueButtonStyle}" TargetType="{x:Type Button}">
<Setter Property="resx:ResxExtension.DefaultResxName" Value="Smack.Core.Presentation.Resources.MasterDetail"/>
<Setter Property="Content" >
<Setter.Value>
<StackPanel Orientation="Horizontal">
<Image Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" Stretch="Uniform" />
<AccessText VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag, Converter={StaticResource AddNewItemForLabel}}" />
<ContentPresenter/>
</StackPanel>
</Setter.Value>
</Setter>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag, Converter={StaticResource AddNewItemForToolTip}}"/>
<Setter Property="Command" Value="{Binding AddNewItemCommand}" />
</Style>
If you change the xaml in the answer I gave to your other question to
<AccessText Grid.Column="1" VerticalAlignment="Center">
<AccessText.Text>
<MultiBinding StringFormat="{}_{0} {1}">
<Binding Source="{StaticResource Test}"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Tag"/>
</MultiBinding>
</AccessText.Text>
</AccessText>
Then Tag will work.
Or you can use the short form of TemplateBinding
<AccessText Grid.Column="1" VerticalAlignment="Center" Text="{TemplateBinding Tag}"/>
or the long form
<AccessText Grid.Column="1" VerticalAlignment="Center"
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag}"/>
or, your style will work like this (bits deleted for testing):
<Style x:Key="AddNewItemButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Content" >
<Setter.Value>
<StackPanel Orientation="Horizontal">
<AccessText VerticalAlignment="Center"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=Tag}" />
<ContentPresenter/>
</StackPanel>
</Setter.Value>
</Setter>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}"/>
</Style>
I'm trying to change the background of the selected item in a WPF ListBox.
I have attempted to implement a style for it, but for some reason it's not being applied. I still get a blue background. Can anyone see why?
<UserControl x:Class="Thumbnails"
xmlns:local="clr-namespace:ContentPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="800">
<UserControl.Resources>
<local:ThumbImageHeightConverter x:Key="HeightConv" />
<local:ThumbImageWidthConverter x:Key="WidthConv" />
<local:InnerGridHeightConverter x:Key="InnerGridHeightConv" />
<local:ReflectWidthConverter x:Key="ReflectWidthConv" />
<local:ReflectCenterYConv x:Key="ReflectCenterYConv" />
<Style x:Name="ListBoxItemStyle" TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ListBox}">
<!-- Set the ItemTemplate of the ListBox to a DataTemplate which explains how to display an object of type BitmapImage. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid x:Name="ThumbGrid" VerticalAlignment="Top" Height="{Binding ElementName=ThumbListBox, Path=ActualHeight}" >
<Grid.RowDefinitions>
<RowDefinition x:Name="ThumbGridThumbImgRow" ></RowDefinition>
<RowDefinition x:Name="GridReflectRow" Height="40" ></RowDefinition>
</Grid.RowDefinitions>
<Border x:Name="HighlightBorder" BorderThickness="7" BorderBrush="Black" CornerRadius="18" Padding="2" Margin="4" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid x:Name="ThumbInnerGrid">
<Grid.Height>
<MultiBinding Converter="{StaticResource InnerGridHeightConv}">
<Binding ElementName="ThumbGrid" Path="ActualHeight" />
<Binding ElementName="HighlightBorder" Path="CornerRadius" />
<Binding ElementName="mask" Path="CornerRadius" />
</MultiBinding>
</Grid.Height>
<Border x:Name="mask" Background="White" CornerRadius="15" />
<StackPanel x:Name="ThumbInnerStack" >
<StackPanel.OpacityMask>
<VisualBrush Visual="{Binding ElementName=mask}"/>
</StackPanel.OpacityMask>
<!--<Image x:Name="ThumbImg" Source="{Binding Path=UriSource}" Stretch="Fill" Width="{Binding}" Height="{Binding Source={StaticResource ThumbImageSize}, Path=ImgHeight}">-->
<Image x:Name="ThumbImg" Stretch="UniformToFill" SnapsToDevicePixels="True" >
<Image.Height>
<MultiBinding Converter="{StaticResource HeightConv}">
<Binding ElementName="HighlightBorder" Path="ActualHeight" />
<Binding ElementName="HighlightBorder" Path="BorderThickness" />
<Binding ElementName="HighlightBorder" Path="Padding" />
</MultiBinding>
</Image.Height>
<Image.Width>
<MultiBinding Converter="{StaticResource WidthConv}">
<Binding ElementName="ThumbImg" Path="ActualHeight" />
<Binding ElementName="HighlightBorder" Path="BorderThickness" />
<Binding ElementName="HighlightBorder" Path="Padding" />
</MultiBinding>
</Image.Width>
<Image.Source>
<BitmapImage UriSource="{Binding Path=Src}"></BitmapImage>
</Image.Source>
</Image>
</StackPanel>
</Grid>
</Border>
<Border Height="{Binding ElementName=ThumbImg, Path=ActualHeight}" Grid.Row="1" CornerRadius="15" SnapsToDevicePixels="True" Opacity="0.75" >
<Border.Width>
<MultiBinding Converter="{StaticResource ReflectWidthConv}">
<Binding ElementName="HighlightBorder" Path="ActualWidth" />
<Binding ElementName="HighlightBorder" Path="BorderThickness" />
</MultiBinding>
</Border.Width>
<Border.Background>
<VisualBrush Visual="{Binding ElementName=ThumbImg}">
<VisualBrush.Transform>
<ScaleTransform ScaleX="1" ScaleY="-1" CenterX="200">
<ScaleTransform.CenterY>
<MultiBinding Converter="{StaticResource ReflectCenterYConv}">
<Binding ElementName="ThumbImg" Path="ActualHeight" />
</MultiBinding>
</ScaleTransform.CenterY>
</ScaleTransform>
</VisualBrush.Transform>
</VisualBrush>
</Border.Background>
<Border.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1.3">
<GradientStop Offset="0" Color="Black"></GradientStop>
<GradientStop Offset="0.1" Color="Transparent"></GradientStop>
</LinearGradientBrush>
</Border.OpacityMask>
</Border>
<!--<Label x:Name="ThumbTitle" Grid.Row="1" Content="{Binding Path=Title}" HorizontalAlignment="Center"></Label>-->
<Label x:Name="ThumbTitle" Grid.Row="1" Content="{Binding ElementName=ThumbInnerGrid, Path=ActualHeight, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center"></Label>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled" />
<Setter Property="Background" Value="Black"/>
</Style>
</UserControl.Resources>
<UserControl.DataContext>
<ObjectDataProvider ObjectType="{x:Type local:ThumbImageLoader}" MethodName="LoadImagesv2" IsAsynchronous="True" />
</UserControl.DataContext>
<!-- This ListBox is the Content of the Window. Normally you would have a panel of some type as the Window's Content, but let's keep it simple. -->
<Grid x:Name="ThumbListBoxGrid">
<ListBox x:Name="ThumbListBox" ItemsSource="{Binding}" VerticalAlignment="Top" Height="{Binding ElementName=ThumbListBoxGrid, Path=ActualHeight}" IsSynchronizedWithCurrentItem="True" />
</Grid>
Anyone see what's wrong here?
You specify the SelectedItem Background for a ListBox with the SystemColors.HighlightBrushKey (focused) and SystemColors.ControlBrushKey (not focused)
<Style TargetType="{x:Type ListBox}">
<Style.Resources>
<!-- Background of selected item when focussed -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="White"/>
<!-- Background of selected item when not focussed -->
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="White" />
</Style.Resources>
<!--...-->
</Style>
For anyone searching for this in the future, the accepted answer doesn't actually apply the colour when control is not focused for me. The following should be used instead which appears to work as intended.
<Style TargetType="ListBox">
<Style.Resources>
<!-- Background of selected item when focussed -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#FFFFB733" />
<!-- Background of selected item when not focussed -->
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#FFFFB733"/>
</Style.Resources>
</Style>