Why are my TreeViewItems acting like RadioButtons? - wpf

I have a WPF TreeView for which I've implemented a small model class behind the scenes. I bind a list of them to the TreeView's ItemsSource when creating the control. (I've pared the code here down a bit for the sake of simplicity, but it should be reproducable.)
public class TreeViewItemModel
{
public ObservableCollection<TreeViewItemModel> Children { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
public TreeViewItemModel()
{
Children = new ObservableCollection<TreeViewItemModel>();
IsSelected = false;
}
}
public partial class MainWindow : Window
{
public ObservableCollection<TreeViewItemModel> MyTree { get; set; }
public MainWindow()
{
InitializeComponent();
// Add some dummy values
List<TreeViewItemModel> items = new List<TreeViewItemModel>();
for (int i = 0; i < 10; i++) items.Add(new TreeViewItemModel() { Name = ("Node" + i) });
MyTree = new ObservableCollection<TreeViewItemModel>(items);
DataContext = this;
}
}
My TreeViewItems themselves contain checkboxes. Now, what I'd like to do is to bind IsSelected to the checkbox so that at the end of the day I (hopefully) have a list of TreeViewItemModel classes with IsSelected set to whether or not the checkbox is checked.
To that end, I have this style:
<Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
and this TreeView declaration:
<TreeView ItemsSource="{Binding MyTree}" >
<TreeView.Resources>
<DataTemplate DataType="{x:Type UI:TreeViewItemModel}">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
This almost works. I can create a list of items programmatically and they get bound to the TreeView, check off items in my TreeView, and when I check them in C# IsSelected is set appropriately.
Except for one thing: my TreeViewItems all act like RadioButtons. I click one, and it sets IsSelected to true. I rejoice! But then I click on another... and it deselects the first TreeViewItem! I can never have more than one selected at a time.
But... why?! I don't understand at all. They're all bound to different items on the backend, so why would setting IsSelected change the state of another item?
:'(

In your Style for TreeViewItem you bind TreeViewItem.IsSelected to IsSelected property of your view model which basically means that CheckBox will be checked if TreeViewItem is selected. It happens so because WPF TreeView does not support multi selection.
You can easily add multi selection by changing TreeViewItem content into CheckBox or ToggleButton, exactly what you're trying to achieve, but then you cannot bind TreeViewItem.IsSelected to your view model.
What currently happens is
you click to select one item
previous TreeViewItem.IsSelected is set to false
this is passed to your view model by IsSelected
which is then passed back to CheckBox.IsChecked
new TreeViewItem.IsSelected is set to true
and so on
Remove Style for TreeViewItem and leave only CheckBox.IsChecked to IsSelected binding
On a side note you don't need StackPanel when you want to show just one element like CheckBox

You try removing your style? You should then see multiple selections

Related

Bind IsExpaned to my own custom TreeViewItem in Wpf?

I want to bind my custom TreeViewItem to IsExpanded.
The normal way without a custom TreeView Item would look like this.
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"></Setter>
</Style>
</TreeView.ItemContainerStyle>
But I want to bind it to my own TreeViewItem for example named CoolTreeItemModel.
CoolTreeItemModel could look like this:
public class CoolTreeItemModel : XY
{
public LocalTreeItemModel()
{
TreeViewItems = new List<CoolTreeItemModel>();
}
public List<CoolTreeItemModel> TreeViewItems { get; set; }
public SomeType IsValid { get; set; }
public bool IsExpanded { get; set; }
}
How CoolTreeItemModel is bound:
<TreeView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding TreeRoots}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="models:CoolTreeItemModel"
ItemsSource="{Binding TreeViewItems}"></HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
How can I bind to the IsExpanded property of CoolTreeItemModel ?
Thanks for your help.
The "normal way" is applicable in this case. Each CoolTreeItemModel will be implicitly wrapped in a TreeViewItem container so you should be able to bind to your IsExpanded property. You may want to set the Mode of the binding to TwoWay though:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"></Setter>
</Style>
</TreeView.ItemContainerStyle>
Binding requires the use of dependency properties. Dependency Properties On MSDN You would need to define a dependency property like so:
public ClassName
{
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(bool), typeof(ClassName));
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
}
From there you can then include the namespace of your class in the top of your xaml and then bind to IsExpanded like normal
<MyNamespace:ClassName IsExpanded="{Binding Value}" />
I Would like to raise one concern of my though. When developers new to XAML/WPF start developing their own controls, the distinction between view data and business data gets muddled. If this is your own control to be consumed by others, there is an entire style template that may need to be created and used. All of this binding should be taking place in this style template, following the example set forth by Microsoft. Also a custom control should have no view model or defined data context as it is expected to be provided by the consumer, meaning a lot of your logic is going to be in the code behind, which is okay as a custom control is only view related and shouldn't have any business logic in it.

WPF ListView ignores SelectedItem-change

I am working with a ListView in WPF which is bound an observable collection. I then bound the SelectedItem-Property to a property of the ViewModel.
When I select an item in the ListView via GUI "SelectedItem" is changed.
When I change "SelectedItem" in the ViewModel, the ListView only changes, when I set the SelectedItem to NULL.
When I set any other (valid!) object (like the first entry of the ObservableCollection) the ListView just ignores it.
Furthermore: When I want to "Veto" a SelectedItem Change (because data is not saved), the ListView highlights the new selected item instead of the SelectedItem-Property of the ViewModel.
I already tried to change the Binding to Mode=TwoWay - doesn't work as well (otherwise the "NULL" change to SelectedItem wouldn't work as well)
This is the code from the view:
<ListView ItemsSource="{Binding Configurations}" SelectedItem="{Binding SelectedUserConfiguration}" SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Header="User Configuration" DisplayMemberBinding="{Binding ConfigurationName}" Width="200" />
</GridView>
</ListView.View>
</ListView>
And my ViewModel:
public ObservableCollection<UserConfigurationViewModel> Configurations { get; private set; }
private UserConfigurationViewModel _selectedUserConfiguration;
public UserConfigurationViewModel SelectedUserConfiguration
{
get {
return this._selectedUserConfiguration;
}
set
{
if (this._selectedUserConfiguration != null && this._selectedUserConfiguration.WasChanged)
{
if (ask-user)
{
this._selectedUserConfiguration.Reset();
this._selectedUserConfiguration = value;
}
}
else
{
this._selectedUserConfiguration = value;
}
NotifyOfPropertyChange(() => this.SelectedUserConfiguration);
}
}
In order to select the selected item in any collection control from code, the selected item must be an actual item from the collection that is bound to the ItemsSource property. This can be achieved easily enough with LinQ if your collection items have at least one unique property:
SelectedUserConfiguration = Configurations.Where(c => c.UniqueProperty ==
valueOfItemToSelect).FirstOrDefault();
If your data type objects do not have a unique property, you can simply add an int Id property for this purpose.
I was just having the same issue with a TabControl.
I've tried to create a int property and bind it to SelectedIndex, but no success.
Strangely enough this did the trick:
In your ListView set IsSynchronizedWithCurrentItem="True"
Set a ListView.Resources like this:
<ListView.Resources>
<Style TargetType="ListViewItem" x:Key="ListViewTemplate">
<Setter Property="IsSelected" Value="True" />
</Style>
</ListView.Resources>

Inverted TwoWay-MultiBinding

I'm trying to express an enumeration property in my view-model as a set of radio buttons in my view. So far, so good; I can express that with a two-way MultiBinding:
(rb1.IsChecked, rb2.IsChecked, rb3.IsChecked) <-> vm.Value
The multi-binding used here would feature a multi-converter that converts between (bool, bool, bool) <-> MyValue; obviously, one of the (three) allowable values of the MyValue type is chosen based on which bool is true, and vice-versa.
This is already a bit inconvenient, though: I cannot define that binding in my view's Xaml, as multi-bindings have to be defined from the side of the single value. Hence, I have to define the multi-binding in code-behind and use SetBinding set it on my view model's Value property.
Now, the issue that I'm stuck at is that I'm not just binding one set of radio buttons to that value, but two. Hence, my bindings would have to look like this:
(rbA1.IsChecked, rbA2.IsChecked, rbA3.IsChecked) <-> vm.Value <-> (rbB1.IsChecked, rbB2.IsChecked, rbB3.IsChecked)
The problem is that I cannot use SetBinding to connect several bindings to vm.Value at a time.
Solutions that I have tried so far are:
Use one big multi-binding, binding to all radio buttons at a time. This would mean a binding of the form (rbA1.IsChecked, rbA2.IsChecked, rbA3.IsChecked, rbB1.IsChecked, rbB2.IsChecked, rbB3.IsChecked) <-> vm.Value. The problem with this solution is that if one of the radio buttons (say, rbB2) is checked, I have no way of telling whether rbA2 (unchecked) or rbB2 (checked) has the "new, correct" value.
Abstract the radio button groups first by defining a radio group control that exposes only one SelectedIndex property. This property can then be conveniently bound to my vm.Value property from all instances of my radio group control. While feasible, it requires writing a new control class and I wonder whether this is the only way in WPF.
Bind one set of radio buttons to another one: By two-way-binding rbB1 to rbA1, rbB2 to rbA2, and so on, and using a multi-binding between my vm.Value and the first set of radio buttons only, I would achieve the desired effect, but I don't like the notion of having a "master radio group". It would be abusing GUI elements for data transfer.
Do everything in code-behind and manually update radio buttons and view-model value. Of course this is a viable fallback solution, but this feels like it should be feasible in Xaml/with bindings.
Bind VMEnum to each RadioButton seperately(use single two-way binding). Each binding should have CommandParameter it's enum. Like:
<RadioButton IsChecked="{Binding VMEnum, Converter={StaticResource EnumConverter}, ConverterParameter={Enums:VMEnums.FirstRadioButtonGroupA}}" />
In the converter,
Convert should return correct value(true/false) depending of the VMEnum and COmmandParameter. Essentially the logic is VMEnum == (YourEnum)CommandParameter.
ConvertBack should return correct Enum based on IsChecked. If IsChecked is true, return the correct enum. Otherwise return Binding.DoNothing which will abort the binding for that specific case.
Using complex multibinding with converters and codebehind will not only make your code harder to debug but even harder to test. In my opinion it's better to express each set of radio buttons (flags) as a view model. Evaluate your value when any of the radio buttons is checked/unchecked.
RadioButtonGroup
public class RadioButtonGroup : ViewModel {
public RadioButtonGroup(string groupName, int count, Action<bool[]> whenAnyChanaged = null) {
RadioButtons = Enumerable.Range(0, count).Select(_ => {
var button = new RadioButton { GroupName = groupName };
button.PropertyChanged += (s, e) => {
if (e.PropertyName == "IsChecked")
whenAnyChanaged(Flags);
};
return button;
}).ToList();
}
public List<RadioButton> RadioButtons { get; private set; }
public bool[] Flags { get { return RadioButtons.Select(rb => rb.IsChecked).ToArray(); } }
}
RadioButton
public class RadioButton : ViewModel {
private bool isChecked;
public bool IsChecked {
get { return isChecked; }
set { SetProperty(ref this.isChecked, value); }
}
public string GroupName { get; set; }
}
MainViewModel
public class MainViewModel : ViewModel {
public MainViewModel() {
GroupA = new RadioButtonGroup("A", 10, flags => GroupToggle(flags, GroupB.Flags));
GroupB = new RadioButtonGroup("B", 10, flags => GroupToggle(GroupA.Flags, flags));
}
public RadioButtonGroup GroupA { get; private set; }
public RadioButtonGroup GroupB { get; private set; }
void GroupToggle(bool[] groupA, bool[] groupB) {
MyValue = Evaluate(groupA, groupB);
}
}
View
<Window x:Class="WpfLab.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="RadioButton">
<RadioButton IsChecked="{Binding IsChecked, Mode=OneWayToSource}" GroupName="{Binding GroupName}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding GroupA.RadioButtons}" ItemTemplate="{StaticResource ResourceKey=RadioButton}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<ListBox Grid.Row="1" ItemsSource="{Binding GroupB.RadioButtons}" ItemTemplate="{StaticResource ResourceKey=RadioButton}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>

Silverlight Combobox ItemTemplate Stuck Displaying First Selected Item

I've got a Combobox whose ItemTemplate is bound to a DataTemplate containing one of my custom label controls. All the custom control does is localize the content assigned to it.
The Combobox (when closed) will display the text of the first item selected. However when the selected item is changed, the display of the closed Combobox will not update. I know the actual selected item is updated because it's bound to a property that changes correctly. The only problem is the display text.
So for instance if I select the item with text 'Item 1' the closed Combobox will display 'Item 1'. Then if I select 'Item 2' the closed Combobox will still display 'Item 1'.
Here's how it's set up ('Name' is a property of the items being bound in the ItemsSource):
<Grid.Resources>
<DataTemplate x:Key="MyTemplate">
<MyCustomLabel Content="{Binding Name}" />
<DataTemplate>
</Grid.Resources>
<Combobox ItemsSource="{Binding MyItems}" ItemTemplate="{StaticResource MyTemplate}" />
Below is the code for my label control:
public class MyLabel : Label
{
/// <summary>
/// When reassigning content in the OnContentChanged method, this will prevent an infinite loop.
/// </summary>
private bool _overrideOnContentChanged;
protected override void OnContentChanged(object oldContent, object newContent)
{
// if this method has been called recursively (since this method assigns content)
// break out to avoid an infinite loop
if (_overrideOnContentChanged)
{
_overrideOnContentChanged = false;
return;
}
base.OnContentChanged(oldContent, newContent);
var newContentString = newContent as string;
if (newContentString != null)
{
// convert the string using localization
newContentString = LocalizationConverter.Convert(newContentString);
// override the content changed method
// will prevent infinite looping when this method causes itself to be called again
_overrideOnContentChanged = true;
Content = newContentString;
}
}
}
Any advice would be greatly appreciated. Thanks!
Perform a 2-way databinding on the SelectedItem property of the combo box.
The target property that you're binding the combo-box to - should raise a PropertyChanged event.
<ComboBox ItemsSource={Binding Path=Source} SelectedItem={Binding Path=CurrentItem, Mode=TwoWay}/>
class ComboContext : INotifyPropertyChanged
{
public List<object> Source { get; set; } // attach your source here
public object CurrentItem { get { return mCurrentItem; } set { mCurrentItem = value; OnPropertyChanged("CurrentItem"); } } // bind to this property
private object mCurrentItem;
void OnPropertyChanged(string name)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
The code sample might not be "complete" - it's 1am and I'm kinda tired, but it should put you on the right track.
Edit2.
Just noticed your template, it's wrong, the whole idea is wrong.
<ComboBox ItemsSource={Binding Path=Source} SelectedItem={Binding Path=CurrentItem, Mode=TwoWay}>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text={Binding}/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Notice that the texblock's text is a binding with no path, (and it's a default binding too.) the no-path binding means that this textblock will bind to whatever is directly "underneath".

CheckedItems property for custom CheckBoxList control in Silverlight

I need to implement CheckBoxList control with ItemsSource and CheckedItems properties. Items from ItemsSource should be displayed as checked checkboxes if CheckedItems contains these values or unchecked otherwise. Also I need two-way databinding support for CheckedItems property (value of this property should be updated when user clicks on checkboxes).
Here some code which probably can help to understand my problem
XAML:
<UserControl x:Class="Namespace.Controls.CheckBoxList" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListBox x:Name="LayoutRoot">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Code behind:
public partial class CheckBoxList : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CheckBoxList), null);
public static readonly DependencyProperty CheckedItemsProperty = DependencyProperty.Register("CheckedItems", typeof(IEnumerable), typeof(CheckBoxList), null);
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public IEnumerable CheckedItems
{
get { return (IEnumerable)GetValue(CheckedItemsProperty); }
set { SetValue(CheckedItemsProperty, value); }
}
public CheckBoxList()
{
InitializeComponent();
LayoutRoot.SetBinding(ItemsControl.ItemsSourceProperty, new Binding("ItemsSource") { Source = this });
}
}
I think that I need to bind ListBox to UserControl with custom converter, which will return collection of items with additional IsChecked property, but it works only in case of one-way data binding.
Looks like I need two-way binding to two properties at one time, but I don't know how to implement it and will appreciate any help with this issue.
Thanks in advance.
First of all you should consider deriving from ListBox rather than UserControl. The ListBox already does most of what you want.
Secondly consider one way binding to an IList. You can then add and remove entires to that IList as the respective items are selected.
Rather than try to bind a CheckBox control in an Item Template you make a copy of the ListBox styles, place them in Generic.xaml as the style of your new control. Then modify the unselected and selected visual states using a checked and unchecked check box as part of the visual appearance.
Now you can attach to the SelectionChanged event and use the Event args AddedItems list to add to the bound IList and the RemovedItems list to remove items from the bound list.
You would need to clear and re-add the set of items to the list box SelectedItems list when either your CheckedItems is assigned or the ItemsSource is changed.
There are probably a number gotchas that you will need to work round but this seems like a more direct path to your goal than starting from scratch with a UserControl base.
Add an observable collection for your list box datasource to your datacontext:
private ObservableCollection<MyItem> _myItems;
public ObservableCollection<MyItem> MyItems
{
get { return _searchByFields; }
set
{
_myItems = value;
}
}
Add a class to hold the data about your checkboxes:
public class MyItem
{
public bool Checked {get; set; }
public string MyItemValue { set ; set; }
}
Then in your data template bind listbox to the collection and your data template checkboxes to the respective MyItem properties:
<UserControl x:Class="Namespace.Controls.CheckBoxList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListBox x:Name="LayoutRoot"
DataContext="[Dataconext here]"
ItemsSource={Binding MyItems}>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked, Mode=TwoWay}"
Content="{Binding MyItemValue}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Don't forget to set the DataContext of the binding to the appropriate class (you might be doing this in the XAML or the code behind perhaps)

Resources