Find Control in DataTemplate with a multi selection listbox - c#

I have a multi selection listbox where a user can tick multiple items in the list. At the moment I have it so when a checkbox is ticked the ListBoxItem it is within also gets selected:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
//Select the Item using the DataContext of the Button
object clicked = (e.OriginalSource as FrameworkElement).DataContext;
var lbi = LstDistro.ItemContainerGenerator.ContainerFromItem(clicked) as ListBoxItem;
lbi.IsSelected = true;
}
Now I am trying to do it the other way. Whenever a ListBoxItem is selected the checkbox within it gets ticked. So far I have it so the first item you select will get ticked but after that none of the other items you select get ticked. I need to somehow have it loop through all the current selected items.
My current code:
WPF:
<ListBox x:Name="LstDistro" HorizontalAlignment="Left" Margin="10,10,0,42" Width="235" BorderBrush="Black" BorderThickness="2,2,1,1" SelectionMode="Multiple" SelectionChanged="LstDistro_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Canvas x:Name="EventItem" HorizontalAlignment="Left" Height="42" VerticalAlignment="Top" Width="215" Background="{Binding Path=LBackground}">
<Label Content="{Binding LInits}" HorizontalAlignment="Left" Height="33" VerticalAlignment="Top" Width="40" FontWeight="Bold" FontSize="12" Canvas.Top="5"/>
<Label Content="{Binding LFullName}" HorizontalAlignment="Left" Height="33" VerticalAlignment="Top" Width="164" FontSize="12" Canvas.Left="40" Canvas.Top="5"/>
<CheckBox x:Name="ChkName" Height="20" Width="20" Canvas.Left="190" Canvas.Top="12" Checked="CheckBox_Checked" IsChecked="False"/>
</Canvas>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
//Select the Item using the DataContext of the Button
object clicked = (e.OriginalSource as FrameworkElement).DataContext;
var lbi = LstDistro.ItemContainerGenerator.ContainerFromItem(clicked) as ListBoxItem;
lbi.IsSelected = true;
}
private void LstDistro_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Get the current selected item
ListBoxItem item = LstDistro.ItemContainerGenerator.ContainerFromIndex(LstDistro.SelectedIndex) as ListBoxItem;
CheckBox ChkName = null;
//Get the item's template parent
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
//Get the DataTemplate that the Checkbox is in.
DataTemplate dataTemplate = LstDistro.ItemTemplate;
ChkName = dataTemplate.FindName("ChkName", templateParent) as CheckBox;
ChkName.IsChecked = true;
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
FrameworkElement child = null;
for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceElement); i++)
{
child = VisualTreeHelper.GetChild(referenceElement, i) as FrameworkElement;
System.Diagnostics.Debug.WriteLine(child);
if (child != null && child.GetType() == typeof(T))
{ break; }
else if (child != null)
{
child = GetFrameworkElementByName<T>(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
}
}
return child as T;
}

You should set the Checkbox IsChecked binding like this:
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
This means whenever you select a ListBoxItem your checkbox will also become checked.
Hope this helped :)

See the VisualHelper class here
It provides extension methods FindVisualChild, FindVisualChilds
var checkedCheckBoxes= LstDistro.FindVisualChilds<CheckBox>().Where(s=>s.IsChecked==true);

Ok. Delete all that code and start all over.
If you're working with WPF, you really need to understand and embrace The WPF Mentality.
This is how you do what you're looking for, in proper WPF:
<Window x:Class="WpfApplication14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication14"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<Button Content="Show Selected Item Count" Click="Button_Click"
DockPanel.Dock="Top"/>
<Button Content="Select All" Click="SelectAll"
DockPanel.Dock="Top"/>
<Button Content="Select All" Click="UnSelectAll"
DockPanel.Dock="Top"/>
<ListBox ItemsSource="{Binding}" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DockPanel>
</Window>
Code behind:
public partial class MainWindow : Window
{
private ObservableCollection<SelectableItem> Items { get; set; }
public MainWindow()
{
InitializeComponent();
//Create dummy items, you will not need this, it's just part of the example.
var dummyitems = Enumerable.Range(0, 100)
.Select(x => new SelectableItem()
{
DisplayName = x.ToString()
});
DataContext = Items = new ObservableCollection<SelectableItem>(dummyitems);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Items.Count(x => x.IsSelected).ToString());
}
private void SelectAll(object sender, RoutedEventArgs e)
{
foreach (var item in Items)
item.IsSelected = true;
}
private void UnSelectAll(object sender, RoutedEventArgs e)
{
foreach (var item in Items)
item.IsSelected = false;
}
}
Data Item:
public class SelectableItem:INotifyPropertyChanged
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public string DisplayName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Notice How simple and happy life is when you're using WPF's capabilities instead of a manual, procedural, winforms-like approach.
Simple, simple properties and INotifyPropertyChanged. That's how you program in WPF. No need for complicated VisualTreeHelper.Whatever() stuff, no need to manipulate the UI in procedural code. Just simple, beautiful DataBinding.
See how I'm operating against my Data in the SelectAll() and UnSelectAll() methods, rather than the UI. The UI is not responsible for maintaining the state of data, only for showing it.
Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
WPF Rocks

Related

Select the associated ListViewItem when selecting a control inside the ListView DataTemplate

I have a WinUI 3 ListView that displays a list of items. Every item has a ToggleSwitch and a Expander. When i click on the ToggleSwitch or the Expander the ListView selection does not change.
I found some solutions for WPF but they dont work in WinUI 3:
Selecting a Textbox Item in a Listbox does not change the selected item of the listbox
How can I do this for WinUI 3 so that the associated ListViewItem is selected when the ToggleSwitch or Expander is selected?
You could handle the Tapped event for the Expander and ToggleSwitch and programmatically set the SelectedItem property of the ListView:
private void OnTapped(object sender, TappedRoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
lv.SelectedItem = element.DataContext;
}
XAML:
<ListView x:Name="lv">
<ListView.ItemTemplate>
<DataTemplate>
...
<Expander Tapped="OnTapped" ... />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If you don't want to change selection when you do something programmatically, you can do it this way.
.xaml
<StackPanel>
<Button
Command="{x:Bind ViewModel.TestCommand}"
Content="Click" />
<ListView
x:Name="ListViewControl"
ItemsSource="{x:Bind ViewModel.Items}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<StackPanel>
<ToggleSwitch Toggled="ToggleSwitch_Toggled" />
<Expander Expanding="Expander_Expanding" IsExpanded="{x:Bind IsChecked, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
.xaml.cs
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (ViewModel.IsProgrammatical is false)
{
ListViewControl.SelectedItem = (sender as ToggleSwitch)?.DataContext;
}
}
private void Expander_Expanding(Expander sender, ExpanderExpandingEventArgs args)
{
if (ViewModel.IsProgrammatical is false)
{
ListViewControl.SelectedItem = sender.DataContext;
}
}
ViewModel.cs
public partial class Item : ObservableObject
{
[ObservableProperty]
private string text = string.Empty;
[ObservableProperty]
private bool isChecked;
}
public partial class MainWindowViewModel : ObservableObject
{
public bool IsProgrammatical { get; set; }
[ObservableProperty]
private List<Item> items = new()
{
{ new Item() { Text = "A", IsChecked = false,} },
{ new Item() { Text = "B", IsChecked = false,} },
{ new Item() { Text = "C", IsChecked = false,} },
};
[RelayCommand]
private void Test()
{
IsProgrammatical = true;
Items[1].IsChecked = !Items[1].IsChecked;
IsProgrammatical = false;
}
}
Workaround
In this case, the source collection is untouchable and we can't use a flag if the property was changed programmatically or not, we need to use the Tapped event to make the item selected. But unfortunately, the ToggleSwitch's Tapped event is not fired (at least in my environment). Might be a WinUI bug (issue posted here).
As a workaround, at least until this bug gets fixed, you can use the ToggleButton. I tested it out and the Tapped event is fired.
<DataTemplate x:DataType="local:Item">
<StackPanel>
<ToggleButton Tapped="ToggleButton_Tapped" />
<Expander Tapped="Expander_Tapped" IsExpanded="{x:Bind IsChecked, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
private void ToggleButton_Tapped(object sender, TappedRoutedEventArgs e)
{
ListViewControl.SelectedItem = (sender as ToggleSwitch)?.DataContext;
}
private void Expander_Tapped(Expander sender, TappedRoutedEventArgs e)
{
ListViewControl.SelectedItem = sender.DataContext;
}

C# WPF Directory Treeview with checkboxes: check items on building fails with empty PropertyChanged

In a WPF window I show a treeview with checkboxes with disks/directories on a Pc. When the user expands a node, an event calls folder_Expanded adding the subdirectories of that node.
What should happen is that certain directories show a color (this works) and certain directories are checked if they are found in a XML file. The user can then check or uncheck (sub)directories after which the modified directory selection is again stored in that xml file.
However, I can't get a checkbox in that treeviewitem checked with a certain directory. In the code of the expanded event, I test it with a sample directory. The background color works fine, but the IsSelected line is doing nothing. Reason is that PropertyChanged is null so it doesn't create an instance of PropertyChangedEventArgs. I would say I have everything: a model inheriting from INotifyPropertyChanged and assigned as DataContext in the XAML and setting the property IsChecked of the CheckBox as defined in the XAML via this model.
What do I miss?
Alternatively I would like to know if I can directly set the checkbox to checked, without databinding, like I set the background color? Problem with databinding is when it doesn't work there's no way to debug the code, it just doesn't work....
At the start:
SelectFilesModel selectFilesModel = new SelectFilesModel();
public SelectFiles()
{
InitializeComponent();
Window_Loaded();
}
void folder_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem item = (TreeViewItem)sender;
if (item.Items.Count == 1 && item.Items[0] == dummyNode)
{
item.Items.Clear();
try
{
foreach (string s in Directory.GetDirectories(item.Tag.ToString()))
{
TreeViewItem subitem = new TreeViewItem();
subitem.Header = s.Substring(s.LastIndexOf("\\") + 1);
subitem.Tag = s;
subitem.FontWeight = FontWeights.Normal;
subitem.Items.Add(dummyNode);
subitem.Expanded += new RoutedEventHandler(folder_Expanded);
if (s.ToLower() == "c:\\temp") // Sample directory to test
{
subitem.Background = Brushes.Yellow; // This works!
selectFilesModel.IsChecked = true; // Eventually PropertyChanged is always null!!
}
item.Items.Add(subitem);
}
}
catch (Exception e2)
{
MessageBox.Show(e2.Message + " " + e2.InnerException);
}
}
}
The XAML looks as follows:
<Window.DataContext>
<local:SelectFilesModel/>
</Window.DataContext>
<Grid>
<TreeView x:Name="foldersItem" SelectedItemChanged="foldersItem_SelectedItemChanged" Width="Auto" Background="#FFFFFFFF" BorderBrush="#FFFFFFFF" Foreground="#FFFFFFFF">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Name="img" Width="20" Height="20" Stretch="Fill"
Source="{Binding
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type TreeViewItem}},
Path=Header,
Converter={x:Static local:HeaderToImageConverter.Instance}}"
/>
<TextBlock Name="DirName" Text="{Binding}" Margin="5,0" />
<CheckBox Name="cb" Focusable="False" IsThreeState="True" IsChecked="{Binding IsChecked ,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center"/> </StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>
</Grid>
and the model looks as follows:
public class SelectFilesModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
bool? _isChecked = false;
public bool? IsChecked
{
get { return _isChecked; }
set { this.SetIsChecked(value, true, true); }
}
void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{
if (value == _isChecked)
return;
_isChecked = value;
RaisePropertyChanged("IsChecked");
}
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
} // SelectFilesModel
It would be interesting to see how youuu initialize the TreeView. It really looks like the selectFilesModel is not source of any data binding. It's not even part of your tree.
You are adding TreeViewItem manually (which is not a good idea - see your problem, which wouldn't exist if you would focus on dealing with the data models instead). Because of adding TreeViewItem elements directly, the DataContext of the TreeViewItem is the item itself.
The DataContext of your HeaderTemplate is the header value, which in your case is a string. You see selectFilesModel is never involved.
CheckBox.IsChecked currently binds to this string and we all know string has no property IsChecked.
What you should do is to create the tree using SelectFilesModel.
The following example is your modified code. It is not tested and written with no editor so it may contain minor erros. It should be enough to show the pattern.
Also note that Directory.EnumerateDirectories will perform much better in your scenario than Directory.GetDirectories.
Create an enum to express different states. Each state will map to a color which you set in XAML using a trigger.
enum DirectoryState
{
Default = 0,
Special
}
Then modify SelectFilesModel to allow to reference its children (subdirectories) and add a State enum property
public class SelectFilesModel : INotifyPropertyChanged
{
// TODO::Implement constructor to initialize properties
public event PropertyChangedEventHandler PropertyChanged;
bool? _isChecked = false;
public bool? IsChecked
{
get { return _isChecked; }
set { this.SetValue(value, ref _isChecked, true, true); }
}
DirectoryState _state;
public DirectoryState State
{
get { return _state; }
set { this.SetValue(value, ref _state, true, true); }
}
string _path;
public string Path
{
get { return _path; }
set { this.SetValue(value, ref _path, true, true); }
}
public ObservableCollection<SelectFilesModel> Subdirectories { get; }
void SetValue<TValue>(TValue value, ref TValue field, bool updateChildren, bool updateParent, [CallerMemberName] string propertyName = null)
{
if (value == field)
return;
field = value;
RaisePropertyChanged(propertName);
}
void RaisePropertyChanged(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
Then build the tree using the model. Note that since Expanded is a routed event, you don't have to subscribe to each item explicitly. Just listen to the routed event.
ObservableCollection<SelectFilesModel> TreeRoot { get; }
public SelectFiles()
{
InitializeComponent();
Window_Loaded();
foldersItem.AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(folder_Expanded)));
TreeRoot = new ObservableCollection<SelectFilesModel>() { new SelectFilesModel() };
foldersItem.ItemsSource = TreeRoot;
}
void folder_Expanded(object sender, RoutedEventArgs e)
{
var item = (sender as TreeViewItem).DataContext as SelectFilesModel;
if (item.Subdirectories.Count == 1 && item.Subdirectories[0] == dummyNode)
{
item.Subdirectories.Clear();
try
{
foreach (string s in Directory.EnumerateDirectories(item.Path))
{
var subitem = new SelectFilesModel() { Path = Path.GetDirectoryName(s) };
subitem.Subdirectories.Add(dummyNode);
if (subitem.Path.ToLower() == "c:\\temp") // Sample directory to test
{
subitem.State = DirectoryState.Special; // This works!
subitem.IsChecked = true; // This should work too
}
item.Subdirectories.Add(subitem);
}
}
catch (Exception e2)
{
MessageBox.Show(e2.Message + " " + e2.InnerException);
}
}
}
Finally define the data temnplate with the appropriate triggers and add it to e.g. TreeView.Resources:
<HierarchicalDataTemplate DataType="{x:Type SelectFilesModel}
ItemsSource="{Binding Subdirectories}">
<StackPanel Orientation="Horizontal">
<Image Name="img" Width="20" Height="20" Stretch="Fill"
Source="{Binding
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type TreeViewItem}},
Path=Header,
Converter={x:Static local:HeaderToImageConverter.Instance}}"
/>
<TextBlock Name="DirName" Text="{Binding Path}" Margin="5,0" />
<CheckBox Name="cb" Focusable="False" IsThreeState="True" IsChecked="{Binding IsChecked ,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center"/> </StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding State}" Value="{x:Static DirectoryState.Special}">
<Setter TargetName="DirName" Property="Foreground" Value="Yellow" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>

ComboBox Selected Item not updating

Problem
I am trying to bind a ComboBox's SelectedItem to a custom class but this does not update when the property is changed.INotifyPropertyChanged is implemented.
The DataContext
The DataContext is a custom class which contains many properties, but an extract of this is below. You can see it implements INotifyPropertyChanged and this called when the two properties are changed.
public class BctsChange : INotifyPropertyChanged
{
#region declarations
private byContact _Engineer;
public byContact Engineer
{
get { return _Engineer; }
set
{
_Engineer = value;
NotifyPropertyChanged("Engineer");
OnEngineerChanged();
}
}
private BctsSvc.DOSets _LeadingSet;
public BctsSvc.DOSets LeadingSet
{
get { return _LeadingSet; }
set { _LeadingSet = value; NotifyPropertyChanged("LeadingSet"); }
}
#endregion
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public BctsChange()
{
Engineer = new byContact(Environment.UserName);
}
private void OnEngineerChanged()
{
if (Engineer != null)
{
BctsSvc.DOSets leadSet = GetLeadingSetFromDeptCode(Engineer.DeptCode);
if (leadSet == null) return;
LeadingSet = leadSet;
}
}
private static BctsSvc.DOSets GetLeadingSetFromDeptCode(string DeptCode)
{
BctsSvc.BctsServiceSoapClient svc = new BctsSvc.BctsServiceSoapClient();
BctsSvc.DOSets setX = svc.GetSetFromDeptCode(DeptCode);
return setX;
}
}
The Window XAML
I have several controls on the window, but to keep the code simple I believe the following extract will suffice.
<Window x:Class="MyNamespace.wdSubmit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:MyNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="ucReqForm"
Title="wdSubmit" >
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<GroupBox Header="Engineer Details" Name="grpOwnerDetails" >
<StackPanel Orientation="Vertical">
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="35"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Engineer.FullName, FallbackValue='Please select an engineer by clicking →', Mode=OneWay}" Margin="5,0" IsEnabled="True" FontStyle="Italic" />
<Button Content="{StaticResource icoSearch}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="1" Height="23" Name="btnSelectEngineer" Margin="0,0,5,0" HorizontalAlignment="Stretch" ToolTip="Search for an engineer responsible" Click="btnSelectEngineer_Click" />
</Grid>
<ComboBox Height="23" x:Name="ddSet2" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnTargetUpdated=True}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<my:LabelledDropdown Height="23" x:Name="ddSet" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,NotifyOnTargetUpdated=True,NotifyOnSourceUpdated=True}" Label="e.g. BodyHardware">
<my:LabelledDropdown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</my:LabelledDropdown.ItemTemplate>
</my:LabelledDropdown>
</StackPanel>
</GroupBox>
</StackPanel>
</Window>
The above extract contains:
A Label that contains a contact's name, and a button to search for a contact, bound to the FullName of the Engineer
A ComboBox that contains departments within the company, bound to an ObservableCollection<DOSets>, which contains a list of departments
Two ComboBoxes, one which is a custom one and the other which is temporary to ensure the bug is not within the control. These are Databound to LeadingSet
Window Code Behind
In the code behind I set the DataContext to CurrentChange. When the user wants to select a different Engineer then this will update the selected department for the engineer in CurrentChange.
When the user changes the engineer, the data binding for the engineer is updated, but the selected department (Leading Set) isn't.
//Usings here
namespace MyNamespace
{
public partial class wdSubmit : Window, INotifyPropertyChanged
{
private BctsSvc.BctsServiceSoapClient svc;
private BctsChange _CurrentChange;
public BctsChange CurrentChange
{
get { return _CurrentChange; }
set { _CurrentChange = value; OnPropertyChanged("CurrentChange"); }
}
private List<BctsSvc.DOSets> _LeadingSets;
public List<BctsSvc.DOSets> LeadingSets
{
get
{
return _LeadingSets;
}
}
public wdSubmit()
{
InitializeComponent();
svc = new BctsSvc.BctsServiceSoapClient();
_LeadingSets = svc.GetLeadSets().ToList();
OnPropertyChanged("LeadingSets");
this._CurrentChange = new BctsChange();
this.DataContext = CurrentChange;
CurrentChange.PropertyChanged += new PropertyChangedEventHandler(CurrentChange_PropertyChanged);
}
void CurrentChange_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("CurrentChange");
OnPropertyChanged(e.PropertyName);
}
private void btnSelectEngineer_Click(object sender, RoutedEventArgs e)
{
byContact newContact = new frmSearchEngineer().ShowSearch();
if (newContact != null)
{
CurrentChange.Engineer = newContact;
PropertyChanged(CurrentChange, new PropertyChangedEventArgs("LeadingSet"));
PropertyChanged(CurrentChange.LeadingSet, new PropertyChangedEventArgs("LeadingSet"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(CurrentChange, new PropertyChangedEventArgs(propertyName));
}
}
}
I've realised the problem may be due to the LeadingSet, returned when the engineer is changed, being a different instance to that in the ObservableCollection.

Selecting all CheckBoxes in ListBox not displayed properly

If I press a button "Check All" all CheckBoxes in a ListBox should be selected and added to a list where all checked items are stored. The problem is that only the visible checkboxes are updated properly.
Here is my CheckBoxListItem class:
public class Cbli : INotifyPropertyChanged
{
private string _name;
private Boolean _isChecked;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
public bool IsChecked
{
get { return _isChecked; }
set { _isChecked = value; OnPropertyChanged("IsChecked"); }
}
public override string ToString()
{
return string.Format("Name: {0}, IsChecked: {1}", _name, _isChecked);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window x:Class="ListBoxBuggy.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:listBoxBuggy="clr-namespace:ListBoxBuggy"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}" WindowStartupLocation="CenterScreen">
<Window.Resources>
<DataTemplate x:Key="CheckBoxListItemTemplateNew" DataType="listBoxBuggy:Cbli">
<CheckBox Name="CheckBox"
IsChecked="{Binding IsChecked}"
Checked="Update"
Unchecked="Update"
FontSize="14">
<TextBlock Text="{Binding Name}" FontSize="14"/>
</CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox HorizontalAlignment="Left" Height="300" VerticalAlignment="Top" Width="168"
ItemsSource="{Binding MyItemList}"
ItemTemplate="{StaticResource CheckBoxListItemTemplateNew}"
/>
<ListBox HorizontalAlignment="Left" Height="290" Margin="297,10,0,0" VerticalAlignment="Top" Width="195"
ItemsSource="{Binding CheckedItems}"
/>
<Button Content="Check All" HorizontalAlignment="Left" Margin="173,10,0,0" VerticalAlignment="Top" Width="75" Click="Check_All"/>
<Button Content="Uncheck All" HorizontalAlignment="Left" Margin="173,52,0,0" VerticalAlignment="Top" Width="75" Click="Uncheck_All"/>
</Grid>
</Window>
And the code behind:
public partial class MainWindow : Window
{
public ObservableCollection<Cbli> MyItemList { get; set; }
public ObservableCollection<Cbli> CheckedItems { get; set; }
public MainWindow()
{
// add dummy data
MyItemList = new ObservableCollection<Cbli>();
CheckedItems = new ObservableCollection<Cbli>();
for (int i = 0; i < 20; i++)
{
Cbli cbli = new Cbli
{
Name = "Test " + i,
IsChecked = i < 5 || i > 15
};
MyItemList.Add(cbli);
if (cbli.IsChecked)
CheckedItems.Add(cbli);
}
InitializeComponent();
}
private void Update(object sender, RoutedEventArgs e)
{
CheckBox selectedCheckbox = (CheckBox)sender;
Cbli cbli = (Cbli)selectedCheckbox.DataContext;
if (cbli.IsChecked)
CheckedItems.Add(cbli);
else
CheckedItems.Remove(cbli);
}
private void Check_All(object sender, RoutedEventArgs e)
{
foreach (Cbli cbli in MyItemList)
cbli.IsChecked = true;
}
private void Uncheck_All(object sender, RoutedEventArgs e)
{
foreach (Cbli cbli in MyItemList)
cbli.IsChecked = false;
}
}
After scrolling down, so all 20 items on the left list are visible and clicking then the "check all" button is working pretty well, but I don't know why.
Can someone please tell me what is wrong with that implementation? Checking/unchecking a single CheckBox is working, but the Check/Uncheck all buttons aren't working properly.
The comment from Blam (setting VirtualizingStackPanel.VirtualizationMode="Standard" was nearly the solution.
Add VirtualizingStackPanel.IsVirtualizing="False":
<ListBox HorizontalAlignment="Left" Height="300" VerticalAlignment="Top" Width="168"
ItemsSource="{Binding MyItemList}"
ItemTemplate="{StaticResource CheckBoxListItemTemplateNew}"
VirtualizingStackPanel.IsVirtualizing="False" />
This solved the problem (at least for me)

WPF behaviour to check/uncheck a checkbox in a list item

I have an application with several item controls (treeviews and others) which contain an item template with a checkbox inside. This checkbox checked state is bound to an IsChecked property of the item view model. This works correctly when clicking on the checkbox, but it's impossible to check/uncheck them with the keyboard (I believe this is due to the fact that the checkbox itself never gets the focus).
I like the solution proposed by DLeh in here: https://stackoverflow.com/a/24327765/352826 but I would like an improvement: Instead of having the behaviour calling a command on the base view model (the vm which contains the list of items), I would like the behaviour to directly act on the IsChecked property of the item.
My problem is that I don't know how to modify the behaviour or how to set up the binding on it, so that the behaviour can have access to the item's IsChecked property.
So, instead of the following:
<DataGrid>
<i:Interaction.Behaviors>
<shared:ToggleSelectOnSpace ToggleSelectCommand="{Binding Data.ToggleSelectParticipantCommand, Source={StaticResource BindingProxy}}" />
</i:Interaction.Behaviors>
...
</DataGrid>
I would have something like this:
<DataGrid>
<i:Interaction.Behaviors>
<shared:ToggleSelectOnSpace ItemsIsSelectedProperty="{Binding IsChecked}" />
</i:Interaction.Behaviors>
...
</DataGrid>
Update
I should add that my current implementation uses the PreviewKeyUp event in the itemscontrol and the following code behind implementation. The problem with this approach is that I have this code in many code behind files, so there is a lot of duplication. My goal is to replace this by a behaviour.
private void TreeView_OnPreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
var tree = (TreeView) sender;
var item = tree.SelectedItem as IsSelectedViewModelBase;
if (item != null)
{
item.IsSelected = !item.IsSelected;
e.Handled = true;
}
}
}
Update 2
This is the item template and the checkbox is not checked when you press the space bar with the item selected.
<DataTemplate DataType="{x:Type viewModels:ItemViewModel}" >
<StackPanel Orientation="Horizontal" >
<CheckBox Focusable="False" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" />
<StackPanel Margin="2">
<TextBlock Text="{Binding Username}" FontWeight="Bold" />
<TextBlock Text="{Binding FullName}" />
</StackPanel>
</StackPanel>
</DataTemplate>
Not an answer to the asked question per se, but the following will illustrate that it is possible for a check box within a list item to receive keyboard focus.
I created a new WPF project using the default Visual Studio template. This creates a single window called "MainWindow". Here's the contents of the XAML and code-behind of that window.
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thingies = new List<Thingy>
{
new Thingy { Name = "abc", IsChecked = false },
new Thingy { Name = "def", IsChecked = true },
new Thingy { Name = "ghi", IsChecked = false },
new Thingy { Name = "jkl", IsChecked = true },
new Thingy { Name = "mno", IsChecked = false },
}.ToArray();
DataContext = this;
}
public Thingy[] Thingies { get; private set; }
public class Thingy : INotifyPropertyChanged
{
public string Name { get; set; }
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked != value)
{
_isChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("IsChecked"));
}
Console.WriteLine(Name + " = " + _isChecked);
}
}
}
bool _isChecked;
public event PropertyChangedEventHandler PropertyChanged;
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding Thingies}">
<ListBox.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1" Width="400">
<TextBlock Text="{Binding Name}" />
<CheckBox IsChecked="{Binding IsChecked}" />
</UniformGrid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>

Categories