IsSelected Binding in WPF DataGrid - wpf

I have in my Model (Class X) Boolean property: IsSelected, is linked to a WPF DataGrid as follows:
<DataGrid SelectedIndex="{Binding SelectedXIndex,Mode=TwoWay}"
DataContext="{Binding MyViewModel}"
ItemsSource="{Binding ListX}" AutoGenerateColumns="False">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
ListX- ObservableCollection
IsSelecte- Call to NotifyPropertyChange
It works great.
But when I have a lot of rows, that I need to scroll to see them, and I press the button "Select All" that runs the following function, he chooses me only some of the rows and not all: (Even though all the IsSelected on the list is true)
public void SelectAll()
{
ListX.All(c => c.IsSelected = true);
}
I can not understand why this is happening?

that's what helped me finally:
I put in the DataGrid:
VirtualizingStackPanel.VirtualizationMode="Standard"

Related

Set the cell as ReadOnly in Datagrid in code-behind - Cell has different value [duplicate]

I understand you can make the whole DataGrid or a whole column readyonly (IsReadOnly = true). However, at cell level this property is ready only. But I do need this level of granularity. There is blog about adding IsReadOnly to a row by changing the source code in old days when DataGrid was public domain, but now I don't have source code for DataGrid. What's workaround?
Making cell disabled (IsEnabled=false) almost meets my need. But the problem is that you can't even click the disabled cell to select the row (I have full row selection mode).
EDIT: Since nobody has responded to this question, so I guess it's not an easy fix. Here is a possible workaround: Make the cell uneditable. The only problem is that clicking the cell doesn't select the row. I just noticed that MouseDown or MouseUp event of the DataGrid is still fired when the disabled cell is clicked. In this event handler, if I could figure out the row it clicked, I could select the row programmatically. However, I couldn't figure out how to find the underlying row from DataGrid.InputHitTest. Can somebody please give me some tip?
After much searching and experimentation using IsTabStop = False and Focusable = False works best for me.
<DataGridTextColumn Header="My Column" Binding="{Binding Path=MyColumnValue}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ReadOnly}" Value="True">
<Setter Property="IsTabStop" Value="False"></Setter>
<Setter Property="Focusable" Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
I've encountered the same problem, the cell should be read-only in some rows but not in the others. Here is a workaround solution:
The idea is to dynamically switch the CellEditingTemplate between two templates, one is the same as the one in the CellTemplate, the other is for editing. This makes the edit mode acts exactly the same as the non-editing cell although it is in edit mode.
The following is some sample code for doing this, notice that this approach requires DataGridTemplateColumn:
First, define two templates for read-only and editing cells:
<DataGrid>
<DataGrid.Resources>
<!-- the non-editing cell -->
<DataTemplate x:Key="ReadonlyCellTemplate">
<TextBlock Text="{Binding MyCellValue}" />
</DataTemplate>
<!-- the editing cell -->
<DataTemplate x:Key="EditableCellTemplate">
<TextBox Text="{Binding MyCellValue}" />
</DataTemplate>
</DataGrid.Resources>
</DataGrid>
Then define a data template with additional ContentPresenter layer and use Trigger to switch the ContentTemplate of the ContentPresenter, so the above two templates can be switched dynamically by the IsEditable binding:
<DataGridTemplateColumn CellTemplate="{StaticResource ReadonlyCellTemplate}">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<!-- the additional layer of content presenter -->
<ContentPresenter x:Name="Presenter" Content="{Binding}" ContentTemplate="{StaticResource ReadonlyCellTemplate}" />
<DataTemplate.Triggers>
<!-- dynamically switch the content template by IsEditable binding -->
<DataTrigger Binding="{Binding IsEditable}" Value="True">
<Setter TargetName="Presenter" Property="ContentTemplate" Value="{StaticResource EditableCellTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
HTH
There is a property on DataGridCell.IsReadOnly that you might think you can bind to,
e.g. using XAML like this:
<!-- Won't work -->
<DataGrid Name="myDataGrid" ItemsSource="{Binding MyItems}">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="IsReadOnly" Value="{Binding MyIsReadOnly}" />
</Style>
</DataGrid.Resources>
<!-- Column definitions... -->
</DataGrid>
Unfortunantly this won't work because this property is not writable.
Next you might attempt to intercept and stop mouse events, but this won't prevent the user from entering edit mode using the F2 key.
The way I sloved this was by listening for the PreviewExecutedEvent on the DataGrid and then conditionally flagging it as handled.
E.g. by adding code similar to this to the constructor of my Window or UserControl (or another more suitable place):
myDataGrid.AddHandler(CommandManager.PreviewExecutedEvent,
(ExecutedRoutedEventHandler)((sender, args) =>
{
if (args.Command == DataGrid.BeginEditCommand)
{
DataGrid dataGrid = (DataGrid) sender;
DependencyObject focusScope = FocusManager.GetFocusScope(dataGrid);
FrameworkElement focusedElement = (FrameworkElement) FocusManager.GetFocusedElement(focusScope);
MyRowItemModel model = (MyRowItemModel) focusedElement.DataContext;
if (model.MyIsReadOnly)
{
args.Handled = true;
}
}
}));
By doing it like this the cells are still focusable and selectable.
But the user will not be able to enter edit mode unless your model items allow it for the given row.
And you will not suffer the performance costs or complexities by using the DataGridTemplateColumn.
I've solved this problem in my application by setting the underlying object in the cell (eg. CheckBox) - IsHitTestVisible = false; Focusable = false;
var cb = this.dataGrid.Columns[1].GetCellContent(row) as CheckBox;
cb.IsHitTestVisible = false;
cb.Focusable = false;
"row" is a DataGridRow. IsHitTestVisible=false means that you can't click/select/manipulate the underlying object via mouse, but you can still select the DataGridCell. Focusable=false means that you can't select/manipulate the underlying object with the keyboard. This gives the illusion of a ReadOnly cell, but you can still select the cell and I'm sure if the DataGrid is set up to SelectionMode=FullRow then clicking the "read only" cell will select the entire row.
My solution is to use binding to the DataGridTemplateColumn with converter.
<UserControl.Resources>
<c:isReadOnlyConverter x:Key="isRead"/>
</UserControl.Resources>
<DataGridTemplateColumn x:Name="exampleTemplate" Header="example:" Width="120" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="exampleCheckBox" VerticalAlignment="Center" IsEnabled="{Binding ElementName=exmpleTemplate, Path=IsReadOnly, Converter={StaticResource isRead}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
and the converter:
class isReadOnlyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
return !(bool)value;
}
catch (Exception)
{
return false;
}
}
This is a bit late but, I was looking into this as well, these solutions work well but I needed something a little different, I did the following and it works exactly like I wanted and what the question is looking for.
I essentially I wanted to be able to enter edit mode for the cell and have all that other templates and command logic the same while not being able to edit the cell.
The solution for all this is to set the TextBox.IsReadOnly property to true in the DataGridCell Style and to handle the initial keydown event
<Style TargetType="DataGridCell">
<Setter Property="TextBox.IsReadOnly" Value="True"/>
<EventSetter Event="PreviewKeyDown" Handler="cell_PreviewKeyDown"/>
</Style>
and the following code behind to stop the initial edit
protected void cell_PreviewKeyDown(object sender, KeyEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell.IsEditing == false &&
((Keyboard.Modifiers & ModifierKeys.Control) != ModifierKeys.Control)) //So that Ctrl+C keeps working
{
cell.IsEditing = true;
e.Handled = true;
}
}
Hopefully this is helpful.
Based on #sohum comment, here you can use simplified version of the response marked as answer.
dataGrid.BeginningEdit += DataGrid_BeginningEdit;
(...)
private static void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
//Actual content of the DataGridCell
FrameworkElement content = e.Column.GetCellContent(e.Row);
MyObject myObject = (MyObject)content.DataContext;
if (!myObject.CanEdit)
{
e.Cancel = true;
}
}
You can use it later as Attached Property Behaviour.
One way of getting selectable, read-only text cells for DataGrid is to use template and style like this:
<DataGrid>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Padding="0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<TextBox BorderThickness="0" MouseDoubleClick="DataGrid_TextBox_MouseDoubleClick" IsReadOnly="True" Padding="5" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Text}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
And for CS backend:
private void DataGrid_TextBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
(sender as TextBox).SelectAll();
}
You can do this with a simpler data template.
<DataGrid.Resources>
<DataTemplate x:Key="MyTemplate" DataType="MyRowDataType">
<TextBox Text="{Binding Value}" IsReadOnly="{Binding IsReadOnly}" />
</DataTemplate>
</DataGrid.Resources>
...
<DataGridTemplateColumn CellTemplate="{StaticResource MyTemplate}" />
For me, the most simple solution was to style the TextBox inside the EditingElementStyle.
<DataGridTextColumn Binding="{Binding MyValueProperty}">
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding MyReadonlyProperty}" Value="True">
<Setter Property="IsReadOnly" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
In my case I was using DataGridTextColumn. I set the IsEnabled property on ContentPresenter in Style as follows and it works fine
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}" >
<ContentPresenter IsEnabled="{Binding Path=IsEditable}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGridTextColumn Header="A"
Binding="{Binding Path=A}"/>
</DataGrid>

wpf combobox not binding to collection when inside ContentTemplate

I'm trying show a certain control based on a property (combobox or textbox). So I have this contentcontrol implemented:
<!--<ComboBox MaxWidth="200" Background="#333333" ItemsSource="{Binding ModelObjectWrapper.Values}" Grid.Row="1" Grid.Column="1"/>-->
<ContentControl Grid.Row="1" Grid.Column="1">
<ContentControl.Resources>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ModelObjectWrapper.ObjType}" Value="typeA">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox HorizontalAlignment="Left" MaxWidth="200" Background="#333333" ItemsSource="{Binding ModelObjectWrapper.Values, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ModelObjectWrapper.ObjType}" Value="typeB">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Resources>
</ContentControl>
The problem is that the combobox is not showing any items when its part of a controltemplate, and I know the bound list does have them, so I'm assuming the combobox is not being bound to the ItemsSource correctly.
The first line (having only a combobox without the templates), commented out, works fine. Am I not doing the binding right? Could it be that because its part of a datatrigger, it's not getting the right DataContext? I must note that the DataTrigger itself works great (IE showing a combobox if *.ObjType == "typeA".
The VM is a wrapper class around an object:
public class ModelObjectWrapper : ViewModelBase
{
private theModelObject model_obj;
public ModelObjectWrapper(theModelObject obj)
{
model_obj = obj;
}
public ObservableCollection<string> Values
{
get { return model_obj.Values; }
set
{
if (value == model_obj.Values)
return;
model_obj.Values = value;
OnPropertyChanged();
}
}
}
The DataContext of the root element in a ContentControl is the Content of the same ContentControl. Try to use a RelativeSource to bind to a property of the ContentControl's DataContext:
<ComboBox HorizontalAlignment="Left" MaxWidth="200" Background="#333333"
ItemsSource="{Binding DataContext.ModelObjectWrapper.Values, RelativeSource={RelativeSource AncestorType=ContentControl}}"/>
By the way, there is no point setting the UpdateSourceTrigger of an ItemsSource binding to PropertyChanged because the ComboBox never sets the source property.

Binding to DataGridColumnHeader.Command

I need to use Command on DataGridColumnHeader to sort my data. Standard DataGrid sorting feature is not enough for me because there are not all data displayed in DataGrid. I don't even have all data present in my VM. (It's not possible... it's too much) I just request concrete page of data from server. And now I would also like to get concrete page from sorted data.
So I did this:
<DataGrid ItemsSource="{Binding Path=Entities, Mode=OneWay}" CanUserSortColumns="False" SelectionMode="Single" SelectedItem="{Binding Path=SelectedEntity}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn >
<DataGridTemplateColumn.Header>
<DataGridColumnHeader Content="Column1" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Property1}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
It works great. And now I just want to have AutoGenerateColumns="True" unfortunately it works no more. Is there anybody who can explain why it doesn't work for autogenerated columns and provide me with some solution? Thank you in advance!
EDIT
It has probably something to do with the fact that following doesn't work either.
<DataGrid ItemsSource="{Binding Path=Entities, Mode=OneWay}" CanUserSortColumns="False" SelectionMode="Single" SelectedItem="{Binding Path=SelectedEntity}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn>
<!-- HERE IS THE CHANGE -->
<DataGridTemplateColumn.Header>Column1</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Property1}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
But style is applied. I know this because I tried:
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="Tomato"/> <!-- beautiful tomato background -->
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
Column header has Tomato background, but command doesn't work.
EDIT2
Following is the solution. Apparently DataGridColumnHeaders doesn't inherit DataContext, because when I change the command binding everything works again.
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.MyCommand}"/>
But still I don't quite understand why. So answer with detailed explanation will get the bounty.
DataGridColumnHeader is container item for DataGridColumn.Header same as, for example, ListBoxItem is container item for each item in ListBox's ItemsSource.
Since ListBoxItem's DataContext will be set to an item from ListBox's ItemsSource, it is natural that DataGridColumnHeader's DataContext will be set to DataGridColumn.Header object.
As your findings suggest, autogenerated columns will have a DataGridColumn.Header with its DataContext set to the string header object.
If you do not use autogenerated columns, and instead specify a DataGridTemplateColumn and the DataGridTemplateColumn.Header, standard inheritance for the DataContext still applies, so that's why it worked for you the first time.
Edit: A quick search didn't provide immediate results where this behavior is described. However, verifying the fact is not too hard:
Xaml:
<DataGrid ItemsSource="{Binding MyClasses}" AutoGenerateColumns="True" MouseRightButtonUp="DataGrid_MouseRightButtonUp_1" >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<DataGridColumnHeader Content="Name (Manual Column)" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Class1> _myClasses;
public ObservableCollection<Class1> MyClasses { get { return _myClasses; } set { _myClasses = value; OnPropertyChanged("MyClasses"); } }
public ViewModel()
{
MyClasses = new ObservableCollection<Class1>()
{
new Class1() { Name = "Andy" },
new Class1() { Name = "Mark" },
new Class1() { Name = "Peter" },
new Class1() { Name = "Gregor" },
new Class1() { Name = "Jenny" }
};
}
}
Code behind (for illustration purpose only):
private void DataGrid_MouseRightButtonUp_1(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Microsoft.Windows.Themes.DataGridHeaderBorder)
{
object dataContext = ((Microsoft.Windows.Themes.DataGridHeaderBorder)e.OriginalSource).DataContext;
}
}
Right-click on the manual column header (outside the textblock) produces the following dataContext:
Right-click on the autogenerated column header:

How to bind DataGridTemplateColumn.Visibility to a property outside of DataGrid.ItemsSource?

I need to bind the Visibility of a DataGridTemplateColumn to a property outside of the DataGrid.ItemsSource,because i need to bind this column in the all the rows to one property inside the ViewModel,but as far as i know you just can bind that to something inside the ItemsSource or you should use ElementStyle and EditingElementStyle
I've Already tried this code:
<DataGridTemplateColumn Header="post"
Visibility="{Binding DataContext.ProjectPostVisibility
, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MvvmCommonControl:DataGrid}}"/>
And i'm Sure my binding is correct because it works fine when i bind the DataGridCell.Visibility like below:
<DataGridTemplateColumn Header="post">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Visibility" Value="{Binding DataContext.ProjectPostVisibility,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MvvmCommonControl:DataGrid}}"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn >
Your binding is correct, but it won't work with DataGridTemplateColumn directly because it's not in the visual tree. So it's not inherting DataContext.
You need to bind the DataGridTemplateColumn from code behind. Here is a demo that shows a way of doing it.
As mentionned in other answers, the column isn't part of the visual/logical tree and doesn't inherit from FrameworkElement meaning it has no DataContext. That's why your binding doesn't work.
However you can add a dummy (collapsed) FrameworkElement at a level where the DataContext is what you're looking for (so taking your example, it'd be at the DataGrid's level), collapse it and use it as the Source of your Binding with the x:Reference markup extension.
Here's an example :
<FrameworkElement x:Name="Proxy" Visibility="Collapsed"/>
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn Header="post"
Visibility="{Binding DataContext.ProjectPostVisibility, Source={x:Reference Name=Proxy}}"/>
</DataGrid.Columns>
</DataGrid>
Add this setter in the DataGridTemplateColumn.CellStyle and done:
<Setter Property="Visibility" Value="{Binding DataContext.isVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
If you need more help look at my example below.
I want the Remove button to not be visible at the project level. First you have to make sure you have a isVisible property in your view model:
private System.Windows.Visibility _isVisible;
public System.Windows.Visibility isVisible
{
get { return _isVisible; }
set
{
if (_isVisible != value)
{
_isVisible = value;
OnPropertyChanged("isVisible");
}
}
}
Then:
if (isProj == false)
this.model.isVisible = Visibility.Visible;
else
this.model.isVisible = Visibility.Collapsed;
XAML:
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<Button x:Name="btnRemove" Content="X">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="50" />
</Style>
</Button.Style>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Red"/>
<Setter Property="Visibility" Value="{Binding DataContext.isVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
</Style>
</DataGridTemplateColumn.CellStyle>

WPF ListBox selection issues, providing feedback to user

I'm trying to create a listbox that shows a thumbnail view of page content, for an app with one canvas but multiple 'pages'. Yes, that's probably not the best place to start from but for historical reasons that's what I have.
I've implemented the ListBox with data binding to a singleton 'WorkBook' that has an ObservableCollection of PageData (everything that appears on a page, including it's background).
What I really really want is to be able to change the Border colour of a ListBoxItem when it's selected and keep that Border colour while it's content is the currently selected item in the class that hosts the collection.
My problems are:-
1/ I can't get the ListBox to select the 1st item on program startup.
2/ when the ListBox loses focus the SelectedIndex is always -1 (so no selection)
3/ adding to the ListBox results in no selection (SelectedIndex == -1)
4/ using triggers, I can set the border of the selectedItem but this is lost when the ListBox loses focus. As the ListBoxItem shows an image that is opaque the 'standard' way of making selection colours stay when out of focus doesn't work - ie
<Style x:Key="PsThumb">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="White"></SolidColorBrush>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="White"></SolidColorBrush>
</Style.Resources>
</Style>
My code for the ListBox is as follows :-
<ListBox x:Name="PageSorter" Style="{StaticResource PsThumb}" Width="148" BorderThickness="4" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
VerticalAlignment="Stretch" ItemsSource="{Binding Pages,Source={StaticResource WorkBook}}" SelectedItem="{Binding Path=CurrentPageData, Mode=TwoWay}"
AllowDrop="True" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border x:Name="border" BorderBrush="DarkGray" BorderThickness="4" Margin="2,4,2,4" CornerRadius="5">
<Border.Effect>
<DropShadowEffect ShadowDepth="6"/>
</Border.Effect>
<Image Source="{Binding Thumbnail}" Width="130" Height="95" Stretch="Fill"/>
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsSelected}" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="Red"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The simplest way to achieve what you're asking is to bind the DataTrigger to a property inside the items of the ListBox. In the example below, I added the Selected property on my Contact class:
public class Contact : INPCBase
{
public string Name { get; set; }
private bool _Selected;
public bool Selected
{
get { return _Selected; }
set
{
_Selected = value;
NotifyPropertyChanged("Selected");
}
}
}
I then bind my ListBox to a List<Contact>. When the user checks the CheckBox, we change the Selected property and in turn trigger the Style:
<Window x:Class="WpfApp.ListboxKeepSelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListboxKeepSelectionWindow" Height="277" Width="343"
xmlns:me="clr-namespace:WpfApp">
<Window.Resources>
<me:ContactList x:Key="sample"/>
<Style TargetType="ListBoxItem" x:Key="SelectedListBoxItemStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Selected}" Value="True">
<Setter Property="BorderBrush" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListBox Name="lbxContacts"
ItemsSource="{StaticResource ResourceKey=sample}"
SelectionMode="Extended"
ItemContainerStyle="{StaticResource ResourceKey=SelectedListBoxItemStyle}"
SelectionChanged="lbxContacts_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding Path=Selected}">
<TextBlock Text="{Binding Path=Name}"/>
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
To allow the user to select items without using the checkbox, I added this small event on the ListBox. Since the Contact class implements the INotifyPropertyChanged interface, the value of the CheckBox is updated so the user knows the selection worked:
private void lbxContacts_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (Contact c in e.AddedItems)
{
c.Selected = !c.Selected;
}
}
When you want to get a list of selected items, you just use a LINQ query on the ItemsSource to get items where Selected == true.

Resources