listbox isSelected databinding in DataTemplate - c#

I try to simply databind IsSelected property with IsSelected field in my class. But after I change the value in code, it doesn't change the property, neither does clicking on ListBoxItem change the field value.
XAML:
<FlipView ItemsSource="{Binding Source={StaticResource itemsViewSource}}" ... >
<FlipView.ItemTemplate>
<DataTemplate>
<UserControl Loaded="StartLayoutUpdates"
Unloaded="StopLayoutUpdates">
<!-- other controls -->
<ListBox Grid.Row="1" Grid.ColumnSpan="3"
SelectionMode="Multiple" VerticalAlignment="Center"
ItemsSource="{Binding Answers}">
<ListBox.Resources>
<local:LogicToText x:Key="logToText" />
</ListBox.Resources>
<!-- bind IsSelected only in one way from
code to content -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBoxItem
IsSelected="{Binding IsSelected, Mode=TwoWay, Converter={StaticResource logToText}}"
Content="{Binding IsSelected, Mode=TwoWay, Converter={StaticResource logToText}}">
</ListBoxItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- not working at all
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="Content"
Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>-->
</ListBox>
</UserControl>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Code:
Answers
private ObservableCollection<PrawoJazdyDataAnswer> _answers =
new ObservableCollection<PrawoJazdyDataAnswer>();
public ObservableCollection<PrawoJazdyDataAnswer> Answers
{
get
{
return this._answers;
}
}
Single item(Answer)
public class PrawoJazdyDataAnswer : NPCHelper// PrawoJazdy.Common.BindableBase
{
public PrawoJazdyDataAnswer(String ans, bool ansb)
{
this._ans = ans;
this._isSelected = ansb;
}
public override string ToString()
{
return _isSelected.ToString(); //Only For debug purposes
//normally return _ans
}
private string _ans;
public string Ans
{
get { return this._ans; }
//set { this.SetProperty(ref this._ans, value); }
}
private bool _isSelected;
public bool IsSelected
{
get { return this._isSelected; }
set
{
_isSelected = value;
FirePropertyChanged("IsSelected");
//this.SetProperty(ref this._isSelected, value);
}
}
}
FirePropertyChanged
public class NPCHelper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Converter(which sometimes seems to be needed and others not..., I tried ~10 approaches from different tutorials/examples)
public class LogicToText : IValueConverter
{
/// <summary>
///
/// </summary>
public object Convert(object value, Type targetType,
object parameter, string language)
{
//if (value == null || (bool)value == false)
// return "False";
return value.ToString();
}
/// <summary>
///
/// </summary>
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
return value.ToString().Contains("True") ? true : false;
}
Thanks in advance, and sorry for my English(still learning).
#edit
Thanks for quick reply.
For test purposes I created a button and text block:
<Button Click="spr" >Sprawdź</Button>
<TextBlock Text="{Binding Answers[0].IsSelected, Mode=TwoWay}" > </TextBlock>
It's in other controls part (above list box, but in FlipView)
Click method
private void spr(object sender, RoutedEventArgs e)
{
var ans = ((PrawoJazdyDataQuestion)this.flipView.SelectedItem).Answers;
foreach (var item in ans)
item.IsSelected = item.IsSelected ? false : true;
}
As I wrote, when i'm changing data from code side, it is changing content of element, but not appearance of ListBoxItem. And if I just select it on ListBox, it doesn't change the data in TextBlock neither in ListBox itself.
#edit2 fixing typos...

To change the IsSelected property of the ListBoxItem, you need to change the ListBox.ItemContainerStyle. See here:
<ListBox Grid.Row="1" Grid.ColumnSpan="3"
SelectionMode="Multiple" VerticalAlignment="Center"
ItemsSource="{Binding Answers}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding IsSelected}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since the binding mode is TwoWay, selecting and deselecting the ListBoxItem's dynamically changes the content of the items to display either True or False.
Also notice how I changed the ListBox.ItemTemplate into a TextBlock instead of a ListBoxItem. The ItemTemplate defines the content of the ListBoxItem, so some type of content control is typically used. Below is the UI structure for the different layouts (this can be viewed using the WPF Tree Visualizer).
ListBoxItem as the ItemTemplate:
TextBlock as the ItemTemplate:
Edit
Also note that I removed the IValueConverter. Since your source and target properties are both bool, there is no need for a converter in this case. Try removing the converter references to see if that fixes the problem.

Do not bind to IsSelected on ListBoxItem. ListBox got SelectedItem or SelectedItems property, where You should pass item(s) to be selected, i.e. by binding.
Setting IsSelected property in model is not good idea. Better make SelectedItem notify property in ViewModel.

Related

WPF selecting an Item in a ListBox from the ViewModel using SelectedIndex

I'm new to MVVM/WPF and after a few hours of research, not finding any really useful/working answer for my project I decided to give it a go and try asking here.
I would like to select an Item from my Listbox, which uses a List as an ItemSource.
Relevant ViewModel:
public class FavoriteStructureVm : INotifyPropertyChanged
{
#region
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public ObservableCollection<FavoriteDataVm> Favorites { get; set; }
public int SelectedIndex { get; set; }
private FavoriteDataVm _selectedItem;
public FavoriteDataVm SelectedItem
{
set
{
_selectedItem = value;
var item = (FavoriteDataVm)_selectedItem;
if (item.Type == FavoriteDataType.Add)
{
SelectedIndex = 1;
}
}
}
}
The ListBox contains a few items by default, the last one always being one of the type Add, which if selected, may add a new item and select it by default or select the previously selected item if no new item is added. For Simplicity the selected item will just be 1 regardless of wether a new item is added or not.
No matter where and what I tried to update with OnPropertyChanged it didn't update the SelectedIndex in the view, however, by adding/inserting a new FavoriteDataVm into ObservableCollection<FavoriteDataVm> Favorites, the SelectedIndex in the view gets updated.
The process of adding a new item to the list does not always occur, nonetheless I'd like to always update the SelectedIndex.
Relevant XAML:
<ListBox Name="favMenu" ItemsSource="{Binding Favorites}" SelectionMode="Single"
HorizontalAlignment="Center" VerticalAlignment="Top"
BorderThickness="0" Background="Transparent" Height="{Binding ElementName=window, Path=ActualHeight}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
>
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="Transparent" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<!--changing default orientation-->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="Border"
BorderThickness="0" BorderBrush="Black" Background="{x:Null}"
Width="60" Height="60" CornerRadius="30" Margin="{Binding Margin}"
ToolTip="{Binding Name}">
<Image Source="{Binding ImageUri}" Width="60" Height="60" Stretch="UniformToFill">
<Image.Clip>
<EllipseGeometry RadiusX="30" RadiusY="30" Center="30,30"/>
</Image.Clip>
</Image>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I found a workaround to just create a dummy item and remove it, since adding something seems to update the SelectedIndexin the view. I don't see it as a solution as it comes with a lot of drawbacks.
So this kinda raises two questions actually:
How do I get to update the SelectedIndex of the ListBox?
And a beginner-question, since I'm new to MVVM:
Is this a correct implementation of MVVM?
How do I get to update the SelectedIndex of the ListBox?
You can get the selected index by getting the index of the SelectedItem in the source collection:
int selectedIndex = (SelectedItem != null && Favorites != null) ? Favorites.IndexOf(SelectedItem) : -1;
And you can select an item by setting the SelectedItem property:
SelectedItem = Favorites[1]; //selects the second item
You shouldn't bind both the SelectedItem and the SelectedIndex properties of the ListBox. These must be synchronized. Remove SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" from your XAML.
Also note that if you intend to update a source property dynamically, you need to raise the PropertyChanged for this property for the view to refresh:
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; OnPropertyChanged("SelectedIndex"); }
}
private FavoriteDataVm _selectedItem;
public FavoriteDataVm SelectedItem
{
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
You are not updating the SelectedIndex
The declaration should be as follows
private int _selectedIndex ;
public int SelectedIndex{
get{return _selectedIndex };
set{_selectedIndex=value;OnPropertyChanged("SelectedIndex") };
}
This way the list will be notified when the selectedindex changes
"Sorry if there are any typos, i wrote this directly here and not in a code editor so you might get typos"

Listbox with selectable checkboxes

I am attempting to reach this situation:
Have a ListBox with each cell containing a CheckBox and a TextBox, via DataTemplate
Make the list selectable, ie. I can bind the SelectedItems to a collection in my VM.
Link those selections to the status of the Checkbox(checked, unchecked).
Whenever the user types something in the TextBox, the Checkbox will be checked and selected, and vice versa, when the string is empty, it will be deselected.
I managed to get all of these criterias seperately like this:
Bind to SelectedItems using this solution.
Bind the CheckBox.IsChecked to:
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}" Path="IsSelected" Mode="OneWayToSource"/>
Bind the CheckBox.IsChecked to:
<Binding Path="Text" ElementName="MyTextBox" Converter="{View:EmptyStringToBooleanConverter}" Mode="OneWay"/>
The thing is I can't get both of these bindings to work together. I have tried other solutions like DataTriggers, but they were not helpful because IsSelected is not accessible and because I need to bind to something inside the DataTemplate.
I am really trying to avoid having to add a property "IsSelected" to my class (represented by the DataTemplate).
Please help, I am willing to hear crazy suggestions as long as they're MVVM-y.
Thanks!
In general:
checkbox is unchecked by default
when you type string "checked", the checkbox will be checked, otherwise will stay unchecked
hope it helps.
XAML
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:clr="clr-namespace:WpfApplication1"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<clr:StringToBooleanConverter x:Key="StringToBooleanConverter"/>
</Grid.Resources>
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chb" IsChecked="{Binding Text, ElementName=txt, Mode=OneWay, Converter={StaticResource StringToBooleanConverter}}"/>
<TextBox x:Name="txt" Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" Width="150"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code-Behind
Imports System.Collections.ObjectModel
Class MainWindow
Public Sub New()
InitializeComponent()
Me.DataContext = New ObservableCollection(Of DummyClass)(Enumerable.Range(0, 10).Select(Function(a) New DummyClass() With {.Title = "Dummy Title: " & a}))
End Sub
End Class
Public Class DummyClass
Property Title As String
End Class
Public Class StringToBooleanConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim strValue = System.Convert.ToString(value)
If String.IsNullOrEmpty(strValue) Then
Return False 'Unchecked
End If
If strValue = "checked" Then
Return True 'checked
End If
Return False 'default
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
This is the XAML code:
<ListBox ItemsSource="{Binding MyList}" SelectionMode="Multiple" Width="200">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel Margin="2">
<CheckBox DockPanel.Dock="Left" IsChecked="{Binding IsSelected}"/>
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" Background="Transparent"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
And its code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
for (int i = 0; i < 100; i++)
{
MyList.Add(new ViewModel());
}
}
//MyList Observable Collection
public ObservableCollection<ViewModel> MyList { get { return _myList; } }
private ObservableCollection<ViewModel> _myList = new ObservableCollection<ViewModel>();
}
ViewModel class (each item):
public class ViewModel : DependencyObject
{
//Text Dependency Property
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ViewModel),
new UIPropertyMetadata(null, (d, e) =>
{
((ViewModel)d).IsSelected = !string.IsNullOrWhiteSpace((string)e.NewValue);
}));
//IsSelected Dependency Property
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(ViewModel),
new UIPropertyMetadata(false, (d, e) =>
{
}));
}

Binding SelectedItem in nested ListBox using Caliburn.Micro

I'm trying to use Caliburn.Micro to bind a view model of a nested ListBox but I'm stuck.
I have a list workspace that is divided in two sections a list of groups containtaining items in a different part of that workspace I want to show the details of either a group or an item in a group depending on what is the SelectedItem. I'm new to Caliburn.Micro and looked at the documentation and samples but don't know how to connect the dots. Specifically I'm trying to model this after the Caliburn.Micro.HelloScreens sample. The code I have so far:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : Conductor<AnalyzerGroupWorkspaceViewModel>, IWorkspace
{
private Selected selected = Selected.AnalyzerGroup;
private const string name = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(analyzerGroups.GetAll().Select(fromMapper.Map<Model.AnalyzerGroup,AnalyzerGroupViewModel>));
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return name; } }
public Selected Selected
{
get { return selected; }
set
{
if (value == selected) return;
selected = value;
NotifyOfPropertyChange(() => Selected);
}
}
private IConductor Conductor { get { return (IConductor) Parent; } }
public void Show()
{
var haveActive = Parent as IHaveActiveItem;
if (haveActive != null && haveActive.ActiveItem == this)
{
DisplayName = name;
Selected = Selected.AnalyzerGroup;
}
else
{
Conductor.ActivateItem(this);
}
}
}
The view:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox x:Name="AnalyzerGroups">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<ListBox x:Name="Analyzers">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id }"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Context="{Binding Selected, Mode=TwoWay}"
cal:View.Model="{Binding}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
</GroupBox>
</DockPanel>
</UserControl>
Next to that I have two UserControls that display the detailsof a group or item.
My specific question is how can I use the SelectedItem property of the two ListBoxes to modify the Selected property to switch between displaying the AnalyzerGroup details and the Analyzer details?
I've found the solution to the above described problem the solution consists of four parts:
Add a IsSelected property (that notifies changes) to both 'child' ViewModels
Bind the IsSelected property of the ListBox.ItemContainerStyle to the IsSelected property of the respective ViewModels
Attach a Caliburn.Micro Message to the 'outer' ListBox and use the $eventArgs argument
In the ViewModel bound to the entire UserControl implement the method corresponding to the Message and use the AddedItems property of the eventArgs to set the SelectedViewModel property setting the IsSelected property of the previous SelectedViewModel to false
The code then becomes:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : PropertyChangedBase, IAnalyzerGroupWorkspaceViewModel
{
private IAnalyzerViewModel selectedViewModel;
private const string WorkspaceName = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(
analyzerGroups.GetAll().Select(
fromMapper.Map<Model.AnalyzerGroup, AnalyzerGroupViewModel>));
}
public void SelectionChanged(object eventArgs)
{
var typedEventArgs = eventArgs as SelectionChangedEventArgs;
if (typedEventArgs != null)
{
if (typedEventArgs.AddedItems.Count > 0)
{
var item = typedEventArgs.AddedItems[0];
var itemAsGroup = item as IAnalyzerViewModel;
if (itemAsGroup != null)
{
SelectedViewModel = itemAsGroup;
}
}
}
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return WorkspaceName; } }
public IAnalyzerViewModel SelectedViewModel
{
get { return selectedViewModel; }
set
{
if (Equals(value, selectedViewModel))
{
return;
}
if (SelectedViewModel != null)
{
SelectedViewModel.IsSelected = false;
}
selectedViewModel = value;
NotifyOfPropertyChange(() => SelectedViewModel);
}
}
}
The View:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox SelectionMode="Single"
x:Name="AnalyzerGroups"
cal:Message.Attach="[Event SelectionChanged] = [Action SelectionChanged($eventArgs)]">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="DarkGray">
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock Text="{Binding Name}" />
<ListBox SelectionMode="Single" ItemsSource="{Binding Analyzers}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="DarkGray">
<StackPanel>
<TextBlock Text="{Binding Name }" Margin="10" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Model="{Binding SelectedViewModel}" />
</GroupBox>
</DockPanel>
</UserControl>
The answer to your specific question is yes, you can.
On the ViewModel of your UserControl. You create a property that is a ViewModel of either of the two details.
public interface IAnalyzerViewModel
{
}
Next, create two ViewModels for the Views of your Analyzer and AnalyzerGroup views.
public class AnalyzerGroupViewModel : IAnalyzerViewModel
{
}
public class AnalyzerViewModel : IAnalyzerViewModel
{
}
Next, create a property in your UserControl's ViewModel that implements INPC or PropertyChangedBase of Caliburn Micro.
public class MainViewModel :
{
private IAnalyzerViewModel _analyzerViewModel;
public IAnalyzerViewModel SelectedViewModel { get { return _analyzerViewModel; } set { _analyzerViewModel = value; OnPropertyChanged(() => SelectedViewModel); }
//Hook up the selected item changed event of your listbox and set the appropriate ViewModel to show, so if you either want to show the AnalyzerGroup or AnalyzerView.
}
And lastly, just update your MainView to
<ContentControl x:Name="SelectedViewModel"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
Caliburn will hook up the appropriate bindings and stuff and will pull the View for the associated ViewModel, and also the Name convention part will automatically map it to any public property of its datacontext as long as the names match.

Why does ComboBox not initially show the ComboBoxItem with IsSelected==true?

In the following code, why doesn't the item with property IsSelected set to true become selected in the ComboBox just as it does in the ListBox after clicking the Button?
Once I click on the ComboBox, the selected item becomes selected, but not before.
xaml:
<Window x:Class="WpfApplication1.Desktop.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Items}">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Txt}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Txt}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Select second item" Click="Button_Click" />
</StackPanel>
</Window>
xaml.cs:
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Windows;
using Microsoft.Practices.Prism.ViewModel;
namespace WpfApplication1.Desktop
{
[Export]
public partial class Shell : Window
{
public class Foo : NotificationObject
{
static int _seq = 0;
string _txt = "Item " + (++_seq).ToString();
public string Txt { get { return _txt; } }
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
RaisePropertyChanged(() => IsSelected);
}
}
}
public ObservableCollection<Foo> Items { get; set; }
public Shell()
{
Items = new ObservableCollection<Foo>();
for (int i = 0; i < 5; i++)
Items.Add(new Foo());
DataContext = this;
InitializeComponent();
}
void Button_Click(object sender, RoutedEventArgs e)
{
Items[1].IsSelected = true;
}
}
}
It's because the ItemContainerStyle is applied only when the ComboBoxItems are generated (i.e. when you open the dropdown).
To work around this, you create another property called SelectedItem and bind the Combobox's SelectedValue to it.
Long explanation and example here
Because the binding is set on UpdateSourceTrigger=LostFocus by default, you would have to change it to PropertyChanged to get the result you want.
Like this:
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"Value="{Binding Path=IsSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</Style>
When using a Model as the DataContext for a WPF Window, the Controls may not initially behave as you'd expect. Essentially, some properties/events never get set/called until after the Window been initialized. The work-around in this case is to setup the binding in the Window's Loaded event.
Disclaimer: I have not tested this with the OP's specific scenario, but this is the behavior and work-around I've encountered in the past.

WPF: Custom ListBoxItem with DataTrigger

I have a WPF ListBox containing CheckBoxes. I would like the text colour of the TextBox to change to red when the ViewModel notices that the bound value is now updated. I have the below XAML but it is not working. I can see the IsUpdated property being queried but when the value is True the colour is not changing. I'm sure I'm missing something obvious but can't quite figure it out.
<ListBox MinHeight="100" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="2" SnapsToDevicePixels="true">
<CheckBox x:Name="_checkBox" IsChecked="{Binding Path=IsAllowed}" Content="{Binding Item}"/>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsUpdated}" Value="True">
<Setter TargetName="_checkBox" Property="Foreground" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Are you implementing INotifyPropertyChanged (as mentioned by Matt Hamilton) in your Item class and raising the PropertyChanged event when you set IsUpdated from false to true and vice-versa.
public class Item : INotifyPropertyChanged
{
// ...
private bool _isUpdated;
public bool IsUpdated
{
get{ return _isUpdated; }
set {
_isUpdated= value;
RaisePropertyChanged("IsUpdated");
}
}
// ...
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if(PopertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
// ...
}

Categories